Skip to content

ASGI HTTP support#3571

Draft
takluyver wants to merge 14 commits intotornadoweb:masterfrom
takluyver:asgi
Draft

ASGI HTTP support#3571
takluyver wants to merge 14 commits intotornadoweb:masterfrom
takluyver:asgi

Conversation

@takluyver
Copy link
Copy Markdown
Contributor

Basic support for wrapping a tornado web Application class and serving HTTP requests through an ASGI server.

This obviously needs tests, but I've been able to start a server and serve a basic request locally:

# asgi_eg.py
from tornado.web import Application, RequestHandler
from tornado.asgi import ASGIAdapter

class MainHandler(RequestHandler):
    def get(self):
        self.write("Hello, world")

asgi_app = ASGIAdapter(
    Application([
        (r"/", MainHandler),
    ])
)
daphne asgi_eg:asgi_app

@takluyver
Copy link
Copy Markdown
Contributor Author

@bdarnell when you've got a moment, it would be great if you could have a quick look to see if I'm going in the direction you'd expect, both in the implementation and in the tests. It's relatively easy to make bigger changes while the added code is quite small.

There are 2 main new classes:

  • ASGIAdapter wraps a tornado Application and provides the ASGI entry point
  • ASGIHTTPConnection provides the API for a RequestHandler to send its response

Things I'm aware of that the tests should cover:

  • Testing with at least 1 real ASGI server, i.e. listening on a socket
  • HTTP/2
  • Behaviour when data is streamed, both for the request and the response
  • Either side closing the connection early
  • Non-ASCII characters in various places

(I'm sure that's not exhaustive, just writing down what I can think of now)

Copy link
Copy Markdown
Member

@bdarnell bdarnell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good; I apologize for the slow response.

One thing to watch out for in testing error cases is that moving things into bg_task doesn't cause exceptions to be raised in a different context than they should be.

@bdarnell bdarnell added the asgi label Mar 17, 2026
@takluyver takluyver force-pushed the asgi branch 2 times, most recently from 888a7bf to 4231e04 Compare March 25, 2026 21:29
@takluyver
Copy link
Copy Markdown
Contributor Author

Thanks, and no worries about the wait, I know how this can feel from the maintainer side.

Good point about error handling. I think this is primarily up to application code. If a RequestHandler calls e.g. await self.flush(), any errors from sending will be raised. If it calls write/flush/finish without await-ing, I don't think we can give it the error. This reminds me, though: is it OK to make HTTPConnection.finish() return a future instead of None, so errors from async finish-ing can bubble up?

Commit 311e807 adds a draft implementation of the lifespan protocol, which allows for async startup & shutdown of the application. I'm experimenting with an idea to do this based around an async generator function:

@ASGIAdapter
def asgi_main():
    # async startup, e.g.
    db_conn = await connect_to_database()

    yield Application(...)

    # async shutdown, e.g.
    await db_conn.aclose()

I like this because you don't create a partly initialised Application object, and you don't need to define another (sub)class for the ASGI wrapper. It's roughly inspired by writing pytest fixtures, or the stdlib @contextmanager decorator. But it's a bit different from the general style of tornado APIs, and I'm not sure about it yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants