add command-line options for Uvicorn server configuration
This commit is contained in:
@@ -41,10 +41,21 @@ siteharbor
|
||||
|
||||
Open `http://127.0.0.1:8787`.
|
||||
|
||||
The `siteharbor` command runs the FastAPI application with Uvicorn. Override the bind port and
|
||||
start multiple Uvicorn worker processes when needed:
|
||||
|
||||
```powershell
|
||||
siteharbor --port 9000 --workers 2
|
||||
```
|
||||
|
||||
`--port` accepts ports from 1 through 65535 and overrides `SITEHARBOR_PORT`. `--workers` defaults
|
||||
to 1 and controls Uvicorn processes, not the capture-worker count configured by
|
||||
`SITEHARBOR_WORKER_CONCURRENCY`.
|
||||
|
||||
To use Uvicorn directly:
|
||||
|
||||
```powershell
|
||||
python -m uvicorn app.main:app --host 127.0.0.1 --port 8787
|
||||
python -m uvicorn app.main:app --host 127.0.0.1 --port 8787 --workers 2
|
||||
```
|
||||
|
||||
The default bind address is loopback. Do not expose SiteHarbor directly to the public Internet without authentication, a network egress policy, rate limits, and operational controls.
|
||||
@@ -56,9 +67,9 @@ All configuration uses environment variables prefixed with `SITEHARBOR_`.
|
||||
| Variable | Default | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `SITEHARBOR_HOST` | `127.0.0.1` | Bind address used by `siteharbor`. |
|
||||
| `SITEHARBOR_PORT` | `8787` | Bind port used by `siteharbor`. |
|
||||
| `SITEHARBOR_PORT` | `8787` | Default bind port used by `siteharbor`; overridden by `--port`. |
|
||||
| `SITEHARBOR_DATA_DIR` | `./data` | SQLite database, work directories, artifacts, and reports. |
|
||||
| `SITEHARBOR_WORKER_CONCURRENCY` | `1` | Capture workers per application process. |
|
||||
| `SITEHARBOR_WORKER_CONCURRENCY` | `1` | Capture workers per Uvicorn process; separate from `--workers`. |
|
||||
| `SITEHARBOR_FETCH_CONCURRENCY` | `6` | Maximum parallel fetches a visitor may select for one capture. |
|
||||
| `SITEHARBOR_LEASE_SECONDS` | `120` | SQLite worker lease duration; values below 15 seconds are rejected. |
|
||||
| `SITEHARBOR_ALLOW_PRIVATE_NETWORKS` | `false` | Development-only override that permits loopback and private hosts. |
|
||||
|
||||
+46
-2
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
@@ -387,8 +388,51 @@ def _safe_data_file(path_value: str, root: Path) -> Path | None:
|
||||
return None
|
||||
|
||||
|
||||
def run() -> None:
|
||||
uvicorn.run("app.main:app", host=settings.host, port=settings.port, reload=False)
|
||||
def _positive_integer(value: str) -> int:
|
||||
try:
|
||||
number = int(value)
|
||||
except ValueError as error:
|
||||
raise argparse.ArgumentTypeError("must be a whole number") from error
|
||||
if number < 1:
|
||||
raise argparse.ArgumentTypeError("must be at least 1")
|
||||
return number
|
||||
|
||||
|
||||
def _port_number(value: str) -> int:
|
||||
port = _positive_integer(value)
|
||||
if port > 65_535:
|
||||
raise argparse.ArgumentTypeError("must be between 1 and 65535")
|
||||
return port
|
||||
|
||||
|
||||
def _parse_server_arguments(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Run the SiteHarbor FastAPI server with Uvicorn.")
|
||||
parser.add_argument(
|
||||
"--port",
|
||||
type=_port_number,
|
||||
default=settings.port,
|
||||
metavar="PORT",
|
||||
help=f"bind port (default: SITEHARBOR_PORT or {settings.port})",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--workers",
|
||||
type=_positive_integer,
|
||||
default=1,
|
||||
metavar="WORKERS",
|
||||
help="number of Uvicorn worker processes (default: 1)",
|
||||
)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def run(argv: list[str] | None = None) -> None:
|
||||
arguments = _parse_server_arguments(argv)
|
||||
uvicorn.run(
|
||||
"app.main:app",
|
||||
host=settings.host,
|
||||
port=arguments.port,
|
||||
workers=arguments.workers,
|
||||
reload=False,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -65,3 +65,26 @@ def test_capture_routes_are_scoped_to_the_browser_session(tmp_path, monkeypatch)
|
||||
stats = client.get("/api/stats")
|
||||
assert stats.status_code == 200
|
||||
assert stats.json()["active_crawls"] == 1
|
||||
|
||||
|
||||
def test_run_forwards_server_options_to_uvicorn(monkeypatch) -> None:
|
||||
calls: list[tuple[tuple[object, ...], dict[str, object]]] = []
|
||||
|
||||
def fake_run(*args: object, **kwargs: object) -> None:
|
||||
calls.append((args, kwargs))
|
||||
|
||||
monkeypatch.setattr(main.uvicorn, "run", fake_run)
|
||||
|
||||
main.run(["--port", "9123", "--workers", "2"])
|
||||
|
||||
assert calls == [
|
||||
(
|
||||
("app.main:app",),
|
||||
{
|
||||
"host": settings.host,
|
||||
"port": 9123,
|
||||
"workers": 2,
|
||||
"reload": False,
|
||||
},
|
||||
)
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user