Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to integrate asgi3 middlewares #263

Open
virajkanwade opened this issue May 24, 2022 · 5 comments
Open

Unable to integrate asgi3 middlewares #263

virajkanwade opened this issue May 24, 2022 · 5 comments

Comments

@virajkanwade
Copy link

virajkanwade commented May 24, 2022

Describe the bug
A description of what the bug is, possibly including how to reproduce it.

I am trying to include SentryAsgiMiddleware.

I have to hack it a little as per the known issue as:

class SentryAsgi3Middleware1(SentryAsgiMiddleware):
    """SentryAsgi3Middleware"""

    # pylint: disable=method-hidden
    async def __call__(self, *args, **kwargs):
        """Override __call__ slot"""
        return await self._run_asgi3(*args, **kwargs)

But the problem arrises when a URL is actually called.

TypeError: SentryAsgiMiddleware._run_asgi3() missing 1 required positional argument: 'send'

@virajkanwade
Copy link
Author

Using https://github.com/encode/starlette/blob/b8ea367b4304a98653ec8ce9c794ad0ba6dcaf4b/starlette/middleware/base.py as reference, if I change the function to:

class SentryAsgi3Middleware(SentryAsgiMiddleware):
    """SentryAsgi3Middleware"""

    # pylint: disable=method-hidden
    async def __call__(self, scope: Request, receive: Callable[[Request], Awaitable[Response]], send: Callable[[Response], Awaitable[None]]) -> Response:
        """Override __call__ slot"""
        return await self._run_asgi3(*args, **kwargs)

I get AttributeError: 'SentryAsgi3Middleware' object has no attribute '__qualname__' which is AmbiguousMethodSignatureError

so somewhere normalize_middleware is failing to properly convert it.

@RobertoPrevato
Copy link
Member

Hi @virajkanwade,
Sorry for the late response. Yours is a very good question and this should have been documented explicitly.
BlackSheep never had support for ASGI middlewares, because ASGI request scope is all dict, list, and scalar values, while BlackSheep always handled Request and Response classes, even before being made compatible with ASGI for the main request-response cycle.

I suspect that it won't be possible to add support for ASGI middlewares to BlackSheep, because it really needs to handle instances of Request and Response types, not dict. Adding support for a single middleware might be simple, but not so adding support for multiple ASGI middlewares in a row, for example, or mixed with BlackSheep specific middlewares.

@virajkanwade
Copy link
Author

Hi @RobertoPrevato

Found a sample code for Starlette. Inside the sample asgi3 middleware def call:

❯ python app.py                                                                                                                                                                                                            ─╯
INFO:     Started server process [8701]
INFO:     Waiting for application startup.
  File "/private/tmp/timing-starlette-asgi-example/app.py", line 44, in <module>
    uvicorn.run(app)
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/main.py", line 463, in run
    server.run()
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/server.py", line 60, in run
    return asyncio.run(self.serve(sockets=sockets))
  File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/lifespan/on.py", line 84, in main
    await app(scope, self.receive, self.send)
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/starlette/applications.py", line 133, in __call__
    await self.error_middleware(scope, receive, send)
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 87, in __call__
    await self.app(scope, receive, send)
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/timing_asgi/middleware.py", line 31, in __call__
    traceback.print_stack()
{'type': 'lifespan', 'asgi': {'version': '3.0', 'spec_version': '2.0'}, 'app': <starlette.applications.Starlette object at 0x105d73d30>}
<bound method LifespanOn.receive of <uvicorn.lifespan.on.LifespanOn object at 0x106fb8f70>>
<bound method LifespanOn.send of <uvicorn.lifespan.on.LifespanOn object at 0x106fb8f70>>
2022-05-23 22:24:25 DEBUG [timing_asgi.middleware:46] ASGI scope of type lifespan is not supported yet
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
  File "/private/tmp/timing-starlette-asgi-example/app.py", line 44, in <module>
    uvicorn.run(app)
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/main.py", line 463, in run
    server.run()
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/server.py", line 60, in run
    return asyncio.run(self.serve(sockets=sockets))
  File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 366, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/starlette/applications.py", line 133, in __call__
    await self.error_middleware(scope, receive, send)
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 100, in __call__
    await self.app(scope, receive, _send)
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/timing_asgi/middleware.py", line 31, in __call__
    traceback.print_stack()
{'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'server': ('127.0.0.1', 8000), 'client': ('127.0.0.1', 63904), 'scheme': 'http', 'method': 'GET', 'root_path': '', 'path': '/', 'raw_path': b'/', 'query_string': b'', 'headers': [(b'host', b'127.0.0.1:8000'), (b'user-agent', b'curl/7.79.1'), (b'accept', b'*/*')], 'app': <starlette.applications.Starlette object at 0x105d73d30>}
<bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x107029060>>
<function ServerErrorMiddleware.__call__.<locals>._send at 0x107050940>
INFO:     127.0.0.1:63904 - "GET / HTTP/1.1" 200 OK
^CINFO:     Shutting down
INFO:     Waiting for application shutdown.
INFO:     Application shutdown complete.
INFO:     Finished server process [8701]

Similar call stack for Blacksheep application call

❯ bin/run_dev                                                                                                                                                                                                              ─╯
INFO:     Will watch for changes in these directories: ['/private/tmp/blacksheep-app']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [16277] using statreload
INFO:     Started server process [16289]
INFO:     Waiting for application startup.
  File "<string>", line 1, in <module>
  File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/multiprocessing/spawn.py", line 116, in spawn_main
    exitcode = _main(fd, parent_sentinel)
  File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/multiprocessing/spawn.py", line 129, in _main
    return self._bootstrap(parent_sentinel)
  File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/multiprocessing/process.py", line 315, in _bootstrap
    self.run()
  File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/subprocess.py", line 76, in subprocess_started
    target(sockets=sockets)
  File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/server.py", line 60, in run
    return asyncio.run(self.serve(sockets=sockets))
  File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/lifespan/on.py", line 84, in main
    await app(scope, self.receive, self.send)
  File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/blacksheep/server/application.py", line 762, in __call__
    traceback.print_stack()
{'type': 'lifespan', 'asgi': {'version': '3.0', 'spec_version': '2.0'}}
<bound method LifespanOn.receive of <uvicorn.lifespan.on.LifespanOn object at 0x10c19e110>>
<bound method LifespanOn.send of <uvicorn.lifespan.on.LifespanOn object at 0x10c19e110>>
services {<class 'configuration.common.Configuration'>: <rodi.InstanceProvider object at 0x10e33df90>, 'Configuration': <rodi.InstanceProvider object at 0x10e33df90>, <class 'app.routes.organization.Organizations'>: <rodi.FactoryTypeProvider object at 0x10e33e200>, 'Organizations': <rodi.FactoryTypeProvider object at 0x10e33e200>, 'configuration': <rodi.InstanceProvider object at 0x10e33df90>, 'organizations': <rodi.FactoryTypeProvider object at 0x10e33e200>}
binders [<blacksheep.server.bindings.RequestBinder object at 0x10e33f010>, <blacksheep.server.bindings.QueryBinder object at 0x10e33f0a0>, <blacksheep.server.bindings.QueryBinder object at 0x10e33efe0>]
INFO:     Application startup complete.
  File "<string>", line 1, in <module>
  File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/multiprocessing/spawn.py", line 116, in spawn_main
    exitcode = _main(fd, parent_sentinel)
  File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/multiprocessing/spawn.py", line 129, in _main
    return self._bootstrap(parent_sentinel)
  File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/multiprocessing/process.py", line 315, in _bootstrap
    self.run()
  File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/subprocess.py", line 76, in subprocess_started
    target(sockets=sockets)
  File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/server.py", line 60, in run
    return asyncio.run(self.serve(sockets=sockets))
  File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 372, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/blacksheep/server/application.py", line 762, in __call__
    traceback.print_stack()
{'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'server': ('127.0.0.1', 8000), 'client': ('127.0.0.1', 64658), 'scheme': 'http', 'root_path': '', 'headers': [(b'host', b'localhost:8000'), (b'user-agent', b'curl/7.79.1'), (b'accept', b'*/*')], 'method': 'GET', 'path': '/health', 'raw_path': b'/health', 'query_string': b''}
<bound method RequestResponseCycle.receive of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x10e3a1c60>>
<bound method RequestResponseCycle.send of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x10e3a1c60>>
INFO:     127.0.0.1:64658 - "GET /health HTTP/1.1" 200 OK
^CINFO:     Shutting down
INFO:     Waiting for application shutdown.
INFO:     Application shutdown complete.
INFO:     Finished server process [16289]
INFO:     Stopping reloader process [16277]

@virajkanwade
Copy link
Author

key areas of interest

starlette:

  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/lifespan/on.py", line 84, in main
    await app(scope, self.receive, self.send)
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/starlette/applications.py", line 133, in __call__
    await self.error_middleware(scope, receive, send)

{'type': 'lifespan', 'asgi': {'version': '3.0', 'spec_version': '2.0'}, 'app': <starlette.applications.Starlette object at 0x105d73d30>}
<bound method LifespanOn.receive of <uvicorn.lifespan.on.LifespanOn object at 0x106fb8f70>>
<bound method LifespanOn.send of <uvicorn.lifespan.on.LifespanOn object at 0x106fb8f70>>

blacksheep:

 File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/lifespan/on.py", line 84, in main
    await app(scope, self.receive, self.send)
  File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/blacksheep/server/application.py", line 762, in __call__
    traceback.print_stack()
{'type': 'lifespan', 'asgi': {'version': '3.0', 'spec_version': '2.0'}}
<bound method LifespanOn.receive of <uvicorn.lifespan.on.LifespanOn object at 0x10c19e110>>
<bound method LifespanOn.send of <uvicorn.lifespan.on.LifespanOn object at 0x10c19e110>>

As you can see, blacksheep too gets the same scope, receive and send as starlette in application.call.
So I guess we just need to identify asgi3 middleware and somehow pass through the receive and send to it?

@virajkanwade
Copy link
Author

starlette:

  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 366, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/starlette/applications.py", line 133, in __call__
    await self.error_middleware(scope, receive, send)

{'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'server': ('127.0.0.1', 8000), 'client': ('127.0.0.1', 63904), 'scheme': 'http', 'method': 'GET', 'root_path': '', 'path': '/', 'raw_path': b'/', 'query_string': b'', 'headers': [(b'host', b'127.0.0.1:8000'), (b'user-agent', b'curl/7.79.1'), (b'accept', b'*/*')], 'app': <starlette.applications.Starlette object at 0x105d73d30>}
<bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x107029060>>
<function ServerErrorMiddleware.__call__.<locals>._send at 0x107050940>

blacksheep:

  File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 372, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/blacksheep/server/application.py", line 762, in __call__
    traceback.print_stack()
{'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'server': ('127.0.0.1', 8000), 'client': ('127.0.0.1', 64658), 'scheme': 'http', 'root_path': '', 'headers': [(b'host', b'localhost:8000'), (b'user-agent', b'curl/7.79.1'), (b'accept', b'*/*')], 'method': 'GET', 'path': '/health', 'raw_path': b'/health', 'query_string': b''}
<bound method RequestResponseCycle.receive of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x10e3a1c60>>
<bound method RequestResponseCycle.send of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x10e3a1c60>>

Here too scope is similar in both.
receive is same.
Starlette seems to be overriding send.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants