Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ $ uvicorn --help
Usage: uvicorn [OPTIONS] APP

Options:
--host TEXT Bind socket to this host. [default:
127.0.0.1]
--host TEXT Bind socket to this host.
--port INTEGER Bind socket to this port. If 0, an available
port will be picked. [default: 8000]
--uds TEXT Bind to a UNIX domain socket.
Expand Down
3 changes: 1 addition & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ $ uvicorn --help
Usage: uvicorn [OPTIONS] APP

Options:
--host TEXT Bind socket to this host. [default:
127.0.0.1]
--host TEXT Bind socket to this host.
--port INTEGER Bind socket to this port. If 0, an available
port will be picked. [default: 8000]
--uds TEXT Bind to a UNIX domain socket.
Expand Down
3 changes: 1 addition & 2 deletions docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ For example, in case you want to run the app on port `5000`, just set the enviro

## Socket Binding

* `--host <str>` - Bind socket to this host. Use `--host 0.0.0.0` to make the application available on your local network. IPv6 addresses are supported, for example: `--host '::'`. **Default:** *'127.0.0.1'*.
* `--port <int>` - Bind to a socket with this port. **Default:** *8000*.
* `--host <str>` - Bind socket to this host. May be used multiple times. If unused, then by default 127.0.0.1. IPv6 addresses are supported, for example: `--host '::'`, when using ipv6 only, if ipv4 is available it will work at the same time.
* `--uds <path>` - Bind to a UNIX domain socket, for example `--uds /tmp/uvicorn.sock`. Useful if you want to run Uvicorn behind a reverse proxy.
* `--fd <int>` - Bind to socket from this file descriptor. Useful if you want to run Uvicorn within a process manager.

Expand Down
31 changes: 17 additions & 14 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,10 @@ def test_concrete_http_class() -> None:
def test_socket_bind() -> None:
config = Config(app=asgi_app)
config.load()
sock = config.bind_socket()
assert isinstance(sock, socket.socket)
sock.close()
sockets = config.bind_socket()
for sock in sockets:
assert isinstance(sock, socket.socket)
sock.close()


def test_ssl_config(
Expand Down Expand Up @@ -493,11 +494,12 @@ def test_bind_unix_socket_works_with_reload_or_workers(
): # pragma: py-win32
config = Config(app=asgi_app, uds=short_socket_name, reload=reload, workers=workers)
config.load()
sock = config.bind_socket()
assert isinstance(sock, socket.socket)
assert sock.family == socket.AF_UNIX
assert sock.getsockname() == short_socket_name
sock.close()
sockets = config.bind_socket()
for sock in sockets:
assert isinstance(sock, socket.socket)
assert sock.family == socket.AF_UNIX
assert sock.getsockname() == short_socket_name
sock.close()


@pytest.mark.parametrize(
Expand All @@ -514,12 +516,13 @@ def test_bind_fd_works_with_reload_or_workers(reload: bool, workers: int): # pr
fd = fdsock.fileno()
config = Config(app=asgi_app, fd=fd, reload=reload, workers=workers)
config.load()
sock = config.bind_socket()
assert isinstance(sock, socket.socket)
assert sock.family == socket.AF_UNIX
assert sock.getsockname() == ""
sock.close()
fdsock.close()
sockets = config.bind_socket()
for sock in sockets:
assert isinstance(sock, socket.socket)
assert sock.family == socket.AF_UNIX
assert sock.getsockname() == ""
sock.close()
fdsock.close()


@pytest.mark.parametrize(
Expand Down
59 changes: 34 additions & 25 deletions uvicorn/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class Config:
def __init__(
self,
app: ASGIApplication | Callable[..., Any] | str,
host: str = "127.0.0.1",
host: list[str] | str = "127.0.0.1",
port: int = 8000,
uds: str | None = None,
fd: int | None = None,
Expand Down Expand Up @@ -225,6 +225,8 @@ def __init__(
h11_max_incomplete_event_size: int | None = None,
):
self.app = app
if host and isinstance(host, str):
host = [host]
self.host = host
self.port = port
self.uds = uds
Expand Down Expand Up @@ -476,7 +478,8 @@ def setup_event_loop(self) -> None:
if loop_setup is not None:
loop_setup(use_subprocess=self.use_subprocess)

def bind_socket(self) -> socket.socket:
def bind_socket(self) -> list[socket.socket]:
sockets: list[socket.socket] = []
logger_args: list[str | int]
if self.uds: # pragma: py-win32
path = self.uds
Expand All @@ -489,40 +492,46 @@ def bind_socket(self) -> socket.socket:
logger.error(exc)
sys.exit(1)

sockets.append(sock)
message = "Uvicorn running on unix socket %s (Press CTRL+C to quit)"
sock_name_format = "%s"
color_message = "Uvicorn running on " + click.style(sock_name_format, bold=True) + " (Press CTRL+C to quit)"
logger_args = [self.uds]
logger.info(message, *logger_args, extra={"color_message": color_message})
elif self.fd: # pragma: py-win32
sock = socket.fromfd(self.fd, socket.AF_UNIX, socket.SOCK_STREAM)
sockets.append(sock)
message = "Uvicorn running on socket %s (Press CTRL+C to quit)"
fd_name_format = "%s"
color_message = "Uvicorn running on " + click.style(fd_name_format, bold=True) + " (Press CTRL+C to quit)"
logger_args = [sock.getsockname()]
logger.info(message, *logger_args, extra={"color_message": color_message})
else:
family = socket.AF_INET
addr_format = "%s://%s:%d"

if self.host and ":" in self.host: # pragma: full coverage
# It's an IPv6 address.
family = socket.AF_INET6
addr_format = "%s://[%s]:%d"

sock = socket.socket(family=family)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.bind((self.host, self.port))
except OSError as exc: # pragma: full coverage
logger.error(exc)
sys.exit(1)

message = f"Uvicorn running on {addr_format} (Press CTRL+C to quit)"
color_message = "Uvicorn running on " + click.style(addr_format, bold=True) + " (Press CTRL+C to quit)"
protocol_name = "https" if self.is_ssl else "http"
logger_args = [protocol_name, self.host, sock.getsockname()[1]]
logger.info(message, *logger_args, extra={"color_message": color_message})
sock.set_inheritable(True)
return sock
for host in self.host:
family = socket.AF_INET
addr_format = "%s://%s:%d"

if ":" in host: # pragma: full coverage
# It's an IPv6 address.
family = socket.AF_INET6
addr_format = "%s://[%s]:%d"

sock = socket.socket(family=family)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.bind((host, self.port))
except OSError as exc: # pragma: full coverage
logger.error(exc)
sys.exit(1)
sockets.append(sock)
message = f"Uvicorn running on {addr_format} (Press CTRL+C to quit)"
color_message = "Uvicorn running on " + click.style(addr_format, bold=True) + " (Press CTRL+C to quit)"
protocol_name = "https" if self.is_ssl else "http"
logger_args = [protocol_name, host, sock.getsockname()[1]]
logger.info(message, *logger_args, extra={"color_message": color_message})
for sock in sockets:
sock.set_inheritable(True)
return sockets

@property
def should_reload(self) -> bool:
Expand Down
19 changes: 10 additions & 9 deletions uvicorn/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> No
@click.argument("app", envvar="UVICORN_APP")
@click.option(
"--host",
type=str,
default="127.0.0.1",
multiple=True,
help="Bind socket to this host.",
show_default=True,
default=[
"127.0.0.1",
],
)
@click.option(
"--port",
Expand Down Expand Up @@ -362,7 +363,7 @@ def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> No
)
def main(
app: str,
host: str,
host: list[str],
port: int,
uds: str,
fd: int,
Expand Down Expand Up @@ -463,7 +464,7 @@ def main(
def run(
app: ASGIApplication | Callable[..., Any] | str,
*,
host: str = "127.0.0.1",
host: list[str] | str = "127.0.0.1",
port: int = 8000,
uds: str | None = None,
fd: int | None = None,
Expand Down Expand Up @@ -570,11 +571,11 @@ def run(

try:
if config.should_reload:
sock = config.bind_socket()
ChangeReload(config, target=server.run, sockets=[sock]).run()
sockets = config.bind_socket()
ChangeReload(config, target=server.run, sockets=sockets).run()
elif config.workers > 1:
sock = config.bind_socket()
Multiprocess(config, target=server.run, sockets=[sock]).run()
sockets = config.bind_socket()
Multiprocess(config, target=server.run, sockets=sockets).run()
else:
server.run()
except KeyboardInterrupt:
Expand Down
40 changes: 20 additions & 20 deletions uvicorn/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,26 +198,26 @@ def _log_started_message(self, listeners: Sequence[socket.SocketType]) -> None:
logger.info("Uvicorn running on unix socket %s (Press CTRL+C to quit)", config.uds)

else:
addr_format = "%s://%s:%d"
host = "0.0.0.0" if config.host is None else config.host
if ":" in host:
# It's an IPv6 address.
addr_format = "%s://[%s]:%d"

port = config.port
if port == 0:
port = listeners[0].getsockname()[1]

protocol_name = "https" if config.ssl else "http"
message = f"Uvicorn running on {addr_format} (Press CTRL+C to quit)"
color_message = "Uvicorn running on " + click.style(addr_format, bold=True) + " (Press CTRL+C to quit)"
logger.info(
message,
protocol_name,
host,
port,
extra={"color_message": color_message},
)
for host in config.host:
addr_format = "%s://%s:%d"
if ":" in host:
# It's an IPv6 address.
addr_format = "%s://[%s]:%d"

port = config.port
if port == 0:
port = listeners[0].getsockname()[1]

protocol_name = "https" if config.ssl else "http"
message = f"Uvicorn running on {addr_format} (Press CTRL+C to quit)"
color_message = "Uvicorn running on " + click.style(addr_format, bold=True) + " (Press CTRL+C to quit)"
logger.info(
message,
protocol_name,
host,
port,
extra={"color_message": color_message},
)

async def main_loop(self) -> None:
counter = 0
Expand Down