nrbnlulu / strawberry-django-auth

Authentication system for django using strawberry
https://nrbnlulu.github.io/strawberry-django-auth/
MIT License
68 stars 31 forks source link

it's not working with django channels: ERROR: ASGI callable returned without starting response. #499

Closed shmoon-kr closed 7 months ago

shmoon-kr commented 8 months ago

Description

I've set up django channels as described in the official documentation, but when I throw a query, I get the message "ERROR: ASGI callable returned without starting response". I think this is an issue with django channels, as the other parts are the same and it worked when I served it with GraphQLAsyncView.

django channels setting

arg_schema = JwtSchema(query=Query, mutation=Mutation, subscription=None)

websocket_urlpatterns = [
    re_path("^graphql", channels_jwt_middleware(GraphQLWSConsumer.as_asgi(schema=arg_schema))),
]
gql_http_consumer = AuthMiddlewareStack(
    channels_jwt_middleware(GraphQLHTTPConsumer.as_asgi(schema=arg_schema))
)
application = ProtocolTypeRouter(
    {
        "http": URLRouter(
            [
                re_path("^graphql", gql_http_consumer),
                re_path("^", django_asgi_app),
            ]
        ),
        "websocket": AuthMiddlewareStack(URLRouter(websocket_urlpatterns)),
    }
)

-> got "ASGI callable returned without starting response" error

without channels

urlpatterns = [
    path('admin/', admin.site.urls),
    path("graphql/", AsyncGraphQLView.as_view(schema=schema)),
]

-> no problem

Am I missing something?

nrbnlulu commented 8 months ago

Sorry, I can't tell why isn't it working for you from just this code. If you set a brake point at the middleware does it reaches there? does it exists correctly?

shmoon-kr commented 8 months ago

Yes.

I set a break point at if asyncio.iscoroutinefunction(inner): line of the following function. It reached at the breakpoint during application loading.

and variables are

database_sync_to_async = {type} <class 'channels.db.DatabaseSyncToAsync'>
 context_to_thread_executor = {WeakKeyDictionary: 0} <WeakKeyDictionary at 0x75741b3fce80>
 deadlock_context = {ContextVar} <ContextVar name='deadlock_context' at 0x75741b3ef790>
 single_thread_executor = {ThreadPoolExecutor} <concurrent.futures.thread.ThreadPoolExecutor object at 0x75741b3fcd30>
 thread_sensitive_context = {ContextVar} <ContextVar name='thread_sensitive_context' at 0x75741b3ef7e0>
 threadlocal = {_local} <_thread._local object at 0x75741b3ef470>
inner = {function} <function GraphQLWSConsumer at 0x75741a2db880>
 consumer_class = {type} <class 'strawberry.channels.handlers.ws_handler.GraphQLWSConsumer'>
 consumer_initkwargs = {dict: 1} {'schema': """Date with time (isoformat)"""\nscalar DateTime\n\ntype DjangoModelType {\n  pk: ID!\n}\n\n"\n     Errors messages and codes mapped to\n    fields or non fields errors.\n    Example:\n    {\n        field_name: [\n            {\n                \"message\": \"error message\",\n                \"code\": \"error_code\"\n            }\n        ],\n        other_field: [\n            {\n                \"message\": \"error message\",\n                \"code\": \"error_code\"\n            }\n        ],\n        nonFieldErrors: [\n            {\n                \"message\": \"error message\",\n                \"code\": \"error_code\"\n            }\n        ]\n    }\n    "\nscalar ExpectedError\n\ntype Mutation {\n  "### Checks if a token is not expired and correct.\n\n    *Note that this is not for refresh tokens.*\n    "\n  verifyToken(token: String!): VerifyTokenType!\n\n  "Update user model fields, defined on settings.\n\n    User must be verified.\n    "\n  updateAccount(firstName: String...

def channels_jwt_middleware(inner: Callable):
    from channels.db import database_sync_to_async

    if asyncio.iscoroutinefunction(inner):
        get_user_or_error_async = database_sync_to_async(get_user_or_error)

        async def middleware(scope, receive, send):
            if not scope.get(USER_OR_ERROR_KEY, None):
                user_or_error: UserOrError = await get_user_or_error_async(scope)
                scope[USER_OR_ERROR_KEY] = user_or_error
            return await inner(scope, receive, send)

    else:  # pragma: no cover
        raise NotImplementedError("sync channels middleware is not supported yet.")
    return middleware
shmoon-kr commented 8 months ago

It's a bit stange. When the application is starting, it reaches at the breakpoint. But when I resume it and submit a graphql query via graphiql, it doesn't reach there.

nrbnlulu commented 8 months ago

your revolvers are running correctly? BTW if you want you can contact me on discord nir_benlulu

shmoon-kr commented 8 months ago

Yes. Resolvers are working correctly because there's no problem in running with AsyncGraphQLView without channels.

I just setup a test project at github. https://github.com/shmoon-kr/channels-auth-test

If you have a moment, I'd appreciate it if you could take a look. Next time I'll contact you on discord.

nrbnlulu commented 8 months ago

try to change

urlpatterns = [
    path('admin/', admin.site.urls),
    path("graphql/", AsyncGraphQLView.as_view(schema=schema)),
]

to

urlpatterns = [
    path('admin/', admin.site.urls),
    path("graphql", AsyncGraphQLView.as_view(schema=schema)),
]

Django said something about post request :confused:

nrbnlulu commented 7 months ago

Issue occurs here. The request object has no scope in it it's under request.consumer https://github.com/nrbnlulu/strawberry-django-auth/blob/887bd02ed36139d0d73b13529e0ac0f6ca9650e6/gqlauth/core/middlewares.py#L115

And here context doesn't have a request object using getattr on channels requests https://github.com/nrbnlulu/strawberry-django-auth/blob/887bd02ed36139d0d73b13529e0ac0f6ca9650e6/gqlauth/jwt/types_.py#L172

nrbnlulu commented 7 months ago

PR is welcomed. I might be able to solve this on thursday

nrbnlulu commented 7 months ago

closed in #504