mirror of
https://github.com/simonw/datasette.git
synced 2026-06-13 04:27:00 +02:00
Merge 300fe125c5 into fa86ac7b11
This commit is contained in:
commit
1e2c99a04f
2 changed files with 87 additions and 0 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import asyncio
|
||||
import socket
|
||||
import uvicorn
|
||||
import click
|
||||
from click import formatting
|
||||
|
|
@ -706,6 +707,18 @@ def serve(
|
|||
return
|
||||
|
||||
# Start the server
|
||||
# If port is 0 and we need to print/open a URL before the server starts
|
||||
# (because of --root or --open), pre-bind a TCP socket so we know the
|
||||
# OS-assigned port. See https://github.com/simonw/datasette/issues/873
|
||||
pre_bound_socket = None
|
||||
if port == 0 and not uds and (root or open_browser):
|
||||
family = socket.AF_INET6 if host and ":" in host else socket.AF_INET
|
||||
pre_bound_socket = socket.socket(family=family, type=socket.SOCK_STREAM)
|
||||
pre_bound_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
pre_bound_socket.bind((host, 0))
|
||||
pre_bound_socket.set_inheritable(True)
|
||||
port = pre_bound_socket.getsockname()[1]
|
||||
|
||||
url = None
|
||||
if root:
|
||||
ds.root_enabled = True
|
||||
|
|
@ -719,6 +732,19 @@ def serve(
|
|||
path = run_sync(lambda: initial_path_for_datasette(ds))
|
||||
url = f"http://{host}:{port}{path}"
|
||||
webbrowser.open(url)
|
||||
if pre_bound_socket is not None:
|
||||
# Hand the pre-bound socket to uvicorn via Server.run(sockets=[...])
|
||||
# since uvicorn.run()'s `fd=` parameter assumes AF_UNIX.
|
||||
config_kwargs = dict(
|
||||
host=host, port=port, log_level="info", lifespan="on", workers=1
|
||||
)
|
||||
if ssl_keyfile:
|
||||
config_kwargs["ssl_keyfile"] = ssl_keyfile
|
||||
if ssl_certfile:
|
||||
config_kwargs["ssl_certfile"] = ssl_certfile
|
||||
config = uvicorn.Config(ds.app(), **config_kwargs)
|
||||
uvicorn.Server(config).run(sockets=[pre_bound_socket])
|
||||
return
|
||||
uvicorn_kwargs = dict(
|
||||
host=host, port=port, log_level="info", lifespan="on", workers=1
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
import httpx
|
||||
import pytest
|
||||
import re
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
|
||||
@pytest.mark.serial
|
||||
|
|
@ -27,3 +32,59 @@ def test_serve_unix_domain_socket(ds_unix_domain_socket_server):
|
|||
"path": "/_memory",
|
||||
"tables": [],
|
||||
}.items() <= response.json().items()
|
||||
|
||||
|
||||
@pytest.mark.serial
|
||||
def test_serve_root_url_uses_actual_port_when_port_is_zero():
|
||||
# Regression test for https://github.com/simonw/datasette/issues/873
|
||||
# `datasette -p 0 --root` printed http://127.0.0.1:0/... instead of
|
||||
# the OS-assigned port.
|
||||
proc = subprocess.Popen(
|
||||
[sys.executable, "-m", "datasette", "--memory", "-p", "0", "--root"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=tempfile.gettempdir(),
|
||||
text=True,
|
||||
)
|
||||
try:
|
||||
# Read lines until we see the auth-token URL or time out
|
||||
url_line = None
|
||||
deadline = time.time() + 10.0
|
||||
while time.time() < deadline:
|
||||
line = proc.stdout.readline()
|
||||
if not line:
|
||||
if proc.poll() is not None:
|
||||
break
|
||||
continue
|
||||
if "/-/auth-token?token=" in line:
|
||||
url_line = line.strip()
|
||||
break
|
||||
assert url_line, "Did not see auth-token URL in datasette output"
|
||||
match = re.match(r"http://127\.0\.0\.1:(\d+)/-/auth-token\?token=", url_line)
|
||||
assert match, f"Unexpected auth-token URL format: {url_line!r}"
|
||||
printed_port = int(match.group(1))
|
||||
assert printed_port != 0, (
|
||||
"datasette -p 0 --root should print the OS-assigned port, "
|
||||
"not the placeholder 0"
|
||||
)
|
||||
# Confirm a server is actually listening on that printed port
|
||||
deadline2 = time.time() + 5.0
|
||||
last_err = None
|
||||
while time.time() < deadline2:
|
||||
try:
|
||||
response = httpx.get(f"http://127.0.0.1:{printed_port}/_memory.json")
|
||||
assert response.status_code == 200
|
||||
break
|
||||
except httpx.ConnectError as exc:
|
||||
last_err = exc
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
raise AssertionError(
|
||||
f"Could not connect to printed port {printed_port}: {last_err}"
|
||||
)
|
||||
finally:
|
||||
proc.terminate()
|
||||
try:
|
||||
proc.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue