uvicorn icon indicating copy to clipboard operation
uvicorn copied to clipboard

Programatic access to the port Uvicorn is running on if started with port 0

Open simonw opened this issue 5 years ago • 3 comments

Checklist

  • [x] There are no similar issues or pull requests for this yet.
  • [x] I discussed this idea on the community chat and feedback is positive.

Is your feature related to a problem? Please describe.

If you start Uvicorn on port 0, it will be assigned a port at random. https://github.com/encode/uvicorn/commit/a75fe1381f6b1f78901691c71894f3cf487b5d30 is a change related to this.

I'd like a way to programatically access that port after calling uvicorn.run() so I can display it back to the user - for this issue: https://github.com/simonw/datasette/issues/873

Describe the solution you would like.

I'm not sure what would work best here. I don't want to have to wait for an HTTP request to arrive (because I want to show a message on startup), but calling uvicorn.run() as I do here is a blocking call.

I really want to be able to say "start the server running and then call this callback function with enough information that it can tell what port the server has been assigned". Is there another mechanism I could use for that?

simonw avatar Aug 20 '20 17:08 simonw

Not really.

After a year, do still have interest on this or did you have ideas for it? @simonw

Kludex avatar Sep 18 '21 08:09 Kludex

Hi, i saw this issue come up on the fastapi discord.

Make a kwarg that takes an open socket. Use this for listening and applications tht make the socket can know the port.

This has other nice properties like being able to define the socket as SO_REUSEPORT (which I'd expect a Python server always wants).

Is that a workable plan? It's a common pattern that I've used and seen in the wild in C++, Go, and Rust.

ehiggs avatar Dec 07 '21 19:12 ehiggs

It's a common pattern that I've used and seen in the wild in C++, Go, and Rust.

@ehiggs do you have references?

Kludex avatar Dec 07 '21 19:12 Kludex

This should be enough:

import asyncio

import uvicorn


async def app(scope, receive, send):
    ...


async def main():
    config = uvicorn.Config("main:app", port=0, log_level="info")
    server = uvicorn.Server(config)
    asyncio.create_task(server.serve())

    while not server.started:
        await asyncio.sleep(0.1)

    for server in server.servers:
        for socket in server.sockets:
            print(socket.getsockname())


if __name__ == "__main__":
    asyncio.run(main())

If it's not, please let me know, and I'll reopen it.

Kludex avatar Oct 22 '22 08:10 Kludex