initial commit

This commit is contained in:
Илья Глазунов 2025-12-05 12:57:41 +03:00
commit 1f25033d2d
16 changed files with 2554 additions and 0 deletions

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# docs.pyserve.org
This repository contains the source files for the documentation of the PyServe project, which can be found at [docs.pyserve.org](https://docs.pyserve.org).

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Getting Started - 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> » Getting Started
</div>
<div id="content">
<h2>Getting Started</h2>
<p>Get up and running with pyserve quickly.</p>
<table class="dirindex">
<tr>
<td class="icon"><span class="file">📄</span></td>
<td><a href="installation.html">Installation</a></td>
<td class="desc">Download and install pyserve</td>
</tr>
<tr>
<td class="icon"><span class="file">📄</span></td>
<td><a href="quickstart.html">Quick Start</a></td>
<td class="desc">Get up and running in 5 minutes</td>
</tr>
</table>
</div>
<div id="footer">
<p>pyserve &copy; 2024-2025 | MIT License</p>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Installation - 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="/getting-started/">Getting Started</a> » Installation
</div>
<div id="content">
<h2>Installation</h2>
<h3>Requirements</h3>
<ul class="indent">
<li>Python 3.12 or higher</li>
<li>pip (Python package manager)</li>
</ul>
<h3>Install from Release (Recommended)</h3>
<p>Download the latest wheel file from <a href="https://github.com/ShiftyX1/PyServe/releases">GitHub Releases</a> and install it:</p>
<pre><span class="comment"># Download the wheel file from releases</span>
<span class="comment"># Example: pyserve-0.7.0-py3-none-any.whl</span>
pip install pyserve-0.7.0-py3-none-any.whl</pre>
<p>After installation, the <code>pyserve</code> command will be available in your terminal:</p>
<pre>pyserve --version</pre>
<h3>Install from Source</h3>
<p>For development or if you want the latest changes:</p>
<pre><span class="comment"># Clone the repository</span>
git clone https://github.com/ShiftyX1/PyServe.git
cd PyServe
<span class="comment"># Install with Poetry (recommended for development)</span>
make init
<span class="comment"># Or build and install the package</span>
make build
pip install dist/pyserve-*.whl</pre>
<h3>Verify Installation</h3>
<p>Check that pyserve is installed correctly:</p>
<pre>pyserve --version
<span class="comment"># Output: pyserve 0.7.0</span></pre>
<h3>Dependencies</h3>
<p>pyserve automatically installs the following dependencies:</p>
<ul class="indent">
<li><code>starlette</code> — ASGI framework</li>
<li><code>uvicorn</code> — ASGI server</li>
<li><code>pyyaml</code> — YAML configuration parsing</li>
<li><code>structlog</code> — Structured logging</li>
<li><code>httpx</code> — HTTP client for reverse proxy</li>
</ul>
<div class="note">
<strong>Next:</strong> Continue to <a href="quickstart.html">Quick Start</a> to run your first server.
</div>
</div>
<div id="footer">
<p>pyserve &copy; 2024-2025 | MIT License</p>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quick Start - 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="/getting-started/">Getting Started</a> » Quick Start
</div>
<div id="content">
<h2>Quick Start</h2>
<p>Get pyserve running in under 5 minutes.</p>
<h3>1. Create Configuration File</h3>
<p>Create a file named <code>config.yaml</code> in your project directory:</p>
<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">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">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>
<h3>2. Create Static Directory</h3>
<p>Create a <code>static</code> folder and add an <code>index.html</code>:</p>
<pre>mkdir -p static
echo '&lt;h1&gt;Hello from pyserve!&lt;/h1&gt;' &gt; static/index.html</pre>
<h3>3. Start the Server</h3>
<pre>pyserve</pre>
<p>You should see output like:</p>
<pre>Starting PyServe server on 0.0.0.0:8080</pre>
<h3>4. Open in Browser</h3>
<p>Navigate to <a href="http://localhost:8080">http://localhost:8080</a> — you should see your page!</p>
<h3>Using CLI Options</h3>
<p>Override configuration via command line:</p>
<pre><span class="comment"># Use a different config file</span>
pyserve -c /path/to/config.yaml
<span class="comment"># Override host and port</span>
pyserve --host 127.0.0.1 --port 9000
<span class="comment"># Enable debug mode (verbose logging)</span>
pyserve --debug</pre>
<h3>Example: Serve Documentation</h3>
<p>Serve a documentation directory with proper caching:</p>
<pre><span class="directive">http:</span>
<span class="directive">static_dir:</span> <span class="value">./docs</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">8000</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">"=/"</span>:
<span class="directive">root:</span> <span class="value">"./docs"</span>
<span class="directive">index_file:</span> <span class="value">"index.html"</span>
<span class="value">"~*\\.(css|js)$"</span>:
<span class="directive">root:</span> <span class="value">"./docs"</span>
<span class="directive">cache_control:</span> <span class="value">"public, max-age=3600"</span>
<span class="value">"~*\\.html$"</span>:
<span class="directive">root:</span> <span class="value">"./docs"</span>
<span class="directive">cache_control:</span> <span class="value">"no-cache"</span>
<span class="value">"__default__"</span>:
<span class="directive">root:</span> <span class="value">"./docs"</span>
<span class="directive">index_file:</span> <span class="value">"index.html"</span></pre>
<div class="note">
<strong>Next Steps:</strong>
<ul class="indent">
<li><a href="../guides/process-orchestration.html">Process Orchestration</a> — Run apps in isolated processes</li>
<li><a href="../guides/configuration.html">Configuration Guide</a> — Full configuration reference</li>
<li><a href="../guides/routing.html">Routing Guide</a> — nginx-style URL patterns</li>
<li><a href="../guides/reverse-proxy.html">Reverse Proxy</a> — Proxy to backend services</li>
</ul>
</div>
</div>
<div id="footer">
<p>pyserve &copy; 2024-2025 | MIT License</p>
</div>
</div>
</body>
</html>

269
guides/asgi-mount.html Normal file
View File

@ -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 &copy; 2024-2025 | MIT License</p>
</div>
</div>
</body>
</html>

179
guides/configuration.html Normal file
View File

@ -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 &copy; 2024-2025 | MIT License</p>
</div>
</div>
</body>
</html>

59
guides/index.html Normal file
View File

@ -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 &copy; 2024-2025 | MIT License</p>
</div>
</div>
</body>
</html>

View File

@ -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 &copy; 2024-2025 | MIT License</p>
</div>
</div>
</body>
</html>

156
guides/reverse-proxy.html Normal file
View File

@ -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&lt;version&gt;\\d+)/(?P&lt;service&gt;\\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 &copy; 2024-2025 | MIT License</p>
</div>
</div>
</body>
</html>

169
guides/routing.html Normal file
View File

@ -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&lt;version&gt;\\d+)/(?P&lt;resource&gt;\\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 &copy; 2024-2025 | MIT License</p>
</div>
</div>
</body>
</html>

118
index.html Normal file
View File

@ -0,0 +1,118 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>pyserve - Documentation</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 id="content">
<h2>About</h2>
<p>
pyserve is a Python application orchestrator and HTTP server.
Built on top of <strong>Starlette</strong> and <strong>Uvicorn</strong>,
it manages multiple ASGI/WSGI applications through a single entry point
with process isolation, health monitoring, and auto-restart.
</p>
<h2>Key Features</h2>
<ul class="indent">
<li><strong>Process Orchestration</strong> — Run multiple apps in isolated subprocesses with health checks</li>
<li><strong>Auto-restart</strong> — Failed processes restart automatically with exponential backoff</li>
<li><strong>Multi-worker Support</strong> — Configure workers per application</li>
<li><strong>nginx-style Routing</strong> — Regex patterns with exact, prefix, and case-insensitive matching</li>
<li><strong>Reverse Proxy</strong> — Forward requests to backend services with header manipulation</li>
<li><strong>Static File Serving</strong> — Efficient serving with correct MIME types</li>
<li><strong>SPA Support</strong> — Single Page Application fallback routing</li>
<li><strong>SSL/HTTPS</strong> — Secure connections with certificate configuration</li>
<li><strong>ASGI/WSGI Support</strong> — FastAPI, Flask, Django, Starlette and more</li>
<li><strong>Request Tracing</strong> — X-Request-ID propagation through proxied requests</li>
</ul>
<h2>Documentation</h2>
<h3>Getting Started</h3>
<table class="dirindex">
<tr>
<td class="icon"><span class="file">📄</span></td>
<td><a href="getting-started/installation.html">Installation</a></td>
<td class="desc">Download and install pyserve</td>
</tr>
<tr>
<td class="icon"><span class="file">📄</span></td>
<td><a href="getting-started/quickstart.html">Quick Start</a></td>
<td class="desc">Get up and running in 5 minutes</td>
</tr>
</table>
<h3>Guides</h3>
<table class="dirindex">
<tr>
<td class="icon"><span class="file">📄</span></td>
<td><a href="guides/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="guides/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="guides/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="guides/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="guides/asgi-mount.html">ASGI Mounting</a></td>
<td class="desc">Mount Python web frameworks in-process</td>
</tr>
</table>
<h3>Reference</h3>
<table class="dirindex">
<tr>
<td class="icon"><span class="file">📄</span></td>
<td><a href="reference/cli.html">CLI Reference</a></td>
<td class="desc">Command-line interface options</td>
</tr>
<tr>
<td class="icon"><span class="file">📄</span></td>
<td><a href="reference/extensions.html">Extensions</a></td>
<td class="desc">Built-in extension modules</td>
</tr>
</table>
<h2>Resources</h2>
<ul class="plain">
<li><a href="https://github.com/ShiftyX1/PyServe">GitHub Repository</a></li>
<li><a href="https://github.com/ShiftyX1/PyServe/releases">Releases &amp; Downloads</a></li>
</ul>
<h2>Version</h2>
<p>Current version: <strong>0.9.10</strong></p>
<h2>Requirements</h2>
<ul class="indent">
<li>Python 3.12 or higher</li>
</ul>
</div>
<div id="footer">
<p>pyserve &copy; 2024-2025 | MIT License</p>
</div>
</div>
</body>
</html>

251
reference/asgi-mount.html Normal file
View File

@ -0,0 +1,251 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ASGI Mount API - 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="/reference/">Reference</a> » ASGI Mount API
</div>
<div id="content">
<h2>ASGI Mount API Reference</h2>
<p>The <code>pyserve.asgi_mount</code> module provides a Python API for mounting
ASGI and WSGI applications programmatically.</p>
<h3>Classes</h3>
<h4>ASGIAppLoader</h4>
<p>Loads and manages ASGI/WSGI applications from Python import paths.</p>
<pre><span class="keyword">from</span> pyserve <span class="keyword">import</span> ASGIAppLoader
loader = ASGIAppLoader()
<span class="comment"># Load an ASGI app</span>
app = loader.load_app(
app_path=<span class="value">"mymodule:app"</span>,
app_type=<span class="value">"asgi"</span>,
module_path=<span class="value">"/path/to/project"</span>,
factory=<span class="value">False</span>,
factory_args=<span class="value">None</span>
)</pre>
<h5>Methods</h5>
<dl>
<dt>load_app(app_path, app_type="asgi", module_path=None, factory=False, factory_args=None)</dt>
<dd>
Load an application from an import path.
<ul class="indent">
<li><code>app_path</code>: Import path in format <code>module:attribute</code></li>
<li><code>app_type</code>: <code>"asgi"</code> or <code>"wsgi"</code></li>
<li><code>module_path</code>: Optional path to add to <code>sys.path</code></li>
<li><code>factory</code>: If True, call the attribute as a factory function</li>
<li><code>factory_args</code>: Dict of arguments for factory function</li>
</ul>
Returns the loaded ASGI application or <code>None</code> on error.
</dd>
<dt>get_app(app_path)</dt>
<dd>Get a previously loaded application by its path.</dd>
<dt>reload_app(app_path, **kwargs)</dt>
<dd>Reload an application, useful for development hot-reloading.</dd>
</dl>
<h4>MountedApp</h4>
<p>Represents an application mounted at a specific path.</p>
<pre><span class="keyword">from</span> pyserve <span class="keyword">import</span> MountedApp
mount = MountedApp(
path=<span class="value">"/api"</span>,
app=my_asgi_app,
name=<span class="value">"my-api"</span>,
strip_path=<span class="value">True</span>
)</pre>
<h5>Attributes</h5>
<dl>
<dt>path: str</dt>
<dd>The mount path (without trailing slash).</dd>
<dt>app: ASGIApp</dt>
<dd>The ASGI application.</dd>
<dt>name: str</dt>
<dd>Friendly name for logging.</dd>
<dt>strip_path: bool</dt>
<dd>Whether to strip the mount path from requests.</dd>
</dl>
<h5>Methods</h5>
<dl>
<dt>matches(request_path) → bool</dt>
<dd>Check if a request path matches this mount.</dd>
<dt>get_modified_path(original_path) → str</dt>
<dd>Get the modified path after stripping mount prefix.</dd>
</dl>
<h4>ASGIMountManager</h4>
<p>Manages multiple mounted applications and routes requests.</p>
<pre><span class="keyword">from</span> pyserve <span class="keyword">import</span> ASGIMountManager
manager = ASGIMountManager()
<span class="comment"># Mount using app instance</span>
manager.mount(path=<span class="value">"/api"</span>, app=my_app)
<span class="comment"># Mount using import path</span>
manager.mount(
path=<span class="value">"/flask"</span>,
app_path=<span class="value">"myapp:flask_app"</span>,
app_type=<span class="value">"wsgi"</span>
)</pre>
<h5>Methods</h5>
<dl>
<dt>mount(path, app=None, app_path=None, app_type="asgi", module_path=None, factory=False, factory_args=None, name="", strip_path=True) → bool</dt>
<dd>
Mount an application at a path. Either <code>app</code> or <code>app_path</code> must be provided.
Returns <code>True</code> on success.
</dd>
<dt>unmount(path) → bool</dt>
<dd>Remove a mounted application. Returns <code>True</code> if found and removed.</dd>
<dt>get_mount(request_path) → Optional[MountedApp]</dt>
<dd>Get the mount that matches a request path.</dd>
<dt>handle_request(scope, receive, send) → bool</dt>
<dd>Handle an ASGI request. Returns <code>True</code> if handled by a mounted app.</dd>
<dt>list_mounts() → List[Dict]</dt>
<dd>Get a list of all mounts with their configuration.</dd>
</dl>
<h5>Properties</h5>
<dl>
<dt>mounts: List[MountedApp]</dt>
<dd>Copy of the current mounts list (sorted by path length, longest first).</dd>
</dl>
<h3>Helper Functions</h3>
<p>Convenience functions for loading specific framework applications:</p>
<h4>create_fastapi_app()</h4>
<pre><span class="keyword">from</span> pyserve <span class="keyword">import</span> create_fastapi_app
app = create_fastapi_app(
app_path=<span class="value">"myapp.api:app"</span>,
module_path=<span class="value">None</span>,
factory=<span class="value">False</span>,
factory_args=<span class="value">None</span>
)</pre>
<h4>create_flask_app()</h4>
<pre><span class="keyword">from</span> pyserve <span class="keyword">import</span> create_flask_app
app = create_flask_app(
app_path=<span class="value">"myapp.web:app"</span>,
module_path=<span class="value">None</span>,
factory=<span class="value">False</span>,
factory_args=<span class="value">None</span>
)</pre>
<p>Automatically wraps the WSGI app for ASGI compatibility.</p>
<h4>create_django_app()</h4>
<pre><span class="keyword">from</span> pyserve <span class="keyword">import</span> create_django_app
app = create_django_app(
settings_module=<span class="value">"myproject.settings"</span>,
module_path=<span class="value">"/path/to/project"</span>
)</pre>
<p>Sets <code>DJANGO_SETTINGS_MODULE</code> and returns Django's ASGI application.</p>
<h4>create_starlette_app()</h4>
<pre><span class="keyword">from</span> pyserve <span class="keyword">import</span> create_starlette_app
app = create_starlette_app(
app_path=<span class="value">"myapp:starlette_app"</span>,
module_path=<span class="value">None</span>,
factory=<span class="value">False</span>,
factory_args=<span class="value">None</span>
)</pre>
<h3>Usage Example</h3>
<p>Complete example mounting multiple applications:</p>
<pre><span class="keyword">from</span> pyserve <span class="keyword">import</span> (
PyServeServer,
ASGIMountManager,
create_fastapi_app,
create_flask_app
)
<span class="comment"># Create mount manager</span>
mounts = ASGIMountManager()
<span class="comment"># Mount FastAPI</span>
api_app = create_fastapi_app(<span class="value">"myapp.api:app"</span>)
<span class="keyword">if</span> api_app:
mounts.mount(<span class="value">"/api"</span>, app=api_app, name=<span class="value">"api"</span>)
<span class="comment"># Mount Flask</span>
admin_app = create_flask_app(<span class="value">"myapp.admin:app"</span>)
<span class="keyword">if</span> admin_app:
mounts.mount(<span class="value">"/admin"</span>, app=admin_app, name=<span class="value">"admin"</span>)
<span class="comment"># List mounts</span>
<span class="keyword">for</span> mount <span class="keyword">in</span> mounts.list_mounts():
print(f<span class="value">"Mounted {mount['name']} at {mount['path']}"</span>)</pre>
<h3>Error Handling</h3>
<p>All loader functions return <code>None</code> on failure and log errors.
Check the return value before using:</p>
<pre>app = create_fastapi_app(<span class="value">"nonexistent:app"</span>)
<span class="keyword">if</span> app <span class="keyword">is None</span>:
<span class="comment"># Handle error - check logs for details</span>
print(<span class="value">"Failed to load application"</span>)</pre>
<h3>WSGI Compatibility</h3>
<p>For WSGI applications, pyserve uses adapters in this priority:</p>
<ol class="indent">
<li><code>a2wsgi.WSGIMiddleware</code> (recommended)</li>
<li><code>asgiref.wsgi.WsgiToAsgi</code> (fallback)</li>
</ol>
<p>Install an adapter:</p>
<pre>pip install a2wsgi <span class="comment"># recommended</span>
<span class="comment"># or</span>
pip install asgiref</pre>
<div class="note">
<strong>See Also:</strong>
<ul class="indent">
<li><a href="../guides/asgi-mount.html">ASGI Mounting Guide</a> — Configuration-based mounting</li>
<li><a href="extensions.html">Extensions</a> — ASGI extension configuration</li>
</ul>
</div>
</div>
<div id="footer">
<p>pyserve &copy; 2024-2025 | MIT License</p>
</div>
</div>
</body>
</html>

141
reference/cli.html Normal file
View File

@ -0,0 +1,141 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CLI Reference - 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="/reference/">Reference</a> » CLI
</div>
<div id="content">
<h2>CLI Reference</h2>
<p>pyserve provides a command-line interface for server management.</p>
<h3>Synopsis</h3>
<pre>pyserve [OPTIONS]</pre>
<h3>Options</h3>
<table class="dirindex">
<tr>
<td><code>-c, --config FILE</code></td>
<td>Path to configuration file</td>
<td class="desc">Default: <code>config.yaml</code></td>
</tr>
<tr>
<td><code>--host HOST</code></td>
<td>Bind address</td>
<td class="desc">Overrides config value</td>
</tr>
<tr>
<td><code>--port PORT</code></td>
<td>Listen port</td>
<td class="desc">Overrides config value</td>
</tr>
<tr>
<td><code>--debug</code></td>
<td>Enable debug mode</td>
<td class="desc">Sets log level to DEBUG</td>
</tr>
<tr>
<td><code>--version</code></td>
<td>Show version and exit</td>
<td class="desc"></td>
</tr>
<tr>
<td><code>--help</code></td>
<td>Show help message and exit</td>
<td class="desc"></td>
</tr>
</table>
<h3>Examples</h3>
<p><strong>Start with default configuration:</strong></p>
<pre>pyserve</pre>
<p><strong>Start with custom config file:</strong></p>
<pre>pyserve -c /path/to/config.yaml</pre>
<p><strong>Override host and port:</strong></p>
<pre>pyserve --host 127.0.0.1 --port 9000</pre>
<p><strong>Enable debug mode:</strong></p>
<pre>pyserve --debug</pre>
<p><strong>Show version:</strong></p>
<pre>pyserve --version
<span class="comment"># Output: pyserve 0.7.0</span></pre>
<h3>Configuration Priority</h3>
<p>Settings are applied in the following order (later overrides earlier):</p>
<ol class="indent">
<li>Default values</li>
<li>Configuration file (<code>config.yaml</code>)</li>
<li>Command-line options</li>
</ol>
<h3>Default Configuration</h3>
<p>If no configuration file is found, pyserve uses default settings:</p>
<ul class="indent">
<li>Host: <code>0.0.0.0</code></li>
<li>Port: <code>8080</code></li>
<li>Log level: <code>INFO</code></li>
</ul>
<h3>Exit Codes</h3>
<table class="dirindex">
<tr>
<td><code>0</code></td>
<td>Success / Clean shutdown</td>
</tr>
<tr>
<td><code>1</code></td>
<td>Configuration error or startup failure</td>
</tr>
</table>
<h3>Signals</h3>
<p>pyserve handles the following signals:</p>
<table class="dirindex">
<tr>
<td><code>SIGINT</code> (Ctrl+C)</td>
<td>Graceful shutdown</td>
</tr>
<tr>
<td><code>SIGTERM</code></td>
<td>Graceful shutdown</td>
</tr>
</table>
<h3>Development Commands (Makefile)</h3>
<p>When working with the source repository, use make commands:</p>
<pre>make run <span class="comment"># Start in development mode</span>
make run-prod <span class="comment"># Start in production mode</span>
make test <span class="comment"># Run tests</span>
make test-cov <span class="comment"># Tests with coverage</span>
make lint <span class="comment"># Check code with linters</span>
make format <span class="comment"># Format code</span>
make build <span class="comment"># Build wheel package</span>
make clean <span class="comment"># Clean temporary files</span>
make help <span class="comment"># Show all commands</span></pre>
</div>
<div id="footer">
<p>pyserve &copy; 2024-2025 | MIT License</p>
</div>
</div>
</body>
</html>

325
reference/extensions.html Normal file
View File

@ -0,0 +1,325 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Extensions - 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="/reference/">Reference</a> » Extensions
</div>
<div id="content">
<h2>Extensions</h2>
<p>pyserve uses a modular extension system for adding functionality. Extensions
are loaded in order and can process requests and modify responses.</p>
<h3>Built-in Extensions</h3>
<table class="dirindex">
<tr>
<td><code>process_orchestration</code></td>
<td>Run ASGI/WSGI apps in isolated processes with health monitoring</td>
</tr>
<tr>
<td><code>routing</code></td>
<td>nginx-style URL routing with regex patterns</td>
</tr>
<tr>
<td><code>asgi</code></td>
<td>Mount ASGI/WSGI applications in-process</td>
</tr>
<tr>
<td><code>security</code></td>
<td>Security headers and IP filtering</td>
</tr>
<tr>
<td><code>caching</code></td>
<td>Response caching (in development)</td>
</tr>
<tr>
<td><code>monitoring</code></td>
<td>Request metrics and statistics</td>
</tr>
</table>
<h3>Extension Configuration</h3>
<p>Extensions are configured in the <code>extensions</code> section:</p>
<pre><span class="directive">extensions:</span>
- <span class="directive">type:</span> <span class="value">routing</span>
<span class="directive">config:</span>
<span class="comment"># extension-specific configuration</span>
- <span class="directive">type:</span> <span class="value">security</span>
<span class="directive">config:</span>
<span class="comment"># ...</span></pre>
<h3>Routing Extension</h3>
<p>The primary extension for URL routing. See <a href="../guides/routing.html">Routing Guide</a> for full documentation.</p>
<pre><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">"~^/api/"</span>:
<span class="directive">proxy_pass:</span> <span class="value">"http://backend:9001"</span>
<span class="value">"__default__"</span>:
<span class="directive">root:</span> <span class="value">"./static"</span></pre>
<h3>Security Extension</h3>
<p>Adds security headers and IP-based access control.</p>
<h4>Configuration Options</h4>
<dl>
<dt>security_headers</dt>
<dd>Dictionary of security headers to add to all responses</dd>
<dt>allowed_ips</dt>
<dd>List of allowed IP addresses (whitelist mode)</dd>
<dt>blocked_ips</dt>
<dd>List of blocked IP addresses (blacklist mode)</dd>
</dl>
<pre><span class="directive">- type:</span> <span class="value">security</span>
<span class="directive">config:</span>
<span class="directive">security_headers:</span>
<span class="directive">X-Frame-Options:</span> <span class="value">DENY</span>
<span class="directive">X-Content-Type-Options:</span> <span class="value">nosniff</span>
<span class="directive">X-XSS-Protection:</span> <span class="value">"1; mode=block"</span>
<span class="directive">Strict-Transport-Security:</span> <span class="value">"max-age=31536000"</span>
<span class="directive">blocked_ips:</span>
- <span class="value">"192.168.1.100"</span>
- <span class="value">"10.0.0.50"</span></pre>
<p>Default security headers if not specified:</p>
<ul class="indent">
<li><code>X-Content-Type-Options: nosniff</code></li>
<li><code>X-Frame-Options: DENY</code></li>
<li><code>X-XSS-Protection: 1; mode=block</code></li>
</ul>
<h3>Caching Extension</h3>
<p>Response caching for improved performance. <em>(Currently in development)</em></p>
<h4>Configuration Options</h4>
<dl>
<dt>cache_patterns</dt>
<dd>URL patterns to cache</dd>
<dt>cache_ttl</dt>
<dd>Default cache TTL in seconds. Default: <code>3600</code></dd>
</dl>
<pre><span class="directive">- type:</span> <span class="value">caching</span>
<span class="directive">config:</span>
<span class="directive">cache_ttl:</span> <span class="value">3600</span>
<span class="directive">cache_patterns:</span>
- <span class="value">"/api/public/*"</span></pre>
<h3>Monitoring Extension</h3>
<p>Collects request metrics and provides statistics.</p>
<h4>Configuration Options</h4>
<dl>
<dt>enable_metrics</dt>
<dd>Enable metrics collection. Default: <code>true</code></dd>
</dl>
<pre><span class="directive">- type:</span> <span class="value">monitoring</span>
<span class="directive">config:</span>
<span class="directive">enable_metrics:</span> <span class="value">true</span></pre>
<p>Collected metrics (available at <code>/metrics</code>):</p>
<ul class="indent">
<li><code>request_count</code> — Total number of requests</li>
<li><code>error_count</code> — Number of requests with 4xx/5xx status</li>
<li><code>error_rate</code> — Error rate (errors / total)</li>
<li><code>avg_response_time</code> — Average response time in seconds</li>
</ul>
<h3>Built-in Endpoints</h3>
<p>pyserve provides built-in endpoints regardless of extensions:</p>
<table class="dirindex">
<tr>
<td><code>/health</code></td>
<td>Health check endpoint, returns <code>200 OK</code></td>
</tr>
<tr>
<td><code>/metrics</code></td>
<td>JSON metrics from all extensions</td>
</tr>
</table>
<h3>Extension Processing Order</h3>
<p>Extensions process requests in the order they are defined:</p>
<ol class="indent">
<li>Request comes in</li>
<li>Each extension's <code>process_request</code> is called in order</li>
<li>First extension to return a response wins</li>
<li>Response passes through each extension's <code>process_response</code></li>
<li>Response is sent to client</li>
</ol>
<div class="note">
<strong>Note:</strong> Place the <code>routing</code> extension last if you want
other extensions (like security) to process requests first.
</div>
<h3>ASGI Extension</h3>
<p>Mount external ASGI/WSGI applications (FastAPI, Flask, Django, etc.) at specified paths.</p>
<h4>Configuration Options</h4>
<dl>
<dt>mounts</dt>
<dd>List of mount configurations (see below)</dd>
</dl>
<h4>Mount Configuration</h4>
<dl>
<dt>path</dt>
<dd>URL path where the app will be mounted. Example: <code>/api</code></dd>
<dt>app_path</dt>
<dd>Python import path. Format: <code>module: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></dd>
<dt>factory</dt>
<dd>If <code>true</code>, call as factory function. Default: <code>false</code></dd>
<dt>factory_args</dt>
<dd>Arguments to pass to factory function</dd>
<dt>name</dt>
<dd>Friendly name for logging</dd>
<dt>strip_path</dt>
<dd>Remove mount path from request URL. Default: <code>true</code></dd>
<dt>django_settings</dt>
<dd>Django settings module (for Django apps only)</dd>
</dl>
<pre><span class="directive">- type:</span> <span class="value">asgi</span>
<span class="directive">config:</span>
<span class="directive">mounts:</span>
<span class="comment"># FastAPI application</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"</span>
<span class="comment"># Flask application (WSGI)</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">name:</span> <span class="value">"admin"</span>
<span class="comment"># Factory pattern with arguments</span>
- <span class="directive">path:</span> <span class="value">"/api/v2"</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>
<span class="directive">factory_args:</span>
<span class="directive">debug:</span> <span class="value">true</span>
<span class="directive">version:</span> <span class="value">"2.0"</span></pre>
<p>Supported frameworks:</p>
<ul class="indent">
<li><strong>FastAPI</strong> — Native ASGI (<code>app_type: asgi</code>)</li>
<li><strong>Starlette</strong> — Native ASGI (<code>app_type: asgi</code>)</li>
<li><strong>Flask</strong> — WSGI, auto-wrapped (<code>app_type: wsgi</code>)</li>
<li><strong>Django</strong> — Use <code>django_settings</code> parameter</li>
<li><strong>Custom ASGI</strong> — Any ASGI-compatible application</li>
</ul>
<div class="note">
<strong>Note:</strong> For WSGI applications, install <code>a2wsgi</code> or <code>asgiref</code>:
<code>pip install a2wsgi</code>
</div>
<p>See <a href="../guides/asgi-mount.html">ASGI Mounting Guide</a> for detailed documentation.</p>
<h3>Process Orchestration Extension</h3>
<p>The flagship extension for running apps in isolated subprocesses. <strong>Recommended for production.</strong></p>
<h4>Key Features</h4>
<ul class="indent">
<li>Process isolation — each app runs in its own subprocess</li>
<li>Health monitoring with automatic restart</li>
<li>Multi-worker support per application</li>
<li>Dynamic port allocation (9000-9999)</li>
<li>Request tracing with X-Request-ID</li>
</ul>
<pre><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="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>
- <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>
<h4>App Configuration</h4>
<dl>
<dt>name</dt>
<dd>Unique identifier (required)</dd>
<dt>path</dt>
<dd>URL path prefix (required)</dd>
<dt>app_path</dt>
<dd>Python import path (required)</dd>
<dt>app_type</dt>
<dd><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>health_check_path</dt>
<dd>Health endpoint. Default: <code>/health</code></dd>
<dt>max_restart_count</dt>
<dd>Max restart attempts. Default: <code>5</code></dd>
</dl>
<p>See <a href="../guides/process-orchestration.html">Process Orchestration Guide</a> for full documentation.</p>
</div>
<div id="footer">
<p>pyserve &copy; 2024-2025 | MIT License</p>
</div>
</div>
</body>
</html>

49
reference/index.html Normal file
View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reference - 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> » Reference
</div>
<div id="content">
<h2>Reference</h2>
<p>API and CLI reference documentation.</p>
<table class="dirindex">
<tr>
<td class="icon"><span class="file">📄</span></td>
<td><a href="cli.html">CLI Reference</a></td>
<td class="desc">Command-line interface options</td>
</tr>
<tr>
<td class="icon"><span class="file">📄</span></td>
<td><a href="extensions.html">Extensions</a></td>
<td class="desc">Built-in extension modules</td>
</tr>
<tr>
<td class="icon"><span class="file">📄</span></td>
<td><a href="asgi-mount.html">ASGI Mount API</a></td>
<td class="desc">Python API for mounting ASGI/WSGI applications</td>
</tr>
</table>
</div>
<div id="footer">
<p>pyserve &copy; 2024-2025 | MIT License</p>
</div>
</div>
</body>
</html>

237
style.css Normal file
View File

@ -0,0 +1,237 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, sans-serif;
font-size: 13px;
line-height: 1.5;
color: #c9c9c9;
background: #1a1a1a;
}
#container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
#header {
border-bottom: 2px solid #2e8b57;
padding-bottom: 10px;
margin-bottom: 20px;
}
#header h1 {
font-size: 24px;
font-weight: normal;
color: #3cb371;
margin: 0;
}
#header .tagline {
font-size: 12px;
color: #888;
font-style: italic;
}
#content {
margin-bottom: 30px;
}
h2 {
font-size: 16px;
font-weight: bold;
color: #e0e0e0;
margin: 20px 0 10px 0;
padding-bottom: 5px;
border-bottom: 1px solid #333;
}
h3 {
font-size: 14px;
font-weight: bold;
color: #d0d0d0;
margin: 15px 0 8px 0;
}
p {
margin: 10px 0;
}
a {
color: #5fba7d;
text-decoration: none;
}
a:hover {
text-decoration: underline;
color: #7ccd9a;
}
a:visited {
color: #4a9a6a;
}
/* Directory listing table */
table.dirindex {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}
table.dirindex td {
padding: 4px 8px;
border-bottom: 1px solid #2a2a2a;
vertical-align: top;
}
table.dirindex tr:hover {
background: #252525;
}
table.dirindex .icon {
width: 20px;
text-align: center;
}
table.dirindex .desc {
color: #888;
font-size: 12px;
}
/* Plain list */
ul.plain {
list-style: none;
margin: 10px 0;
padding-left: 0;
}
ul.plain li {
padding: 3px 0;
}
ul.plain li:before {
content: "» ";
color: #555;
}
/* Indented list */
ul.indent {
margin: 10px 0;
padding-left: 25px;
}
ul.indent li {
padding: 2px 0;
}
/* Code blocks */
pre {
background: #0d0d0d;
border: 1px solid #333;
padding: 10px;
overflow-x: auto;
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
font-size: 12px;
line-height: 1.4;
margin: 10px 0;
color: #b0b0b0;
}
code {
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
font-size: 12px;
background: #0d0d0d;
padding: 1px 4px;
color: #b0b0b0;
}
/* Inline code in text */
p code, li code, td code {
border: 1px solid #333;
}
/* Configuration block */
.config {
background: #1f1f1a;
border: 1px solid #3a3a30;
padding: 10px;
margin: 10px 0;
}
/* Note/warning blocks */
.note {
background: #1a2a1a;
border-left: 3px solid #2e8b57;
padding: 10px;
margin: 15px 0;
}
.note strong {
color: #3cb371;
}
.warning {
background: #2a2a1a;
border-left: 3px solid #b8860b;
padding: 10px;
margin: 15px 0;
}
.warning strong {
color: #daa520;
}
/* Definition list */
dl {
margin: 10px 0;
}
dt {
font-weight: bold;
margin-top: 10px;
color: #e0e0e0;
}
dd {
margin-left: 20px;
color: #999;
}
/* Navigation breadcrumb */
.breadcrumb {
font-size: 12px;
color: #888;
margin-bottom: 15px;
}
.breadcrumb a {
color: #5fba7d;
}
/* Footer */
#footer {
border-top: 1px solid #333;
padding-top: 10px;
font-size: 11px;
color: #666;
text-align: center;
}
/* Syntax highlighting for config examples */
.directive {
color: #5fba7d;
font-weight: bold;
}
.value {
color: #87ceeb;
}
.comment {
color: #666;
font-style: italic;
}