initial commit
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ASGI Mounting - pyserve</title>
|
||||
<link rel="stylesheet" href="../style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="header">
|
||||
<h1>pyserve</h1>
|
||||
<div class="tagline">python application orchestrator</div>
|
||||
</div>
|
||||
|
||||
<div class="breadcrumb">
|
||||
<a href="../index.html">pyserve</a> » <a href="index.html">Guides</a> » ASGI Mounting
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<h2>ASGI Application Mounting (In-Process)</h2>
|
||||
|
||||
<p>The <code>asgi</code> extension mounts ASGI and WSGI applications directly in the pyserve process.
|
||||
This is simpler and has lower latency, but all apps share the same process.</p>
|
||||
|
||||
<div class="note">
|
||||
<strong>For production use cases requiring isolation, consider
|
||||
<a href="process-orchestration.html">Process Orchestration</a></strong> which runs each app
|
||||
in a separate subprocess with health monitoring and auto-restart.
|
||||
</div>
|
||||
|
||||
<h3>Overview</h3>
|
||||
<p>The ASGI mounting system provides:</p>
|
||||
<ul class="indent">
|
||||
<li><strong>Multi-framework support</strong> — Mount FastAPI, Flask, Django, Starlette, or custom ASGI apps</li>
|
||||
<li><strong>Path-based routing</strong> — Each app handles requests at its mounted path</li>
|
||||
<li><strong>WSGI compatibility</strong> — Automatic WSGI-to-ASGI conversion for Flask/Django</li>
|
||||
<li><strong>Factory pattern support</strong> — Create apps dynamically with arguments</li>
|
||||
<li><strong>Path stripping</strong> — Optionally strip mount path from requests</li>
|
||||
</ul>
|
||||
|
||||
<h3>Configuration</h3>
|
||||
<p>ASGI applications are mounted via the <code>asgi</code> extension:</p>
|
||||
|
||||
<pre><span class="directive">extensions:</span>
|
||||
- <span class="directive">type:</span> <span class="value">asgi</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">mounts:</span>
|
||||
- <span class="directive">path:</span> <span class="value">"/api"</span>
|
||||
<span class="directive">app_path:</span> <span class="value">"myapp.api:app"</span>
|
||||
<span class="directive">app_type:</span> <span class="value">asgi</span>
|
||||
<span class="directive">name:</span> <span class="value">"api-app"</span>
|
||||
<span class="directive">strip_path:</span> <span class="value">true</span></pre>
|
||||
|
||||
<h3>Mount Configuration Options</h3>
|
||||
<dl>
|
||||
<dt>path</dt>
|
||||
<dd>URL path where the application will be mounted. Example: <code>/api</code></dd>
|
||||
|
||||
<dt>app_path</dt>
|
||||
<dd>Python import path to the application. Format: <code>module.submodule:attribute</code></dd>
|
||||
|
||||
<dt>app_type</dt>
|
||||
<dd>Application type: <code>asgi</code> or <code>wsgi</code>. Default: <code>asgi</code></dd>
|
||||
|
||||
<dt>module_path</dt>
|
||||
<dd>Optional path to add to <code>sys.path</code> for module resolution</dd>
|
||||
|
||||
<dt>factory</dt>
|
||||
<dd>If <code>true</code>, <code>app_path</code> points to a factory function. Default: <code>false</code></dd>
|
||||
|
||||
<dt>factory_args</dt>
|
||||
<dd>Dictionary of arguments to pass to the factory function</dd>
|
||||
|
||||
<dt>name</dt>
|
||||
<dd>Friendly name for logging. Default: uses <code>app_path</code></dd>
|
||||
|
||||
<dt>strip_path</dt>
|
||||
<dd>Remove mount path from request URL. Default: <code>true</code></dd>
|
||||
</dl>
|
||||
|
||||
<h3>Mounting FastAPI</h3>
|
||||
<p>FastAPI applications are native ASGI:</p>
|
||||
|
||||
<pre><span class="comment"># myapp/api.py</span>
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/users")
|
||||
async def get_users():
|
||||
return [{"id": 1, "name": "Alice"}]</pre>
|
||||
|
||||
<pre><span class="directive">extensions:</span>
|
||||
- <span class="directive">type:</span> <span class="value">asgi</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">mounts:</span>
|
||||
- <span class="directive">path:</span> <span class="value">"/api"</span>
|
||||
<span class="directive">app_path:</span> <span class="value">"myapp.api:app"</span>
|
||||
<span class="directive">app_type:</span> <span class="value">asgi</span>
|
||||
<span class="directive">name:</span> <span class="value">"fastapi-app"</span></pre>
|
||||
|
||||
<p>With this configuration:</p>
|
||||
<ul class="indent">
|
||||
<li><code>GET /api/users</code> → handled by FastAPI as <code>GET /users</code></li>
|
||||
<li>FastAPI docs available at <code>/api/docs</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Mounting Flask</h3>
|
||||
<p>Flask applications are WSGI and will be automatically wrapped:</p>
|
||||
|
||||
<pre><span class="comment"># myapp/flask_api.py</span>
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/hello")
|
||||
def hello():
|
||||
return {"message": "Hello from Flask!"}</pre>
|
||||
|
||||
<pre><span class="directive">extensions:</span>
|
||||
- <span class="directive">type:</span> <span class="value">asgi</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">mounts:</span>
|
||||
- <span class="directive">path:</span> <span class="value">"/flask"</span>
|
||||
<span class="directive">app_path:</span> <span class="value">"myapp.flask_api:app"</span>
|
||||
<span class="directive">app_type:</span> <span class="value">wsgi</span>
|
||||
<span class="directive">name:</span> <span class="value">"flask-app"</span></pre>
|
||||
|
||||
<div class="note">
|
||||
<strong>Note:</strong> WSGI wrapping requires either <code>a2wsgi</code> or <code>asgiref</code>
|
||||
to be installed. Install with: <code>pip install a2wsgi</code>
|
||||
</div>
|
||||
|
||||
<h3>Mounting Django</h3>
|
||||
<p>Django can be mounted using its ASGI application:</p>
|
||||
|
||||
<pre><span class="directive">extensions:</span>
|
||||
- <span class="directive">type:</span> <span class="value">asgi</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">mounts:</span>
|
||||
- <span class="directive">path:</span> <span class="value">"/django"</span>
|
||||
<span class="directive">django_settings:</span> <span class="value">"myproject.settings"</span>
|
||||
<span class="directive">module_path:</span> <span class="value">"/path/to/django/project"</span>
|
||||
<span class="directive">name:</span> <span class="value">"django-app"</span></pre>
|
||||
|
||||
<h3>Factory Pattern</h3>
|
||||
<p>Use factory functions to create apps with custom configuration:</p>
|
||||
|
||||
<pre><span class="comment"># myapp/api.py</span>
|
||||
from fastapi import FastAPI
|
||||
|
||||
def create_app(debug: bool = False, prefix: str = "/v1") -> FastAPI:
|
||||
app = FastAPI(debug=debug)
|
||||
|
||||
@app.get(f"{prefix}/status")
|
||||
async def status():
|
||||
return {"debug": debug}
|
||||
|
||||
return app</pre>
|
||||
|
||||
<pre><span class="directive">extensions:</span>
|
||||
- <span class="directive">type:</span> <span class="value">asgi</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">mounts:</span>
|
||||
- <span class="directive">path:</span> <span class="value">"/api"</span>
|
||||
<span class="directive">app_path:</span> <span class="value">"myapp.api:create_app"</span>
|
||||
<span class="directive">app_type:</span> <span class="value">asgi</span>
|
||||
<span class="directive">factory:</span> <span class="value">true</span>
|
||||
<span class="directive">factory_args:</span>
|
||||
<span class="directive">debug:</span> <span class="value">true</span>
|
||||
<span class="directive">prefix:</span> <span class="value">"/v2"</span></pre>
|
||||
|
||||
<h3>Path Stripping</h3>
|
||||
<p>By default, <code>strip_path: true</code> removes the mount prefix from requests:</p>
|
||||
|
||||
<table class="dirindex">
|
||||
<tr>
|
||||
<td>Request</td>
|
||||
<td><code>strip_path: true</code></td>
|
||||
<td><code>strip_path: false</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>GET /api/users</code></td>
|
||||
<td>App sees <code>/users</code></td>
|
||||
<td>App sees <code>/api/users</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>GET /api/</code></td>
|
||||
<td>App sees <code>/</code></td>
|
||||
<td>App sees <code>/api/</code></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Multiple Mounts</h3>
|
||||
<p>Mount multiple applications at different paths:</p>
|
||||
|
||||
<pre><span class="directive">extensions:</span>
|
||||
- <span class="directive">type:</span> <span class="value">asgi</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">mounts:</span>
|
||||
<span class="comment"># FastAPI for REST API</span>
|
||||
- <span class="directive">path:</span> <span class="value">"/api"</span>
|
||||
<span class="directive">app_path:</span> <span class="value">"apps.api:app"</span>
|
||||
<span class="directive">app_type:</span> <span class="value">asgi</span>
|
||||
|
||||
<span class="comment"># Flask admin panel</span>
|
||||
- <span class="directive">path:</span> <span class="value">"/admin"</span>
|
||||
<span class="directive">app_path:</span> <span class="value">"apps.admin:app"</span>
|
||||
<span class="directive">app_type:</span> <span class="value">wsgi</span>
|
||||
|
||||
<span class="comment"># Starlette websocket handler</span>
|
||||
- <span class="directive">path:</span> <span class="value">"/ws"</span>
|
||||
<span class="directive">app_path:</span> <span class="value">"apps.websocket:app"</span>
|
||||
<span class="directive">app_type:</span> <span class="value">asgi</span>
|
||||
|
||||
<span class="comment"># Standard routing for static files</span>
|
||||
- <span class="directive">type:</span> <span class="value">routing</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">regex_locations:</span>
|
||||
<span class="value">"__default__"</span>:
|
||||
<span class="directive">root:</span> <span class="value">"./static"</span></pre>
|
||||
|
||||
<h3>Mount Priority</h3>
|
||||
<p>Mounts are matched by path length (longest first). Given mounts at
|
||||
<code>/api</code> and <code>/api/v2</code>:</p>
|
||||
<ul class="indent">
|
||||
<li><code>/api/v2/users</code> → matches <code>/api/v2</code> mount</li>
|
||||
<li><code>/api/users</code> → matches <code>/api</code> mount</li>
|
||||
</ul>
|
||||
|
||||
<h3>Combining with Routing</h3>
|
||||
<p>ASGI mounts work alongside the routing extension. The <code>asgi</code> extension
|
||||
should be listed before <code>routing</code> to handle mounted paths first:</p>
|
||||
|
||||
<pre><span class="directive">extensions:</span>
|
||||
<span class="comment"># ASGI apps handle /api/* and /admin/*</span>
|
||||
- <span class="directive">type:</span> <span class="value">asgi</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">mounts:</span>
|
||||
- <span class="directive">path:</span> <span class="value">"/api"</span>
|
||||
<span class="directive">app_path:</span> <span class="value">"myapp:api"</span>
|
||||
<span class="directive">app_type:</span> <span class="value">asgi</span>
|
||||
|
||||
<span class="comment"># Routing handles everything else</span>
|
||||
- <span class="directive">type:</span> <span class="value">routing</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">regex_locations:</span>
|
||||
<span class="value">"=/health"</span>:
|
||||
<span class="directive">return:</span> <span class="value">"200 OK"</span>
|
||||
<span class="value">"__default__"</span>:
|
||||
<span class="directive">spa_fallback:</span> <span class="value">true</span>
|
||||
<span class="directive">root:</span> <span class="value">"./dist"</span></pre>
|
||||
|
||||
<h3>Python API</h3>
|
||||
<p>For programmatic mounting, see <a href="../reference/asgi-mount.html">ASGI Mount API Reference</a>.</p>
|
||||
|
||||
<div class="warning">
|
||||
<strong>Warning:</strong> Mounted applications share the same process.
|
||||
Ensure your applications are compatible and don't have conflicting global state.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<p>pyserve © 2024-2025 | MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,179 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Configuration - pyserve</title>
|
||||
<link rel="stylesheet" href="../style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="header">
|
||||
<h1>pyserve</h1>
|
||||
<div class="tagline">python application orchestrator</div>
|
||||
</div>
|
||||
|
||||
<div class="breadcrumb">
|
||||
<a href="../index.html">pyserve</a> » <a href="/guides/">Guides</a> » Configuration
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<h2>Configuration Reference</h2>
|
||||
|
||||
<p>pyserve uses YAML configuration files. By default, it looks for
|
||||
<code>config.yaml</code> in the current directory.</p>
|
||||
|
||||
<h3>http</h3>
|
||||
<p>HTTP-related paths configuration.</p>
|
||||
<dl>
|
||||
<dt>static_dir</dt>
|
||||
<dd>Path to static files directory. Default: <code>./static</code></dd>
|
||||
|
||||
<dt>templates_dir</dt>
|
||||
<dd>Path to templates directory. Default: <code>./templates</code></dd>
|
||||
</dl>
|
||||
|
||||
<h3>server</h3>
|
||||
<p>Core server settings.</p>
|
||||
<dl>
|
||||
<dt>host</dt>
|
||||
<dd>Bind address. Default: <code>0.0.0.0</code></dd>
|
||||
|
||||
<dt>port</dt>
|
||||
<dd>Listen port. Default: <code>8080</code></dd>
|
||||
|
||||
<dt>backlog</dt>
|
||||
<dd>Connection queue size. Default: <code>5</code></dd>
|
||||
|
||||
<dt>default_root</dt>
|
||||
<dd>Enable default root handler. Default: <code>false</code></dd>
|
||||
|
||||
<dt>proxy_timeout</dt>
|
||||
<dd>Default timeout for proxy requests in seconds. Default: <code>30.0</code></dd>
|
||||
|
||||
<dt>redirect_instructions</dt>
|
||||
<dd>Dictionary of redirect rules. Format: <code>"/from": "/to"</code></dd>
|
||||
</dl>
|
||||
|
||||
<h3>ssl</h3>
|
||||
<p>SSL/TLS configuration for HTTPS.</p>
|
||||
<dl>
|
||||
<dt>enabled</dt>
|
||||
<dd>Enable HTTPS. Default: <code>false</code></dd>
|
||||
|
||||
<dt>cert_file</dt>
|
||||
<dd>Path to SSL certificate file. Default: <code>./ssl/cert.pem</code></dd>
|
||||
|
||||
<dt>key_file</dt>
|
||||
<dd>Path to SSL private key file. Default: <code>./ssl/key.pem</code></dd>
|
||||
</dl>
|
||||
|
||||
<h3>logging</h3>
|
||||
<p>Logging configuration with structlog support.</p>
|
||||
<dl>
|
||||
<dt>level</dt>
|
||||
<dd>Log level: <code>DEBUG</code>, <code>INFO</code>, <code>WARNING</code>,
|
||||
<code>ERROR</code>. Default: <code>INFO</code></dd>
|
||||
|
||||
<dt>console_output</dt>
|
||||
<dd>Output to console. Default: <code>true</code></dd>
|
||||
|
||||
<dt>format</dt>
|
||||
<dd>Format configuration object (see below)</dd>
|
||||
|
||||
<dt>console</dt>
|
||||
<dd>Console handler configuration</dd>
|
||||
|
||||
<dt>files</dt>
|
||||
<dd>List of file handlers for logging to files</dd>
|
||||
</dl>
|
||||
|
||||
<h4>logging.format</h4>
|
||||
<dl>
|
||||
<dt>type</dt>
|
||||
<dd>Format type: <code>standard</code> or <code>json</code>. Default: <code>standard</code></dd>
|
||||
|
||||
<dt>use_colors</dt>
|
||||
<dd>Enable colored output in console. Default: <code>true</code></dd>
|
||||
|
||||
<dt>show_module</dt>
|
||||
<dd>Show module name in logs. Default: <code>true</code></dd>
|
||||
|
||||
<dt>timestamp_format</dt>
|
||||
<dd>Timestamp format string. Default: <code>%Y-%m-%d %H:%M:%S</code></dd>
|
||||
</dl>
|
||||
|
||||
<h4>logging.files[]</h4>
|
||||
<dl>
|
||||
<dt>path</dt>
|
||||
<dd>Path to log file</dd>
|
||||
|
||||
<dt>level</dt>
|
||||
<dd>Log level for this file handler</dd>
|
||||
|
||||
<dt>format</dt>
|
||||
<dd>Format configuration for this file</dd>
|
||||
|
||||
<dt>loggers</dt>
|
||||
<dd>List of logger names to include (empty = all loggers)</dd>
|
||||
|
||||
<dt>max_bytes</dt>
|
||||
<dd>Maximum file size before rotation. Default: <code>10485760</code> (10MB)</dd>
|
||||
|
||||
<dt>backup_count</dt>
|
||||
<dd>Number of backup files to keep. Default: <code>5</code></dd>
|
||||
</dl>
|
||||
|
||||
<h3>extensions</h3>
|
||||
<p>List of extension modules to load. See <a href="../reference/extensions.html">Extensions Reference</a>.</p>
|
||||
|
||||
<h3>Complete Example</h3>
|
||||
<pre><span class="directive">http:</span>
|
||||
<span class="directive">static_dir:</span> <span class="value">./static</span>
|
||||
<span class="directive">templates_dir:</span> <span class="value">./templates</span>
|
||||
|
||||
<span class="directive">server:</span>
|
||||
<span class="directive">host:</span> <span class="value">0.0.0.0</span>
|
||||
<span class="directive">port:</span> <span class="value">8080</span>
|
||||
<span class="directive">backlog:</span> <span class="value">5</span>
|
||||
<span class="directive">default_root:</span> <span class="value">false</span>
|
||||
<span class="directive">proxy_timeout:</span> <span class="value">30.0</span>
|
||||
|
||||
<span class="directive">ssl:</span>
|
||||
<span class="directive">enabled:</span> <span class="value">false</span>
|
||||
<span class="directive">cert_file:</span> <span class="value">./ssl/cert.pem</span>
|
||||
<span class="directive">key_file:</span> <span class="value">./ssl/key.pem</span>
|
||||
|
||||
<span class="directive">logging:</span>
|
||||
<span class="directive">level:</span> <span class="value">INFO</span>
|
||||
<span class="directive">console_output:</span> <span class="value">true</span>
|
||||
<span class="directive">format:</span>
|
||||
<span class="directive">type:</span> <span class="value">standard</span>
|
||||
<span class="directive">use_colors:</span> <span class="value">true</span>
|
||||
<span class="directive">timestamp_format:</span> <span class="value">"%Y-%m-%d %H:%M:%S"</span>
|
||||
<span class="directive">files:</span>
|
||||
- <span class="directive">path:</span> <span class="value">./logs/pyserve.log</span>
|
||||
<span class="directive">level:</span> <span class="value">DEBUG</span>
|
||||
<span class="directive">max_bytes:</span> <span class="value">10485760</span>
|
||||
<span class="directive">backup_count:</span> <span class="value">5</span>
|
||||
|
||||
<span class="directive">extensions:</span>
|
||||
- <span class="directive">type:</span> <span class="value">routing</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">regex_locations:</span>
|
||||
<span class="value">"__default__"</span>:
|
||||
<span class="directive">root:</span> <span class="value">"./static"</span>
|
||||
<span class="directive">index_file:</span> <span class="value">"index.html"</span></pre>
|
||||
|
||||
<div class="warning">
|
||||
<strong>Warning:</strong> When running in production, always use SSL
|
||||
and restrict the bind address appropriately.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<p>pyserve © 2024-2025 | MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Guides - pyserve</title>
|
||||
<link rel="stylesheet" href="../style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="header">
|
||||
<h1>pyserve</h1>
|
||||
<div class="tagline">python application orchestrator</div>
|
||||
</div>
|
||||
|
||||
<div class="breadcrumb">
|
||||
<a href="../index.html">pyserve</a> » Guides
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<h2>Guides</h2>
|
||||
|
||||
<p>In-depth guides for configuring and using pyserve.</p>
|
||||
|
||||
<table class="dirindex">
|
||||
<tr>
|
||||
<td class="icon"><span class="file">📄</span></td>
|
||||
<td><a href="process-orchestration.html">Process Orchestration</a></td>
|
||||
<td class="desc">Run apps in isolated processes with health monitoring</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="icon"><span class="file">📄</span></td>
|
||||
<td><a href="configuration.html">Configuration</a></td>
|
||||
<td class="desc">Complete configuration reference</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="icon"><span class="file">📄</span></td>
|
||||
<td><a href="routing.html">Routing</a></td>
|
||||
<td class="desc">URL routing and regex patterns</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="icon"><span class="file">📄</span></td>
|
||||
<td><a href="reverse-proxy.html">Reverse Proxy</a></td>
|
||||
<td class="desc">Proxying requests to backend services</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="icon"><span class="file">📄</span></td>
|
||||
<td><a href="asgi-mount.html">ASGI Mounting</a></td>
|
||||
<td class="desc">Mount Python web frameworks in-process</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<p>pyserve © 2024-2025 | MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,354 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Process Orchestration - pyserve</title>
|
||||
<link rel="stylesheet" href="../style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="header">
|
||||
<h1>pyserve</h1>
|
||||
<div class="tagline">async http server</div>
|
||||
</div>
|
||||
|
||||
<div class="breadcrumb">
|
||||
<a href="../index.html">pyserve</a> » <a href="/guides/">Guides</a> » Process Orchestration
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<h2>Process Orchestration</h2>
|
||||
|
||||
<p>Process Orchestration is pyserve's flagship feature for running multiple Python web
|
||||
applications with full process isolation. Each application runs in its own subprocess
|
||||
with independent lifecycle, health monitoring, and automatic restart on failure.</p>
|
||||
|
||||
<h3>Overview</h3>
|
||||
<p>Unlike <a href="asgi-mount.html">ASGI Mounting</a> which runs apps in-process,
|
||||
Process Orchestration provides:</p>
|
||||
<ul class="indent">
|
||||
<li><strong>Process Isolation</strong> — Each app runs in a separate Python process</li>
|
||||
<li><strong>Health Monitoring</strong> — Automatic health checks with configurable intervals</li>
|
||||
<li><strong>Auto-restart</strong> — Failed processes restart with exponential backoff</li>
|
||||
<li><strong>Multi-worker Support</strong> — Configure multiple uvicorn workers per app</li>
|
||||
<li><strong>Dynamic Port Allocation</strong> — Automatic port assignment (9000-9999)</li>
|
||||
<li><strong>WSGI Support</strong> — Flask/Django apps via automatic wrapping</li>
|
||||
<li><strong>Request Tracing</strong> — X-Request-ID propagation through proxied requests</li>
|
||||
</ul>
|
||||
|
||||
<h3>Architecture</h3>
|
||||
<pre>
|
||||
PyServe Gateway (:8000)
|
||||
│
|
||||
┌────────────────┼────────────────┐
|
||||
▼ ▼ ▼
|
||||
FastAPI Flask Starlette
|
||||
:9001 :9002 :9003
|
||||
/api/* /admin/* /ws/*
|
||||
</pre>
|
||||
<p>PyServe acts as a gateway, routing requests to the appropriate subprocess based on URL path.</p>
|
||||
|
||||
<h3>Basic Configuration</h3>
|
||||
<pre><span class="directive">server:</span>
|
||||
<span class="directive">host:</span> <span class="value">0.0.0.0</span>
|
||||
<span class="directive">port:</span> <span class="value">8000</span>
|
||||
|
||||
<span class="directive">extensions:</span>
|
||||
- <span class="directive">type:</span> <span class="value">process_orchestration</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">apps:</span>
|
||||
- <span class="directive">name:</span> <span class="value">api</span>
|
||||
<span class="directive">path:</span> <span class="value">/api</span>
|
||||
<span class="directive">app_path:</span> <span class="value">myapp.api:app</span>
|
||||
|
||||
- <span class="directive">name:</span> <span class="value">admin</span>
|
||||
<span class="directive">path:</span> <span class="value">/admin</span>
|
||||
<span class="directive">app_path:</span> <span class="value">myapp.admin:app</span></pre>
|
||||
|
||||
<h3>App Configuration Options</h3>
|
||||
<dl>
|
||||
<dt>name</dt>
|
||||
<dd>Unique identifier for the application (required)</dd>
|
||||
|
||||
<dt>path</dt>
|
||||
<dd>URL path prefix for routing requests (required)</dd>
|
||||
|
||||
<dt>app_path</dt>
|
||||
<dd>Python import path. Format: <code>module:attribute</code> (required)</dd>
|
||||
|
||||
<dt>app_type</dt>
|
||||
<dd>Application type: <code>asgi</code> or <code>wsgi</code>. Default: <code>asgi</code></dd>
|
||||
|
||||
<dt>workers</dt>
|
||||
<dd>Number of uvicorn workers. Default: <code>1</code></dd>
|
||||
|
||||
<dt>port</dt>
|
||||
<dd>Fixed port number. Default: auto-allocated from port_range</dd>
|
||||
|
||||
<dt>factory</dt>
|
||||
<dd>If <code>true</code>, app_path points to a factory function. Default: <code>false</code></dd>
|
||||
|
||||
<dt>env</dt>
|
||||
<dd>Environment variables to pass to the subprocess</dd>
|
||||
|
||||
<dt>module_path</dt>
|
||||
<dd>Path to add to <code>sys.path</code> for module resolution</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Health Check Options</h3>
|
||||
<dl>
|
||||
<dt>health_check_enabled</dt>
|
||||
<dd>Enable health monitoring. Default: <code>true</code></dd>
|
||||
|
||||
<dt>health_check_path</dt>
|
||||
<dd>Endpoint to check for health. Default: <code>/health</code></dd>
|
||||
|
||||
<dt>health_check_interval</dt>
|
||||
<dd>Interval between health checks in seconds. Default: <code>10.0</code></dd>
|
||||
|
||||
<dt>health_check_timeout</dt>
|
||||
<dd>Timeout for health check requests. Default: <code>5.0</code></dd>
|
||||
|
||||
<dt>health_check_retries</dt>
|
||||
<dd>Failed checks before restart. Default: <code>3</code></dd>
|
||||
</dl>
|
||||
|
||||
<h3>Restart Options</h3>
|
||||
<dl>
|
||||
<dt>max_restart_count</dt>
|
||||
<dd>Maximum restart attempts before giving up. Default: <code>5</code></dd>
|
||||
|
||||
<dt>restart_delay</dt>
|
||||
<dd>Initial delay between restarts in seconds. Default: <code>1.0</code></dd>
|
||||
|
||||
<dt>shutdown_timeout</dt>
|
||||
<dd>Timeout for graceful shutdown. Default: <code>30.0</code></dd>
|
||||
</dl>
|
||||
|
||||
<h3>Global Configuration</h3>
|
||||
<pre><span class="directive">extensions:</span>
|
||||
- <span class="directive">type:</span> <span class="value">process_orchestration</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">port_range:</span> <span class="value">[9000, 9999]</span>
|
||||
<span class="directive">health_check_enabled:</span> <span class="value">true</span>
|
||||
<span class="directive">proxy_timeout:</span> <span class="value">60.0</span>
|
||||
<span class="directive">logging:</span>
|
||||
<span class="directive">httpx_level:</span> <span class="value">warning</span>
|
||||
<span class="directive">proxy_logs:</span> <span class="value">true</span>
|
||||
<span class="directive">health_check_logs:</span> <span class="value">false</span>
|
||||
<span class="directive">apps:</span>
|
||||
<span class="comment"># ...</span></pre>
|
||||
|
||||
<dl>
|
||||
<dt>port_range</dt>
|
||||
<dd>Range for dynamic port allocation. Default: <code>[9000, 9999]</code></dd>
|
||||
|
||||
<dt>proxy_timeout</dt>
|
||||
<dd>Timeout for proxied requests in seconds. Default: <code>60.0</code></dd>
|
||||
|
||||
<dt>logging.httpx_level</dt>
|
||||
<dd>Log level for HTTP client (debug/info/warning/error). Default: <code>warning</code></dd>
|
||||
|
||||
<dt>logging.proxy_logs</dt>
|
||||
<dd>Log proxied requests with latency. Default: <code>true</code></dd>
|
||||
|
||||
<dt>logging.health_check_logs</dt>
|
||||
<dd>Log health check results. Default: <code>false</code></dd>
|
||||
</dl>
|
||||
|
||||
<h3>FastAPI Example</h3>
|
||||
<pre><span class="comment"># myapp/api.py</span>
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
return {"status": "ok"}
|
||||
|
||||
@app.get("/users")
|
||||
async def get_users():
|
||||
return [{"id": 1, "name": "Alice"}]</pre>
|
||||
|
||||
<pre><span class="directive">extensions:</span>
|
||||
- <span class="directive">type:</span> <span class="value">process_orchestration</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">apps:</span>
|
||||
- <span class="directive">name:</span> <span class="value">api</span>
|
||||
<span class="directive">path:</span> <span class="value">/api</span>
|
||||
<span class="directive">app_path:</span> <span class="value">myapp.api:app</span>
|
||||
<span class="directive">workers:</span> <span class="value">4</span>
|
||||
<span class="directive">health_check_path:</span> <span class="value">/health</span></pre>
|
||||
|
||||
<p>Requests to <code>/api/users</code> are proxied to the FastAPI process as <code>/users</code>.</p>
|
||||
|
||||
<h3>Flask Example (WSGI)</h3>
|
||||
<pre><span class="comment"># myapp/admin.py</span>
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/health")
|
||||
def health():
|
||||
return {"status": "ok"}
|
||||
|
||||
@app.route("/dashboard")
|
||||
def dashboard():
|
||||
return {"page": "dashboard"}</pre>
|
||||
|
||||
<pre><span class="directive">extensions:</span>
|
||||
- <span class="directive">type:</span> <span class="value">process_orchestration</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">apps:</span>
|
||||
- <span class="directive">name:</span> <span class="value">admin</span>
|
||||
<span class="directive">path:</span> <span class="value">/admin</span>
|
||||
<span class="directive">app_path:</span> <span class="value">myapp.admin:app</span>
|
||||
<span class="directive">app_type:</span> <span class="value">wsgi</span>
|
||||
<span class="directive">workers:</span> <span class="value">2</span></pre>
|
||||
|
||||
<div class="note">
|
||||
<strong>Note:</strong> WSGI support requires <code>a2wsgi</code> package:
|
||||
<code>pip install a2wsgi</code>
|
||||
</div>
|
||||
|
||||
<h3>Factory Pattern</h3>
|
||||
<pre><span class="comment"># myapp/api.py</span>
|
||||
from fastapi import FastAPI
|
||||
|
||||
def create_app(debug: bool = False) -> FastAPI:
|
||||
app = FastAPI(debug=debug)
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
return {"status": "ok", "debug": debug}
|
||||
|
||||
return app</pre>
|
||||
|
||||
<pre><span class="directive">apps:</span>
|
||||
- <span class="directive">name:</span> <span class="value">api</span>
|
||||
<span class="directive">path:</span> <span class="value">/api</span>
|
||||
<span class="directive">app_path:</span> <span class="value">myapp.api:create_app</span>
|
||||
<span class="directive">factory:</span> <span class="value">true</span></pre>
|
||||
|
||||
<h3>Environment Variables</h3>
|
||||
<p>Pass environment variables to subprocesses:</p>
|
||||
<pre><span class="directive">apps:</span>
|
||||
- <span class="directive">name:</span> <span class="value">api</span>
|
||||
<span class="directive">path:</span> <span class="value">/api</span>
|
||||
<span class="directive">app_path:</span> <span class="value">myapp.api:app</span>
|
||||
<span class="directive">env:</span>
|
||||
<span class="directive">DATABASE_URL:</span> <span class="value">"postgresql://localhost/mydb"</span>
|
||||
<span class="directive">REDIS_URL:</span> <span class="value">"redis://localhost:6379"</span>
|
||||
<span class="directive">DEBUG:</span> <span class="value">"false"</span></pre>
|
||||
|
||||
<h3>Multiple Applications</h3>
|
||||
<pre><span class="directive">extensions:</span>
|
||||
- <span class="directive">type:</span> <span class="value">process_orchestration</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">port_range:</span> <span class="value">[9000, 9999]</span>
|
||||
<span class="directive">apps:</span>
|
||||
<span class="comment"># FastAPI REST API</span>
|
||||
- <span class="directive">name:</span> <span class="value">api</span>
|
||||
<span class="directive">path:</span> <span class="value">/api</span>
|
||||
<span class="directive">app_path:</span> <span class="value">apps.api:app</span>
|
||||
<span class="directive">workers:</span> <span class="value">4</span>
|
||||
|
||||
<span class="comment"># Flask Admin Panel</span>
|
||||
- <span class="directive">name:</span> <span class="value">admin</span>
|
||||
<span class="directive">path:</span> <span class="value">/admin</span>
|
||||
<span class="directive">app_path:</span> <span class="value">apps.admin:app</span>
|
||||
<span class="directive">app_type:</span> <span class="value">wsgi</span>
|
||||
<span class="directive">workers:</span> <span class="value">2</span>
|
||||
|
||||
<span class="comment"># Starlette WebSocket Handler</span>
|
||||
- <span class="directive">name:</span> <span class="value">websocket</span>
|
||||
<span class="directive">path:</span> <span class="value">/ws</span>
|
||||
<span class="directive">app_path:</span> <span class="value">apps.websocket:app</span>
|
||||
<span class="directive">workers:</span> <span class="value">1</span></pre>
|
||||
|
||||
<h3>Request Tracing</h3>
|
||||
<p>PyServe automatically generates and propagates <code>X-Request-ID</code> headers:</p>
|
||||
<ul class="indent">
|
||||
<li>If a request has <code>X-Request-ID</code>, it's preserved</li>
|
||||
<li>Otherwise, a UUID is generated</li>
|
||||
<li>The ID is passed to subprocesses and included in response headers</li>
|
||||
<li>All logs include the request ID for tracing</li>
|
||||
</ul>
|
||||
|
||||
<h3>Process Orchestration vs ASGI Mount</h3>
|
||||
<table class="dirindex">
|
||||
<tr>
|
||||
<th>Feature</th>
|
||||
<th>Process Orchestration</th>
|
||||
<th>ASGI Mount</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Isolation</td>
|
||||
<td>Full process isolation</td>
|
||||
<td>Shared process</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Memory</td>
|
||||
<td>Separate per app</td>
|
||||
<td>Shared</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Crash Impact</td>
|
||||
<td>Only that app restarts</td>
|
||||
<td>All apps affected</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Health Checks</td>
|
||||
<td>Yes, with auto-restart</td>
|
||||
<td>No</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Multi-worker</td>
|
||||
<td>Yes, per app</td>
|
||||
<td>No</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Latency</td>
|
||||
<td>HTTP proxy overhead</td>
|
||||
<td>In-process (faster)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Use Case</td>
|
||||
<td>Production, isolation needed</td>
|
||||
<td>Development, simple setups</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="note">
|
||||
<strong>When to use Process Orchestration:</strong>
|
||||
<ul class="indent">
|
||||
<li>Running multiple apps that shouldn't affect each other</li>
|
||||
<li>Need automatic restart on failure</li>
|
||||
<li>Different resource requirements per app</li>
|
||||
<li>Production deployments</li>
|
||||
</ul>
|
||||
<strong>When to use ASGI Mount:</strong>
|
||||
<ul class="indent">
|
||||
<li>Development and testing</li>
|
||||
<li>Simple setups with trusted apps</li>
|
||||
<li>Minimal latency requirements</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="note">
|
||||
<strong>See Also:</strong>
|
||||
<ul class="indent">
|
||||
<li><a href="asgi-mount.html">ASGI Mounting Guide</a> — In-process app mounting</li>
|
||||
<li><a href="../reference/extensions.html">Extensions Reference</a> — All extension types</li>
|
||||
<li><a href="configuration.html">Configuration Guide</a> — Full configuration reference</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<p>pyserve © 2024-2025 | MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,156 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Reverse Proxy - pyserve</title>
|
||||
<link rel="stylesheet" href="../style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="header">
|
||||
<h1>pyserve</h1>
|
||||
<div class="tagline">python application orchestrator</div>
|
||||
</div>
|
||||
|
||||
<div class="breadcrumb">
|
||||
<a href="../index.html">pyserve</a> » <a href="/guides/">Guides</a> » Reverse Proxy
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<h2>Reverse Proxy</h2>
|
||||
|
||||
<p>pyserve can act as a reverse proxy, forwarding requests to backend services.</p>
|
||||
|
||||
<h3>Basic Proxy Configuration</h3>
|
||||
<p>Use the <code>proxy_pass</code> directive in routing:</p>
|
||||
|
||||
<pre><span class="directive">extensions:</span>
|
||||
- <span class="directive">type:</span> <span class="value">routing</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">regex_locations:</span>
|
||||
<span class="value">"~^/api/"</span>:
|
||||
<span class="directive">proxy_pass:</span> <span class="value">"http://localhost:9001"</span></pre>
|
||||
|
||||
<p>All requests to <code>/api/*</code> will be forwarded to <code>http://localhost:9001/api/*</code>.</p>
|
||||
|
||||
<h3>Proxy Headers</h3>
|
||||
<p>pyserve automatically adds standard proxy headers:</p>
|
||||
|
||||
<table class="dirindex">
|
||||
<tr>
|
||||
<td><code>X-Forwarded-For</code></td>
|
||||
<td>Client's IP address</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>X-Forwarded-Proto</code></td>
|
||||
<td>Original protocol (http/https)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>X-Forwarded-Host</code></td>
|
||||
<td>Original Host header</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>X-Real-IP</code></td>
|
||||
<td>Client's real IP address</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Custom Headers</h3>
|
||||
<p>Add custom headers to proxied requests:</p>
|
||||
|
||||
<pre><span class="value">"~^/api/"</span>:
|
||||
<span class="directive">proxy_pass:</span> <span class="value">"http://localhost:9001"</span>
|
||||
<span class="directive">headers:</span>
|
||||
- <span class="value">"X-Custom-Header: my-value"</span>
|
||||
- <span class="value">"Authorization: Bearer token123"</span></pre>
|
||||
|
||||
<h3>Dynamic Headers with Captures</h3>
|
||||
<p>Use regex capture groups to build dynamic headers:</p>
|
||||
|
||||
<pre><span class="value">"~^/api/v(?P<version>\\d+)/(?P<service>\\w+)"</span>:
|
||||
<span class="directive">proxy_pass:</span> <span class="value">"http://localhost:9001"</span>
|
||||
<span class="directive">headers:</span>
|
||||
- <span class="value">"X-API-Version: {version}"</span>
|
||||
- <span class="value">"X-Service: {service}"</span>
|
||||
- <span class="value">"X-Client-IP: $remote_addr"</span></pre>
|
||||
|
||||
<p>Special variables:</p>
|
||||
<ul class="indent">
|
||||
<li><code>{capture_name}</code> — Named capture group from regex</li>
|
||||
<li><code>$remote_addr</code> — Client's IP address</li>
|
||||
</ul>
|
||||
|
||||
<h3>Proxy Timeout</h3>
|
||||
<p>Configure timeout for proxy requests:</p>
|
||||
|
||||
<pre><span class="comment"># Global default timeout</span>
|
||||
<span class="directive">server:</span>
|
||||
<span class="directive">proxy_timeout:</span> <span class="value">30.0</span>
|
||||
|
||||
<span class="comment"># Per-route timeout</span>
|
||||
<span class="directive">extensions:</span>
|
||||
- <span class="directive">type:</span> <span class="value">routing</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">regex_locations:</span>
|
||||
<span class="value">"~^/api/slow"</span>:
|
||||
<span class="directive">proxy_pass:</span> <span class="value">"http://localhost:9001"</span>
|
||||
<span class="directive">timeout:</span> <span class="value">120</span> <span class="comment"># 2 minutes for slow endpoints</span></pre>
|
||||
|
||||
<h3>URL Rewriting</h3>
|
||||
<p>The proxy preserves the original request path by default:</p>
|
||||
|
||||
<pre><span class="comment"># Request: GET /api/users/123</span>
|
||||
<span class="comment"># Proxied: GET http://backend:9001/api/users/123</span>
|
||||
<span class="value">"~^/api/"</span>:
|
||||
<span class="directive">proxy_pass:</span> <span class="value">"http://backend:9001"</span></pre>
|
||||
|
||||
<p>To proxy to a specific path:</p>
|
||||
|
||||
<pre><span class="comment"># Request: GET /api/users/123</span>
|
||||
<span class="comment"># Proxied: GET http://backend:9001/v2/users/123 (path preserved)</span>
|
||||
<span class="value">"~^/api/"</span>:
|
||||
<span class="directive">proxy_pass:</span> <span class="value">"http://backend:9001/v2"</span></pre>
|
||||
|
||||
<h3>Load Balancing Example</h3>
|
||||
<p>Route different services to different backends:</p>
|
||||
|
||||
<pre><span class="directive">extensions:</span>
|
||||
- <span class="directive">type:</span> <span class="value">routing</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">regex_locations:</span>
|
||||
<span class="value">"~^/api/users"</span>:
|
||||
<span class="directive">proxy_pass:</span> <span class="value">"http://user-service:8001"</span>
|
||||
|
||||
<span class="value">"~^/api/orders"</span>:
|
||||
<span class="directive">proxy_pass:</span> <span class="value">"http://order-service:8002"</span>
|
||||
|
||||
<span class="value">"~^/api/products"</span>:
|
||||
<span class="directive">proxy_pass:</span> <span class="value">"http://product-service:8003"</span></pre>
|
||||
|
||||
<h3>Error Handling</h3>
|
||||
<p>pyserve returns appropriate error codes for proxy failures:</p>
|
||||
|
||||
<table class="dirindex">
|
||||
<tr>
|
||||
<td><code>502 Bad Gateway</code></td>
|
||||
<td>Backend connection failed or returned invalid response</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>504 Gateway Timeout</code></td>
|
||||
<td>Backend did not respond within timeout</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="note">
|
||||
<strong>Note:</strong> pyserve uses <code>httpx</code> for async HTTP requests
|
||||
to backend services, supporting HTTP/1.1 and HTTP/2.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<p>pyserve © 2024-2025 | MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,169 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Routing - pyserve</title>
|
||||
<link rel="stylesheet" href="../style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="header">
|
||||
<h1>pyserve</h1>
|
||||
<div class="tagline">python application orchestrator</div>
|
||||
</div>
|
||||
|
||||
<div class="breadcrumb">
|
||||
<a href="../index.html">pyserve</a> » <a href="/guides/">Guides</a> » Routing
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<h2>Routing</h2>
|
||||
|
||||
<p>pyserve supports nginx-style routing patterns including exact matches,
|
||||
regex locations, and SPA fallback.</p>
|
||||
|
||||
<h3>Location Types</h3>
|
||||
|
||||
<table class="dirindex">
|
||||
<tr>
|
||||
<td><code>=</code></td>
|
||||
<td>Exact match</td>
|
||||
<td class="desc"><code>=/health</code> matches only <code>/health</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>~</code></td>
|
||||
<td>Case-sensitive regex</td>
|
||||
<td class="desc"><code>~^/api/v\d+/</code> matches <code>/api/v1/</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>~*</code></td>
|
||||
<td>Case-insensitive regex</td>
|
||||
<td class="desc"><code>~*\.(js|css)$</code> matches <code>.JS</code> and <code>.css</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>__default__</code></td>
|
||||
<td>Default fallback</td>
|
||||
<td class="desc">Matches when no other route matches</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Match Priority</h3>
|
||||
<p>Routes are processed in the following order:</p>
|
||||
<ol class="indent">
|
||||
<li><strong>Exact matches</strong> (<code>=</code>) — checked first</li>
|
||||
<li><strong>Regex patterns</strong> (<code>~</code> and <code>~*</code>) — in definition order</li>
|
||||
<li><strong>Default fallback</strong> (<code>__default__</code>) — last resort</li>
|
||||
</ol>
|
||||
|
||||
<h3>Routing Configuration</h3>
|
||||
<p>Routing is configured via the <code>routing</code> extension:</p>
|
||||
|
||||
<pre><span class="directive">extensions:</span>
|
||||
- <span class="directive">type:</span> <span class="value">routing</span>
|
||||
<span class="directive">config:</span>
|
||||
<span class="directive">regex_locations:</span>
|
||||
<span class="comment"># Exact match for health check</span>
|
||||
<span class="value">"=/health"</span>:
|
||||
<span class="directive">return:</span> <span class="value">"200 OK"</span>
|
||||
<span class="directive">content_type:</span> <span class="value">"text/plain"</span>
|
||||
|
||||
<span class="comment"># Static files with caching</span>
|
||||
<span class="value">"~*\\.(js|css|png|jpg|gif|ico)$"</span>:
|
||||
<span class="directive">root:</span> <span class="value">"./static"</span>
|
||||
<span class="directive">cache_control:</span> <span class="value">"public, max-age=31536000"</span>
|
||||
|
||||
<span class="comment"># HTML files without caching</span>
|
||||
<span class="value">"~*\\.html$"</span>:
|
||||
<span class="directive">root:</span> <span class="value">"./static"</span>
|
||||
<span class="directive">cache_control:</span> <span class="value">"no-cache"</span>
|
||||
|
||||
<span class="comment"># Default fallback</span>
|
||||
<span class="value">"__default__"</span>:
|
||||
<span class="directive">root:</span> <span class="value">"./static"</span>
|
||||
<span class="directive">index_file:</span> <span class="value">"index.html"</span></pre>
|
||||
|
||||
<h3>Location Directives</h3>
|
||||
|
||||
<dl>
|
||||
<dt>root</dt>
|
||||
<dd>Base directory for serving files.</dd>
|
||||
|
||||
<dt>index_file</dt>
|
||||
<dd>Index file name for directory requests. Default: <code>index.html</code></dd>
|
||||
|
||||
<dt>proxy_pass</dt>
|
||||
<dd>Upstream server URL for reverse proxy. See <a href="reverse-proxy.html">Reverse Proxy</a>.</dd>
|
||||
|
||||
<dt>return</dt>
|
||||
<dd>Return a fixed response. Format: <code>"status message"</code> or <code>"status"</code></dd>
|
||||
|
||||
<dt>content_type</dt>
|
||||
<dd>Response content type for <code>return</code> directive.</dd>
|
||||
|
||||
<dt>cache_control</dt>
|
||||
<dd>Cache-Control header value.</dd>
|
||||
|
||||
<dt>headers</dt>
|
||||
<dd>List of additional headers to add. Format: <code>"Header-Name: value"</code></dd>
|
||||
|
||||
<dt>spa_fallback</dt>
|
||||
<dd>Enable SPA mode — serve index file for all routes.</dd>
|
||||
|
||||
<dt>exclude_patterns</dt>
|
||||
<dd>URL patterns to exclude from SPA fallback.</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Named Capture Groups</h3>
|
||||
<p>Regex locations support named capture groups that can be used in headers and proxy URLs:</p>
|
||||
|
||||
<pre><span class="value">"~^/api/v(?P<version>\\d+)/(?P<resource>\\w+)"</span>:
|
||||
<span class="directive">proxy_pass:</span> <span class="value">"http://backend:9001"</span>
|
||||
<span class="directive">headers:</span>
|
||||
- <span class="value">"X-API-Version: {version}"</span>
|
||||
- <span class="value">"X-Resource: {resource}"</span></pre>
|
||||
|
||||
<p>Request to <code>/api/v2/users</code> will have headers:</p>
|
||||
<ul class="indent">
|
||||
<li><code>X-API-Version: 2</code></li>
|
||||
<li><code>X-Resource: users</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>SPA Configuration</h3>
|
||||
<p>For Single Page Applications, use <code>spa_fallback</code> with <code>exclude_patterns</code>:</p>
|
||||
|
||||
<pre><span class="value">"__default__"</span>:
|
||||
<span class="directive">spa_fallback:</span> <span class="value">true</span>
|
||||
<span class="directive">root:</span> <span class="value">"./dist"</span>
|
||||
<span class="directive">index_file:</span> <span class="value">"index.html"</span>
|
||||
<span class="directive">exclude_patterns:</span>
|
||||
- <span class="value">"/api/"</span>
|
||||
- <span class="value">"/assets/"</span>
|
||||
- <span class="value">"/static/"</span></pre>
|
||||
|
||||
<p>This will:</p>
|
||||
<ul class="indent">
|
||||
<li>Serve <code>index.html</code> for routes like <code>/about</code>, <code>/users/123</code></li>
|
||||
<li>Return 404 for <code>/api/*</code>, <code>/assets/*</code>, <code>/static/*</code> if file not found</li>
|
||||
</ul>
|
||||
|
||||
<h3>Static File Serving</h3>
|
||||
<p>Basic static file configuration:</p>
|
||||
|
||||
<pre><span class="value">"~*\\.(css|js|png|jpg|gif|svg|woff2?)$"</span>:
|
||||
<span class="directive">root:</span> <span class="value">"./static"</span>
|
||||
<span class="directive">cache_control:</span> <span class="value">"public, max-age=86400"</span>
|
||||
<span class="directive">headers:</span>
|
||||
- <span class="value">"X-Content-Type-Options: nosniff"</span></pre>
|
||||
|
||||
<div class="note">
|
||||
<strong>Note:</strong> pyserve automatically detects MIME types based on file extensions.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<p>pyserve © 2024-2025 | MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user