mirror of
https://github.com/emmett-framework/granian.git
synced 2025-07-07 19:35:33 +00:00
Add support for static files serving (#566)
This commit is contained in:
parent
f5cf71fd0b
commit
6959ee0d11
17 changed files with 2266 additions and 781 deletions
23
Cargo.lock
generated
23
Cargo.lock
generated
|
@ -381,6 +381,7 @@ dependencies = [
|
|||
"itertools",
|
||||
"log",
|
||||
"mimalloc",
|
||||
"mime_guess",
|
||||
"pem",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
|
@ -622,6 +623,22 @@ dependencies = [
|
|||
"libmimalloc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.8"
|
||||
|
@ -1299,6 +1316,12 @@ version = "1.18.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
|
|
|
@ -41,6 +41,7 @@ hyper-util = { version = "=0.1", features = ["server-auto", "tokio"] }
|
|||
itertools = "0.14"
|
||||
log = "0.4"
|
||||
mimalloc = { version = "0.1.43", default-features = false, features = ["local_dynamic_tls"], optional = true }
|
||||
mime_guess = "=2.0"
|
||||
pem = "=3.0"
|
||||
percent-encoding = "=2.3"
|
||||
pin-project-lite = "=0.2"
|
||||
|
|
|
@ -228,6 +228,22 @@ def option(*param_decls: str, cls: Optional[Type[click.Option]] = None, **attrs:
|
|||
default=False,
|
||||
help='Treat target as a factory function, that should be invoked to build the actual target',
|
||||
)
|
||||
@option(
|
||||
'--static-path-route',
|
||||
default='/static',
|
||||
help='Route for static file serving',
|
||||
)
|
||||
@option(
|
||||
'--static-path-mount',
|
||||
type=click.Path(exists=True, file_okay=False, dir_okay=True, readable=True, path_type=pathlib.Path),
|
||||
help='Path to mount for static file serving',
|
||||
)
|
||||
@option(
|
||||
'--static-path-expires',
|
||||
type=click.IntRange(60),
|
||||
default=86400,
|
||||
help='Cache headers expiration (in seconds) for static file service',
|
||||
)
|
||||
@option(
|
||||
'--reload/--no-reload',
|
||||
default=False,
|
||||
|
@ -327,6 +343,9 @@ def cli(
|
|||
workers_lifetime: Optional[int],
|
||||
workers_kill_timeout: Optional[int],
|
||||
factory: bool,
|
||||
static_path_route: str,
|
||||
static_path_mount: Optional[pathlib.Path],
|
||||
static_path_expires: int,
|
||||
reload: bool,
|
||||
reload_paths: Optional[List[pathlib.Path]],
|
||||
reload_ignore_dirs: Optional[List[str]],
|
||||
|
@ -394,6 +413,9 @@ def cli(
|
|||
workers_lifetime=workers_lifetime,
|
||||
workers_kill_timeout=workers_kill_timeout,
|
||||
factory=factory,
|
||||
static_path_route=static_path_route,
|
||||
static_path_mount=static_path_mount,
|
||||
static_path_expires=static_path_expires,
|
||||
reload=reload,
|
||||
reload_paths=reload_paths,
|
||||
reload_ignore_paths=reload_ignore_paths,
|
||||
|
|
|
@ -107,6 +107,9 @@ class AbstractServer(Generic[WT]):
|
|||
workers_lifetime: Optional[int] = None,
|
||||
workers_kill_timeout: Optional[int] = None,
|
||||
factory: bool = False,
|
||||
static_path_route: str = '/static',
|
||||
static_path_mount: Optional[Path] = None,
|
||||
static_path_expires: int = 86400,
|
||||
reload: bool = False,
|
||||
reload_paths: Optional[Sequence[Path]] = None,
|
||||
reload_ignore_dirs: Optional[Sequence[str]] = None,
|
||||
|
@ -152,6 +155,11 @@ class AbstractServer(Generic[WT]):
|
|||
self.workers_lifetime = workers_lifetime
|
||||
self.workers_kill_timeout = workers_kill_timeout
|
||||
self.factory = factory
|
||||
self.static_path = (
|
||||
(static_path_route, str(static_path_mount.resolve()), str(static_path_expires))
|
||||
if static_path_mount
|
||||
else None
|
||||
)
|
||||
self.reload_paths = reload_paths or [Path.cwd()]
|
||||
self.reload_ignore_paths = reload_ignore_paths or ()
|
||||
self.reload_ignore_dirs = reload_ignore_dirs or ()
|
||||
|
|
|
@ -112,6 +112,9 @@ class Server(AbstractServer[AsyncWorker]):
|
|||
ssl_key_password: Optional[str] = None,
|
||||
url_path_prefix: Optional[str] = None,
|
||||
factory: bool = False,
|
||||
static_path_route: str = '/static',
|
||||
static_path_mount: Optional[Path] = None,
|
||||
static_path_expires: int = 86400,
|
||||
):
|
||||
super().__init__(
|
||||
target=target,
|
||||
|
@ -139,6 +142,9 @@ class Server(AbstractServer[AsyncWorker]):
|
|||
ssl_key_password=ssl_key_password,
|
||||
url_path_prefix=url_path_prefix,
|
||||
factory=factory,
|
||||
static_path_route=static_path_route,
|
||||
static_path_mount=static_path_mount,
|
||||
static_path_expires=static_path_expires,
|
||||
)
|
||||
self.main_loop_interrupt = asyncio.Event()
|
||||
|
||||
|
@ -164,6 +170,7 @@ class Server(AbstractServer[AsyncWorker]):
|
|||
self.http1_settings,
|
||||
self.http2_settings,
|
||||
self.websockets,
|
||||
self.static_path,
|
||||
self.log_access_format if self.log_access else None,
|
||||
self.ssl_ctx,
|
||||
{'url_path_prefix': self.url_path_prefix},
|
||||
|
@ -189,6 +196,7 @@ class Server(AbstractServer[AsyncWorker]):
|
|||
http1_settings: Optional[HTTP1Settings],
|
||||
http2_settings: Optional[HTTP2Settings],
|
||||
websockets: bool,
|
||||
static_path: Optional[Tuple[str, str, str]],
|
||||
log_access_fmt: Optional[str],
|
||||
ssl_ctx: Tuple[bool, Optional[str], Optional[str], Optional[str]],
|
||||
scope_opts: Dict[str, Any],
|
||||
|
@ -207,6 +215,7 @@ class Server(AbstractServer[AsyncWorker]):
|
|||
http1_settings,
|
||||
http2_settings,
|
||||
websockets,
|
||||
static_path,
|
||||
*ssl_ctx,
|
||||
)
|
||||
scheduler = _new_cbscheduler(loop, wcallback, impl_asyncio=task_impl == TaskImpl.asyncio)
|
||||
|
@ -230,6 +239,7 @@ class Server(AbstractServer[AsyncWorker]):
|
|||
http1_settings: Optional[HTTP1Settings],
|
||||
http2_settings: Optional[HTTP2Settings],
|
||||
websockets: bool,
|
||||
static_path: Optional[Tuple[str, str, str]],
|
||||
log_access_fmt: Optional[str],
|
||||
ssl_ctx: Tuple[bool, Optional[str], Optional[str], Optional[str]],
|
||||
scope_opts: Dict[str, Any],
|
||||
|
@ -256,6 +266,7 @@ class Server(AbstractServer[AsyncWorker]):
|
|||
http1_settings,
|
||||
http2_settings,
|
||||
websockets,
|
||||
static_path,
|
||||
*ssl_ctx,
|
||||
)
|
||||
scheduler = _new_cbscheduler(loop, wcallback, impl_asyncio=task_impl == TaskImpl.asyncio)
|
||||
|
@ -280,6 +291,7 @@ class Server(AbstractServer[AsyncWorker]):
|
|||
http1_settings: Optional[HTTP1Settings],
|
||||
http2_settings: Optional[HTTP2Settings],
|
||||
websockets: bool,
|
||||
static_path: Optional[Tuple[str, str, str]],
|
||||
log_access_fmt: Optional[str],
|
||||
ssl_ctx: Tuple[bool, Optional[str], Optional[str], Optional[str]],
|
||||
scope_opts: Dict[str, Any],
|
||||
|
@ -300,6 +312,7 @@ class Server(AbstractServer[AsyncWorker]):
|
|||
http1_settings,
|
||||
http2_settings,
|
||||
websockets,
|
||||
static_path,
|
||||
*ssl_ctx,
|
||||
)
|
||||
scheduler = _new_cbscheduler(loop, wcallback, impl_asyncio=task_impl == TaskImpl.asyncio)
|
||||
|
|
|
@ -93,6 +93,7 @@ class MPServer(AbstractServer[WorkerProcess]):
|
|||
http1_settings: Optional[HTTP1Settings],
|
||||
http2_settings: Optional[HTTP2Settings],
|
||||
websockets: bool,
|
||||
static_path: Optional[Tuple[str, str, str]],
|
||||
log_access_fmt: Optional[str],
|
||||
ssl_ctx: Tuple[bool, Optional[str], Optional[str], Optional[str]],
|
||||
scope_opts: Dict[str, Any],
|
||||
|
@ -114,6 +115,7 @@ class MPServer(AbstractServer[WorkerProcess]):
|
|||
http1_settings,
|
||||
http2_settings,
|
||||
websockets,
|
||||
static_path,
|
||||
*ssl_ctx,
|
||||
)
|
||||
serve = getattr(worker, {RuntimeModes.mt: 'serve_mtr', RuntimeModes.st: 'serve_str'}[runtime_mode])
|
||||
|
@ -138,6 +140,7 @@ class MPServer(AbstractServer[WorkerProcess]):
|
|||
http1_settings: Optional[HTTP1Settings],
|
||||
http2_settings: Optional[HTTP2Settings],
|
||||
websockets: bool,
|
||||
static_path: Optional[Tuple[str, str, str]],
|
||||
log_access_fmt: Optional[str],
|
||||
ssl_ctx: Tuple[bool, Optional[str], Optional[str], Optional[str]],
|
||||
scope_opts: Dict[str, Any],
|
||||
|
@ -167,6 +170,7 @@ class MPServer(AbstractServer[WorkerProcess]):
|
|||
http1_settings,
|
||||
http2_settings,
|
||||
websockets,
|
||||
static_path,
|
||||
*ssl_ctx,
|
||||
)
|
||||
serve = getattr(worker, {RuntimeModes.mt: 'serve_mtr', RuntimeModes.st: 'serve_str'}[runtime_mode])
|
||||
|
@ -192,6 +196,7 @@ class MPServer(AbstractServer[WorkerProcess]):
|
|||
http1_settings: Optional[HTTP1Settings],
|
||||
http2_settings: Optional[HTTP2Settings],
|
||||
websockets: bool,
|
||||
static_path: Optional[Tuple[str, str, str]],
|
||||
log_access_fmt: Optional[str],
|
||||
ssl_ctx: Tuple[bool, Optional[str], Optional[str], Optional[str]],
|
||||
scope_opts: Dict[str, Any],
|
||||
|
@ -215,6 +220,7 @@ class MPServer(AbstractServer[WorkerProcess]):
|
|||
http1_settings,
|
||||
http2_settings,
|
||||
websockets,
|
||||
static_path,
|
||||
*ssl_ctx,
|
||||
)
|
||||
serve = getattr(worker, {RuntimeModes.mt: 'serve_mtr', RuntimeModes.st: 'serve_str'}[runtime_mode])
|
||||
|
@ -240,6 +246,7 @@ class MPServer(AbstractServer[WorkerProcess]):
|
|||
http1_settings: Optional[HTTP1Settings],
|
||||
http2_settings: Optional[HTTP2Settings],
|
||||
websockets: bool,
|
||||
static_path: Optional[Tuple[str, str, str]],
|
||||
log_access_fmt: Optional[str],
|
||||
ssl_ctx: Tuple[bool, Optional[str], Optional[str], Optional[str]],
|
||||
scope_opts: Dict[str, Any],
|
||||
|
@ -260,6 +267,7 @@ class MPServer(AbstractServer[WorkerProcess]):
|
|||
http_mode,
|
||||
http1_settings,
|
||||
http2_settings,
|
||||
static_path,
|
||||
*ssl_ctx,
|
||||
)
|
||||
serve = getattr(worker, {RuntimeModes.mt: 'serve_mtr', RuntimeModes.st: 'serve_str'}[runtime_mode])
|
||||
|
@ -304,6 +312,7 @@ class MPServer(AbstractServer[WorkerProcess]):
|
|||
self.http1_settings,
|
||||
self.http2_settings,
|
||||
self.websockets,
|
||||
self.static_path,
|
||||
self.log_access_format if self.log_access else None,
|
||||
self.ssl_ctx,
|
||||
{'url_path_prefix': self.url_path_prefix},
|
||||
|
|
|
@ -85,6 +85,7 @@ class MTServer(AbstractServer[WorkerThread]):
|
|||
http1_settings: Optional[HTTP1Settings],
|
||||
http2_settings: Optional[HTTP2Settings],
|
||||
websockets: bool,
|
||||
static_path: Optional[Tuple[str, str, str]],
|
||||
log_access_fmt: Optional[str],
|
||||
ssl_ctx: Tuple[bool, Optional[str], Optional[str], Optional[str]],
|
||||
scope_opts: Dict[str, Any],
|
||||
|
@ -103,6 +104,7 @@ class MTServer(AbstractServer[WorkerThread]):
|
|||
http1_settings,
|
||||
http2_settings,
|
||||
websockets,
|
||||
static_path,
|
||||
*ssl_ctx,
|
||||
)
|
||||
serve = getattr(worker, {RuntimeModes.mt: 'serve_mtr', RuntimeModes.st: 'serve_str'}[runtime_mode])
|
||||
|
@ -128,6 +130,7 @@ class MTServer(AbstractServer[WorkerThread]):
|
|||
http1_settings: Optional[HTTP1Settings],
|
||||
http2_settings: Optional[HTTP2Settings],
|
||||
websockets: bool,
|
||||
static_path: Optional[Tuple[str, str, str]],
|
||||
log_access_fmt: Optional[str],
|
||||
ssl_ctx: Tuple[bool, Optional[str], Optional[str], Optional[str]],
|
||||
scope_opts: Dict[str, Any],
|
||||
|
@ -154,6 +157,7 @@ class MTServer(AbstractServer[WorkerThread]):
|
|||
http1_settings,
|
||||
http2_settings,
|
||||
websockets,
|
||||
static_path,
|
||||
*ssl_ctx,
|
||||
)
|
||||
serve = getattr(worker, {RuntimeModes.mt: 'serve_mtr', RuntimeModes.st: 'serve_str'}[runtime_mode])
|
||||
|
@ -180,6 +184,7 @@ class MTServer(AbstractServer[WorkerThread]):
|
|||
http1_settings: Optional[HTTP1Settings],
|
||||
http2_settings: Optional[HTTP2Settings],
|
||||
websockets: bool,
|
||||
static_path: Optional[Tuple[str, str, str]],
|
||||
log_access_fmt: Optional[str],
|
||||
ssl_ctx: Tuple[bool, Optional[str], Optional[str], Optional[str]],
|
||||
scope_opts: Dict[str, Any],
|
||||
|
@ -200,6 +205,7 @@ class MTServer(AbstractServer[WorkerThread]):
|
|||
http1_settings,
|
||||
http2_settings,
|
||||
websockets,
|
||||
static_path,
|
||||
*ssl_ctx,
|
||||
)
|
||||
serve = getattr(worker, {RuntimeModes.mt: 'serve_mtr', RuntimeModes.st: 'serve_str'}[runtime_mode])
|
||||
|
@ -226,6 +232,7 @@ class MTServer(AbstractServer[WorkerThread]):
|
|||
http1_settings: Optional[HTTP1Settings],
|
||||
http2_settings: Optional[HTTP2Settings],
|
||||
websockets: bool,
|
||||
static_path: Optional[Tuple[str, str, str]],
|
||||
log_access_fmt: Optional[str],
|
||||
ssl_ctx: Tuple[bool, Optional[str], Optional[str], Optional[str]],
|
||||
scope_opts: Dict[str, Any],
|
||||
|
@ -243,6 +250,7 @@ class MTServer(AbstractServer[WorkerThread]):
|
|||
http_mode,
|
||||
http1_settings,
|
||||
http2_settings,
|
||||
static_path,
|
||||
*ssl_ctx,
|
||||
)
|
||||
serve = getattr(worker, {RuntimeModes.mt: 'serve_mtr', RuntimeModes.st: 'serve_str'}[runtime_mode])
|
||||
|
@ -273,6 +281,7 @@ class MTServer(AbstractServer[WorkerThread]):
|
|||
self.http1_settings,
|
||||
self.http2_settings,
|
||||
self.websockets,
|
||||
self.static_path,
|
||||
self.log_access_format if self.log_access else None,
|
||||
self.ssl_ctx,
|
||||
{'url_path_prefix': self.url_path_prefix},
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use futures::FutureExt;
|
||||
use pyo3::prelude::*;
|
||||
|
||||
use super::http::{handle, handle_ws};
|
||||
|
@ -5,9 +6,7 @@ use super::http::{handle, handle_ws};
|
|||
use crate::callbacks::CallbackScheduler;
|
||||
use crate::conversion::{worker_http1_config_from_py, worker_http2_config_from_py};
|
||||
use crate::tcp::ListenerSpec;
|
||||
use crate::workers::{
|
||||
serve_fut, serve_fut_ssl, serve_mtr, serve_mtr_ssl, serve_str, serve_str_ssl, WorkerConfig, WorkerSignal,
|
||||
};
|
||||
use crate::workers::{gen_serve_match, gen_serve_methods, WorkerConfig, WorkerSignal};
|
||||
|
||||
#[pyclass(frozen, module = "granian._granian")]
|
||||
pub struct ASGIWorker {
|
||||
|
@ -15,18 +14,8 @@ pub struct ASGIWorker {
|
|||
}
|
||||
|
||||
impl ASGIWorker {
|
||||
serve_mtr!(_serve_mtr, handle);
|
||||
serve_mtr!(_serve_mtr_ws, handle_ws);
|
||||
serve_str!(_serve_str, handle);
|
||||
serve_str!(_serve_str_ws, handle_ws);
|
||||
serve_fut!(_serve_fut, handle);
|
||||
serve_fut!(_serve_fut_ws, handle_ws);
|
||||
serve_mtr_ssl!(_serve_mtr_ssl, handle);
|
||||
serve_mtr_ssl!(_serve_mtr_ssl_ws, handle_ws);
|
||||
serve_str_ssl!(_serve_str_ssl, handle);
|
||||
serve_str_ssl!(_serve_str_ssl_ws, handle_ws);
|
||||
serve_fut_ssl!(_serve_fut_ssl, handle);
|
||||
serve_fut_ssl!(_serve_fut_ssl_ws, handle_ws);
|
||||
gen_serve_methods!(handle);
|
||||
gen_serve_methods!(ws handle_ws);
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
|
@ -45,6 +34,7 @@ impl ASGIWorker {
|
|||
http1_opts=None,
|
||||
http2_opts=None,
|
||||
websockets_enabled=false,
|
||||
static_files=None,
|
||||
ssl_enabled=false,
|
||||
ssl_cert=None,
|
||||
ssl_key=None,
|
||||
|
@ -64,6 +54,7 @@ impl ASGIWorker {
|
|||
http1_opts: Option<PyObject>,
|
||||
http2_opts: Option<PyObject>,
|
||||
websockets_enabled: bool,
|
||||
static_files: Option<(String, String, String)>,
|
||||
ssl_enabled: bool,
|
||||
ssl_cert: Option<&str>,
|
||||
ssl_key: Option<&str>,
|
||||
|
@ -82,6 +73,7 @@ impl ASGIWorker {
|
|||
worker_http1_config_from_py(py, http1_opts)?,
|
||||
worker_http2_config_from_py(py, http2_opts)?,
|
||||
websockets_enabled,
|
||||
static_files,
|
||||
ssl_enabled,
|
||||
ssl_cert,
|
||||
ssl_key,
|
||||
|
@ -97,21 +89,11 @@ impl ASGIWorker {
|
|||
event_loop: &Bound<PyAny>,
|
||||
signal: Py<WorkerSignal>,
|
||||
) {
|
||||
match (self.config.websockets_enabled, self.config.ssl_enabled) {
|
||||
(false, false) => self._serve_mtr(py, callback, event_loop, signal),
|
||||
(true, false) => self._serve_mtr_ws(py, callback, event_loop, signal),
|
||||
(false, true) => self._serve_mtr_ssl(py, callback, event_loop, signal),
|
||||
(true, true) => self._serve_mtr_ssl_ws(py, callback, event_loop, signal),
|
||||
}
|
||||
gen_serve_match!(mtr self, py, callback, event_loop, signal);
|
||||
}
|
||||
|
||||
fn serve_str(&self, callback: Py<CallbackScheduler>, event_loop: &Bound<PyAny>, signal: Py<WorkerSignal>) {
|
||||
match (self.config.websockets_enabled, self.config.ssl_enabled) {
|
||||
(false, false) => self._serve_str(callback, event_loop, signal),
|
||||
(true, false) => self._serve_str_ws(callback, event_loop, signal),
|
||||
(false, true) => self._serve_str_ssl(callback, event_loop, signal),
|
||||
(true, true) => self._serve_str_ssl_ws(callback, event_loop, signal),
|
||||
}
|
||||
gen_serve_match!(str self, callback, event_loop, signal);
|
||||
}
|
||||
|
||||
fn serve_async<'p>(
|
||||
|
@ -120,11 +102,6 @@ impl ASGIWorker {
|
|||
event_loop: &Bound<'p, PyAny>,
|
||||
signal: Py<WorkerSignal>,
|
||||
) -> Bound<'p, PyAny> {
|
||||
match (self.config.websockets_enabled, self.config.ssl_enabled) {
|
||||
(false, false) => self._serve_fut(callback, event_loop, signal),
|
||||
(true, false) => self._serve_fut_ws(callback, event_loop, signal),
|
||||
(false, true) => self._serve_fut_ssl(callback, event_loop, signal),
|
||||
(true, true) => self._serve_fut_ssl_ws(callback, event_loop, signal),
|
||||
}
|
||||
gen_serve_match!(fut self, callback, event_loop, signal)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@ use tokio::sync::Notify;
|
|||
|
||||
use crate::{asyncio::copy_context, conversion::FutureResultToPy};
|
||||
|
||||
pub(crate) type ArcCBScheduler = Arc<Py<CallbackScheduler>>;
|
||||
pub(crate) type PyCBScheduler = Py<CallbackScheduler>;
|
||||
pub(crate) type ArcCBScheduler = Arc<PyCBScheduler>;
|
||||
|
||||
#[pyclass(frozen, subclass, module = "granian._granian")]
|
||||
pub(crate) struct CallbackScheduler {
|
||||
|
|
71
src/files.rs
Normal file
71
src/files.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use anyhow::Result;
|
||||
use futures::TryStreamExt;
|
||||
use http_body_util::BodyExt;
|
||||
use hyper::{
|
||||
header::{HeaderValue, SERVER as HK_SERVER},
|
||||
HeaderMap, StatusCode,
|
||||
};
|
||||
use std::{io, path::Path};
|
||||
use tokio::fs::File;
|
||||
use tokio_util::io::ReaderStream;
|
||||
|
||||
use crate::http::{response_404, HTTPResponse, HV_SERVER};
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn match_static_file(uri_path: &str, prefix: &str, mount_point: &str) -> Option<Result<String>> {
|
||||
if let Some(file_path) = uri_path.strip_prefix(prefix) {
|
||||
#[cfg(not(windows))]
|
||||
let fpath = format!("{mount_point}{file_path}");
|
||||
#[cfg(windows)]
|
||||
let fpath = format!("{mount_point}{}", file_path.replace("/", "\\"));
|
||||
match Path::new(&fpath).canonicalize() {
|
||||
Ok(full_path) => {
|
||||
#[cfg(windows)]
|
||||
let full_path = &full_path.display().to_string()[4..];
|
||||
if full_path.starts_with(mount_point) {
|
||||
#[cfg(not(windows))]
|
||||
return full_path.to_str().map(ToOwned::to_owned).map(Ok);
|
||||
#[cfg(windows)]
|
||||
return Some(Ok(full_path.to_owned()));
|
||||
}
|
||||
return Some(Err(anyhow::anyhow!("outside mount path")));
|
||||
}
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => {
|
||||
return Some(Err(err.into()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) async fn serve_static_file(path: String, expires: String) -> HTTPResponse {
|
||||
match File::open(&path).await {
|
||||
Ok(file) => {
|
||||
let mime = mime_guess::from_path(path).first();
|
||||
let stream = ReaderStream::with_capacity(file, 131_072);
|
||||
let stream_body = http_body_util::StreamBody::new(stream.map_ok(hyper::body::Frame::data));
|
||||
let mut headers = HeaderMap::new();
|
||||
let mut res = hyper::Response::new(BodyExt::map_err(stream_body, std::convert::Into::into).boxed());
|
||||
|
||||
headers.insert(HK_SERVER, HV_SERVER);
|
||||
headers.insert(
|
||||
"cache-control",
|
||||
HeaderValue::from_str(&format!("max-age={expires}")).unwrap(),
|
||||
);
|
||||
if let Some(mime) = mime {
|
||||
if let Ok(hv) = HeaderValue::from_str(mime.essence_str()) {
|
||||
headers.insert("content-type", hv);
|
||||
}
|
||||
}
|
||||
|
||||
*res.status_mut() = StatusCode::from_u16(200).unwrap();
|
||||
*res.headers_mut() = headers;
|
||||
res
|
||||
}
|
||||
Err(_) => {
|
||||
log::info!("Request static file {path} not found");
|
||||
response_404()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ mod asyncio;
|
|||
mod blocking;
|
||||
mod callbacks;
|
||||
mod conversion;
|
||||
mod files;
|
||||
mod http;
|
||||
mod io;
|
||||
mod rsgi;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use futures::FutureExt;
|
||||
use pyo3::prelude::*;
|
||||
|
||||
use super::http::{handle, handle_ws};
|
||||
|
@ -5,9 +6,7 @@ use super::http::{handle, handle_ws};
|
|||
use crate::callbacks::CallbackScheduler;
|
||||
use crate::conversion::{worker_http1_config_from_py, worker_http2_config_from_py};
|
||||
use crate::tcp::ListenerSpec;
|
||||
use crate::workers::{
|
||||
serve_fut, serve_fut_ssl, serve_mtr, serve_mtr_ssl, serve_str, serve_str_ssl, WorkerConfig, WorkerSignal,
|
||||
};
|
||||
use crate::workers::{gen_serve_match, gen_serve_methods, WorkerConfig, WorkerSignal};
|
||||
|
||||
#[pyclass(frozen, module = "granian._granian")]
|
||||
pub struct RSGIWorker {
|
||||
|
@ -15,18 +14,8 @@ pub struct RSGIWorker {
|
|||
}
|
||||
|
||||
impl RSGIWorker {
|
||||
serve_mtr!(_serve_mtr, handle);
|
||||
serve_mtr!(_serve_mtr_ws, handle_ws);
|
||||
serve_str!(_serve_str, handle);
|
||||
serve_str!(_serve_str_ws, handle_ws);
|
||||
serve_fut!(_serve_fut, handle);
|
||||
serve_fut!(_serve_fut_ws, handle_ws);
|
||||
serve_mtr_ssl!(_serve_mtr_ssl, handle);
|
||||
serve_mtr_ssl!(_serve_mtr_ssl_ws, handle_ws);
|
||||
serve_str_ssl!(_serve_str_ssl, handle);
|
||||
serve_str_ssl!(_serve_str_ssl_ws, handle_ws);
|
||||
serve_fut_ssl!(_serve_fut_ssl, handle);
|
||||
serve_fut_ssl!(_serve_fut_ssl_ws, handle_ws);
|
||||
gen_serve_methods!(handle);
|
||||
gen_serve_methods!(ws handle_ws);
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
|
@ -45,6 +34,7 @@ impl RSGIWorker {
|
|||
http1_opts=None,
|
||||
http2_opts=None,
|
||||
websockets_enabled=false,
|
||||
static_files=None,
|
||||
ssl_enabled=false,
|
||||
ssl_cert=None,
|
||||
ssl_key=None,
|
||||
|
@ -64,6 +54,7 @@ impl RSGIWorker {
|
|||
http1_opts: Option<PyObject>,
|
||||
http2_opts: Option<PyObject>,
|
||||
websockets_enabled: bool,
|
||||
static_files: Option<(String, String, String)>,
|
||||
ssl_enabled: bool,
|
||||
ssl_cert: Option<&str>,
|
||||
ssl_key: Option<&str>,
|
||||
|
@ -82,6 +73,7 @@ impl RSGIWorker {
|
|||
worker_http1_config_from_py(py, http1_opts)?,
|
||||
worker_http2_config_from_py(py, http2_opts)?,
|
||||
websockets_enabled,
|
||||
static_files,
|
||||
ssl_enabled,
|
||||
ssl_cert,
|
||||
ssl_key,
|
||||
|
@ -97,21 +89,11 @@ impl RSGIWorker {
|
|||
event_loop: &Bound<PyAny>,
|
||||
signal: Py<WorkerSignal>,
|
||||
) {
|
||||
match (self.config.websockets_enabled, self.config.ssl_enabled) {
|
||||
(false, false) => self._serve_mtr(py, callback, event_loop, signal),
|
||||
(true, false) => self._serve_mtr_ws(py, callback, event_loop, signal),
|
||||
(false, true) => self._serve_mtr_ssl(py, callback, event_loop, signal),
|
||||
(true, true) => self._serve_mtr_ssl_ws(py, callback, event_loop, signal),
|
||||
}
|
||||
gen_serve_match!(mtr self, py, callback, event_loop, signal);
|
||||
}
|
||||
|
||||
fn serve_str(&self, callback: Py<CallbackScheduler>, event_loop: &Bound<PyAny>, signal: Py<WorkerSignal>) {
|
||||
match (self.config.websockets_enabled, self.config.ssl_enabled) {
|
||||
(false, false) => self._serve_str(callback, event_loop, signal),
|
||||
(true, false) => self._serve_str_ws(callback, event_loop, signal),
|
||||
(false, true) => self._serve_str_ssl(callback, event_loop, signal),
|
||||
(true, true) => self._serve_str_ssl_ws(callback, event_loop, signal),
|
||||
}
|
||||
gen_serve_match!(str self, callback, event_loop, signal);
|
||||
}
|
||||
|
||||
fn serve_async<'p>(
|
||||
|
@ -120,11 +102,6 @@ impl RSGIWorker {
|
|||
event_loop: &Bound<'p, PyAny>,
|
||||
signal: Py<WorkerSignal>,
|
||||
) -> Bound<'p, PyAny> {
|
||||
match (self.config.websockets_enabled, self.config.ssl_enabled) {
|
||||
(false, false) => self._serve_fut(callback, event_loop, signal),
|
||||
(true, false) => self._serve_fut_ws(callback, event_loop, signal),
|
||||
(false, true) => self._serve_fut_ssl(callback, event_loop, signal),
|
||||
(true, true) => self._serve_fut_ssl_ws(callback, event_loop, signal),
|
||||
}
|
||||
gen_serve_match!(fut self, callback, event_loop, signal)
|
||||
}
|
||||
}
|
||||
|
|
1923
src/workers.rs
1923
src/workers.rs
File diff suppressed because it is too large
Load diff
|
@ -1,3 +1,4 @@
|
|||
use futures::FutureExt;
|
||||
use pyo3::prelude::*;
|
||||
|
||||
use super::http::handle;
|
||||
|
@ -12,230 +13,559 @@ pub struct WSGIWorker {
|
|||
config: WorkerConfig,
|
||||
}
|
||||
|
||||
macro_rules! serve_mtr {
|
||||
($func_name:ident, $http_mode:tt, $conn_method:ident, $ctx:ty, $svc:ident) => {
|
||||
fn $func_name(
|
||||
&self,
|
||||
py: Python,
|
||||
callback: Py<crate::callbacks::CallbackScheduler>,
|
||||
event_loop: &Bound<PyAny>,
|
||||
signal: Py<WorkerSignalSync>,
|
||||
) {
|
||||
_ = pyo3_log::try_init();
|
||||
|
||||
let worker_id = self.config.id;
|
||||
log::info!("Started worker-{}", worker_id);
|
||||
|
||||
let tcp_listener = self.config.tcp_listener();
|
||||
#[allow(unused_variables)]
|
||||
let http1_opts = self.config.http1_opts.clone();
|
||||
#[allow(unused_variables)]
|
||||
let http2_opts = self.config.http2_opts.clone();
|
||||
let backpressure = self.config.backpressure.clone();
|
||||
|
||||
let ctxw: Box<dyn crate::workers::WorkerCTX<CTX=$ctx>> = Box::new(crate::workers::Worker::new(<$ctx>::new(callback, self.config.static_files.clone())));
|
||||
let ctx = ctxw.get_ctx();
|
||||
|
||||
let rtpyloop = std::sync::Arc::new(event_loop.clone().unbind());
|
||||
let rt = py.allow_threads(|| crate::runtime::init_runtime_mt(
|
||||
self.config.threads,
|
||||
self.config.blocking_threads,
|
||||
self.config.py_threads,
|
||||
self.config.py_threads_idle_timeout,
|
||||
rtpyloop,
|
||||
));
|
||||
let rth = rt.handler();
|
||||
let tasks = tokio_util::task::TaskTracker::new();
|
||||
let (stx, mut srx) = tokio::sync::watch::channel(false);
|
||||
|
||||
let main_loop = rt.inner.spawn(async move {
|
||||
crate::workers::gen_accept!(
|
||||
plain
|
||||
$http_mode
|
||||
$conn_method,
|
||||
ctx,
|
||||
handle,
|
||||
$svc,
|
||||
tcp_listener,
|
||||
srx,
|
||||
backpressure,
|
||||
rth,
|
||||
|task| tasks.spawn(task),
|
||||
hyper_util::rt::TokioExecutor::new,
|
||||
http1_opts,
|
||||
http2_opts,
|
||||
hyper_util::rt::TokioIo::new
|
||||
);
|
||||
|
||||
log::info!("Stopping worker-{}", worker_id);
|
||||
|
||||
tasks.close();
|
||||
tasks.wait().await;
|
||||
|
||||
Python::with_gil(|_| drop(ctx));
|
||||
});
|
||||
|
||||
let pysig = signal.clone_ref(py);
|
||||
std::thread::spawn(move || {
|
||||
let pyrx = pysig.get().rx.lock().unwrap().take().unwrap();
|
||||
_ = pyrx.recv();
|
||||
stx.send(true).unwrap();
|
||||
|
||||
while !main_loop.is_finished() {
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
_ = pysig.get().release(py);
|
||||
drop(pysig);
|
||||
});
|
||||
});
|
||||
|
||||
_ = signal.get().qs.call_method0(py, pyo3::intern!(py, "wait"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! serve_mtr_ssl {
|
||||
($func_name:ident, $http_mode:tt, $conn_method:ident, $ctx:ty, $svc:ident) => {
|
||||
fn $func_name(
|
||||
&self,
|
||||
py: Python,
|
||||
callback: Py<crate::callbacks::CallbackScheduler>,
|
||||
event_loop: &Bound<PyAny>,
|
||||
signal: Py<WorkerSignalSync>,
|
||||
) {
|
||||
_ = pyo3_log::try_init();
|
||||
|
||||
let worker_id = self.config.id;
|
||||
log::info!("Started worker-{}", worker_id);
|
||||
|
||||
let tcp_listener = self.config.tcp_listener();
|
||||
#[allow(unused_variables)]
|
||||
let http1_opts = self.config.http1_opts.clone();
|
||||
#[allow(unused_variables)]
|
||||
let http2_opts = self.config.http2_opts.clone();
|
||||
let backpressure = self.config.backpressure.clone();
|
||||
let tls_cfg = self.config.tls_cfg();
|
||||
|
||||
let ctxw: Box<dyn crate::workers::WorkerCTX<CTX=$ctx>> = Box::new(crate::workers::Worker::new(<$ctx>::new(callback, self.config.static_files.clone())));
|
||||
let ctx = ctxw.get_ctx();
|
||||
|
||||
let rtpyloop = std::sync::Arc::new(event_loop.clone().unbind());
|
||||
let rt = py.allow_threads(|| crate::runtime::init_runtime_mt(
|
||||
self.config.threads,
|
||||
self.config.blocking_threads,
|
||||
self.config.py_threads,
|
||||
self.config.py_threads_idle_timeout,
|
||||
rtpyloop,
|
||||
));
|
||||
let rth = rt.handler();
|
||||
let tasks = tokio_util::task::TaskTracker::new();
|
||||
let (stx, mut srx) = tokio::sync::watch::channel(false);
|
||||
|
||||
let main_loop = rt.inner.spawn(async move {
|
||||
crate::workers::gen_accept!(
|
||||
tls
|
||||
$http_mode
|
||||
$conn_method,
|
||||
tls_cfg,
|
||||
ctx,
|
||||
handle,
|
||||
$svc,
|
||||
tcp_listener,
|
||||
srx,
|
||||
backpressure,
|
||||
rth,
|
||||
|task| tasks.spawn(task),
|
||||
hyper_util::rt::TokioExecutor::new,
|
||||
http1_opts,
|
||||
http2_opts,
|
||||
hyper_util::rt::TokioIo::new
|
||||
);
|
||||
|
||||
log::info!("Stopping worker-{}", worker_id);
|
||||
|
||||
tasks.close();
|
||||
tasks.wait().await;
|
||||
|
||||
Python::with_gil(|_| drop(ctx));
|
||||
});
|
||||
|
||||
let pysig = signal.clone_ref(py);
|
||||
std::thread::spawn(move || {
|
||||
let pyrx = pysig.get().rx.lock().unwrap().take().unwrap();
|
||||
_ = pyrx.recv();
|
||||
stx.send(true).unwrap();
|
||||
|
||||
while !main_loop.is_finished() {
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
_ = pysig.get().release(py);
|
||||
drop(pysig);
|
||||
});
|
||||
});
|
||||
|
||||
_ = signal.get().qs.call_method0(py, pyo3::intern!(py, "wait"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! serve_str {
|
||||
($func_name:ident, $http_mode:tt, $conn_method:ident, $ctx:ty, $svc:ident) => {
|
||||
fn $func_name(
|
||||
&self,
|
||||
py: Python,
|
||||
callback: Py<crate::callbacks::CallbackScheduler>,
|
||||
event_loop: &Bound<PyAny>,
|
||||
signal: Py<WorkerSignalSync>,
|
||||
) {
|
||||
_ = pyo3_log::try_init();
|
||||
|
||||
let worker_id = self.config.id;
|
||||
log::info!("Started worker-{}", worker_id);
|
||||
|
||||
let (stx, srx) = tokio::sync::watch::channel(false);
|
||||
let mut workers = vec![];
|
||||
crate::workers::serve_str_inner!(
|
||||
$http_mode,
|
||||
$conn_method,
|
||||
handle,
|
||||
$ctx,
|
||||
$svc,
|
||||
self,
|
||||
callback,
|
||||
event_loop,
|
||||
worker_id,
|
||||
workers,
|
||||
srx
|
||||
);
|
||||
|
||||
let pysig = signal.clone_ref(py);
|
||||
std::thread::spawn(move || {
|
||||
let pyrx = pysig.get().rx.lock().unwrap().take().unwrap();
|
||||
_ = pyrx.recv();
|
||||
stx.send(true).unwrap();
|
||||
log::info!("Stopping worker-{worker_id}");
|
||||
while let Some(worker) = workers.pop() {
|
||||
worker.join().unwrap();
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
_ = pysig.get().release(py);
|
||||
drop(pysig);
|
||||
});
|
||||
});
|
||||
|
||||
_ = signal.get().qs.call_method0(py, pyo3::intern!(py, "wait"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! serve_str_ssl {
|
||||
($func_name:ident, $http_mode:tt, $conn_method:ident, $ctx:ty, $svc:ident) => {
|
||||
fn $func_name(
|
||||
&self,
|
||||
py: Python,
|
||||
callback: Py<crate::callbacks::CallbackScheduler>,
|
||||
event_loop: &Bound<PyAny>,
|
||||
signal: Py<WorkerSignalSync>,
|
||||
) {
|
||||
_ = pyo3_log::try_init();
|
||||
|
||||
let worker_id = self.config.id;
|
||||
log::info!("Started worker-{}", worker_id);
|
||||
|
||||
let (stx, srx) = tokio::sync::watch::channel(false);
|
||||
let mut workers = vec![];
|
||||
crate::workers::serve_str_ssl_inner!(
|
||||
$http_mode,
|
||||
$conn_method,
|
||||
handle,
|
||||
$ctx,
|
||||
$svc,
|
||||
self,
|
||||
callback,
|
||||
event_loop,
|
||||
worker_id,
|
||||
workers,
|
||||
srx
|
||||
);
|
||||
|
||||
let pysig = signal.clone_ref(py);
|
||||
std::thread::spawn(move || {
|
||||
let pyrx = pysig.get().rx.lock().unwrap().take().unwrap();
|
||||
_ = pyrx.recv();
|
||||
stx.send(true).unwrap();
|
||||
log::info!("Stopping worker-{worker_id}");
|
||||
while let Some(worker) = workers.pop() {
|
||||
worker.join().unwrap();
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
_ = pysig.get().release(py);
|
||||
drop(pysig);
|
||||
});
|
||||
});
|
||||
|
||||
_ = signal.get().qs.call_method0(py, pyo3::intern!(py, "wait"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl WSGIWorker {
|
||||
fn _serve_mtr(
|
||||
&self,
|
||||
py: Python,
|
||||
callback: Py<CallbackScheduler>,
|
||||
event_loop: &Bound<PyAny>,
|
||||
signal: Py<WorkerSignalSync>,
|
||||
) {
|
||||
_ = pyo3_log::try_init();
|
||||
|
||||
let worker_id = self.config.id;
|
||||
log::info!("Started worker-{worker_id}");
|
||||
|
||||
let tcp_listener = self.config.tcp_listener();
|
||||
let http_mode = self.config.http_mode.clone();
|
||||
let http_upgrades = self.config.websockets_enabled;
|
||||
let http1_opts = self.config.http1_opts.clone();
|
||||
let http2_opts = self.config.http2_opts.clone();
|
||||
let backpressure = self.config.backpressure;
|
||||
let callback_wrapper = std::sync::Arc::new(callback);
|
||||
let rtpyloop = std::sync::Arc::new(event_loop.clone().unbind());
|
||||
|
||||
let rt = py.allow_threads(|| {
|
||||
crate::runtime::init_runtime_mt(
|
||||
self.config.threads,
|
||||
self.config.blocking_threads,
|
||||
self.config.py_threads,
|
||||
self.config.py_threads_idle_timeout,
|
||||
rtpyloop,
|
||||
)
|
||||
});
|
||||
let rth = rt.handler();
|
||||
let tasks = tokio_util::task::TaskTracker::new();
|
||||
|
||||
let (stx, mut srx) = tokio::sync::watch::channel(false);
|
||||
let main_loop = rt.inner.spawn(async move {
|
||||
crate::workers::loop_match!(
|
||||
http_mode,
|
||||
http_upgrades,
|
||||
tcp_listener,
|
||||
srx,
|
||||
backpressure,
|
||||
rth,
|
||||
callback_wrapper,
|
||||
|task| tasks.spawn(task),
|
||||
hyper_util::rt::TokioExecutor::new,
|
||||
http1_opts,
|
||||
http2_opts,
|
||||
hyper_util::rt::TokioIo::new,
|
||||
handle
|
||||
);
|
||||
|
||||
log::info!("Stopping worker-{worker_id}");
|
||||
|
||||
tasks.close();
|
||||
tasks.wait().await;
|
||||
|
||||
Python::with_gil(|_| drop(callback_wrapper));
|
||||
});
|
||||
|
||||
let pysig = signal.clone_ref(py);
|
||||
std::thread::spawn(move || {
|
||||
let pyrx = pysig.get().rx.lock().unwrap().take().unwrap();
|
||||
_ = pyrx.recv();
|
||||
stx.send(true).unwrap();
|
||||
|
||||
while !main_loop.is_finished() {
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
_ = pysig.get().release(py);
|
||||
drop(pysig);
|
||||
});
|
||||
});
|
||||
|
||||
_ = signal.get().qs.call_method0(py, pyo3::intern!(py, "wait"));
|
||||
}
|
||||
|
||||
fn _serve_str(
|
||||
&self,
|
||||
py: Python,
|
||||
callback: Py<CallbackScheduler>,
|
||||
event_loop: &Bound<PyAny>,
|
||||
signal: Py<WorkerSignalSync>,
|
||||
) {
|
||||
_ = pyo3_log::try_init();
|
||||
|
||||
let worker_id = self.config.id;
|
||||
log::info!("Started worker-{worker_id}");
|
||||
|
||||
let (stx, srx) = tokio::sync::watch::channel(false);
|
||||
let mut workers = vec![];
|
||||
crate::workers::serve_str_inner!(self, handle, callback, event_loop, worker_id, workers, srx);
|
||||
|
||||
let pysig = signal.clone_ref(py);
|
||||
std::thread::spawn(move || {
|
||||
let pyrx = pysig.get().rx.lock().unwrap().take().unwrap();
|
||||
_ = pyrx.recv();
|
||||
stx.send(true).unwrap();
|
||||
log::info!("Stopping worker-{worker_id}");
|
||||
while let Some(worker) = workers.pop() {
|
||||
worker.join().unwrap();
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
_ = pysig.get().release(py);
|
||||
drop(pysig);
|
||||
});
|
||||
});
|
||||
|
||||
_ = signal.get().qs.call_method0(py, pyo3::intern!(py, "wait"));
|
||||
}
|
||||
|
||||
fn _serve_mtr_ssl(
|
||||
&self,
|
||||
py: Python,
|
||||
callback: Py<CallbackScheduler>,
|
||||
event_loop: &Bound<PyAny>,
|
||||
signal: Py<WorkerSignalSync>,
|
||||
) {
|
||||
_ = pyo3_log::try_init();
|
||||
|
||||
let worker_id = self.config.id;
|
||||
log::info!("Started worker-{worker_id}");
|
||||
|
||||
let tcp_listener = self.config.tcp_listener();
|
||||
let http_mode = self.config.http_mode.clone();
|
||||
let http_upgrades = self.config.websockets_enabled;
|
||||
let http1_opts = self.config.http1_opts.clone();
|
||||
let http2_opts = self.config.http2_opts.clone();
|
||||
let backpressure = self.config.backpressure;
|
||||
let tls_cfg = self.config.tls_cfg();
|
||||
let callback_wrapper = std::sync::Arc::new(callback);
|
||||
let rtpyloop = std::sync::Arc::new(event_loop.clone().unbind());
|
||||
|
||||
let rt = py.allow_threads(|| {
|
||||
crate::runtime::init_runtime_mt(
|
||||
self.config.threads,
|
||||
self.config.blocking_threads,
|
||||
self.config.py_threads,
|
||||
self.config.py_threads_idle_timeout,
|
||||
rtpyloop,
|
||||
)
|
||||
});
|
||||
let rth = rt.handler();
|
||||
let tasks = tokio_util::task::TaskTracker::new();
|
||||
|
||||
let (stx, mut srx) = tokio::sync::watch::channel(false);
|
||||
rt.inner.spawn(async move {
|
||||
crate::workers::loop_match_tls!(
|
||||
http_mode,
|
||||
http_upgrades,
|
||||
tcp_listener,
|
||||
tls_cfg,
|
||||
srx,
|
||||
backpressure,
|
||||
rth,
|
||||
callback_wrapper,
|
||||
|task| tasks.spawn(task),
|
||||
hyper_util::rt::TokioExecutor::new,
|
||||
http1_opts,
|
||||
http2_opts,
|
||||
hyper_util::rt::TokioIo::new,
|
||||
handle
|
||||
);
|
||||
|
||||
log::info!("Stopping worker-{worker_id}");
|
||||
|
||||
tasks.close();
|
||||
tasks.wait().await;
|
||||
|
||||
Python::with_gil(|_| drop(callback_wrapper));
|
||||
});
|
||||
|
||||
let pysig = signal.clone_ref(py);
|
||||
std::thread::spawn(move || {
|
||||
let pyrx = pysig.get().rx.lock().unwrap().take().unwrap();
|
||||
_ = pyrx.recv();
|
||||
stx.send(true).unwrap();
|
||||
|
||||
Python::with_gil(|py| {
|
||||
_ = pysig.get().release(py);
|
||||
drop(pysig);
|
||||
});
|
||||
});
|
||||
|
||||
_ = signal.get().qs.call_method0(py, pyo3::intern!(py, "wait"));
|
||||
}
|
||||
|
||||
fn _serve_str_ssl(
|
||||
&self,
|
||||
py: Python,
|
||||
callback: Py<CallbackScheduler>,
|
||||
event_loop: &Bound<PyAny>,
|
||||
signal: Py<WorkerSignalSync>,
|
||||
) {
|
||||
_ = pyo3_log::try_init();
|
||||
|
||||
let worker_id = self.config.id;
|
||||
log::info!("Started worker-{worker_id}");
|
||||
|
||||
let (stx, srx) = tokio::sync::watch::channel(false);
|
||||
let mut workers = vec![];
|
||||
crate::workers::serve_str_ssl_inner!(self, handle, callback, event_loop, worker_id, workers, srx);
|
||||
|
||||
let pysig = signal.clone_ref(py);
|
||||
std::thread::spawn(move || {
|
||||
let pyrx = pysig.get().rx.lock().unwrap().take().unwrap();
|
||||
_ = pyrx.recv();
|
||||
stx.send(true).unwrap();
|
||||
log::info!("Stopping worker-{worker_id}");
|
||||
while let Some(worker) = workers.pop() {
|
||||
worker.join().unwrap();
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
_ = pysig.get().release(py);
|
||||
drop(pysig);
|
||||
});
|
||||
});
|
||||
|
||||
_ = signal.get().qs.call_method0(py, pyo3::intern!(py, "wait"));
|
||||
}
|
||||
serve_mtr!(
|
||||
_serve_mtr_http_plain_auto_base,
|
||||
auto,
|
||||
serve_connection,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_mtr!(
|
||||
_serve_mtr_http_plain_auto_file,
|
||||
auto,
|
||||
serve_connection,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_mtr!(
|
||||
_serve_mtr_http_plain_autou_base,
|
||||
auto,
|
||||
serve_connection_with_upgrades,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_mtr!(
|
||||
_serve_mtr_http_plain_autou_file,
|
||||
auto,
|
||||
serve_connection_with_upgrades,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_mtr!(
|
||||
_serve_mtr_http_plain_1_base,
|
||||
1,
|
||||
connection_builder_h1,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_mtr!(
|
||||
_serve_mtr_http_plain_1_file,
|
||||
1,
|
||||
connection_builder_h1,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_mtr!(
|
||||
_serve_mtr_http_plain_1u_base,
|
||||
1,
|
||||
connection_builder_h1u,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_mtr!(
|
||||
_serve_mtr_http_plain_1u_file,
|
||||
1,
|
||||
connection_builder_h1u,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_mtr!(
|
||||
_serve_mtr_http_plain_2_base,
|
||||
2,
|
||||
serve_connection,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_mtr!(
|
||||
_serve_mtr_http_plain_2_file,
|
||||
2,
|
||||
serve_connection,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_mtr_ssl!(
|
||||
_serve_mtr_http_tls_auto_base,
|
||||
auto,
|
||||
serve_connection,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_mtr_ssl!(
|
||||
_serve_mtr_http_tls_auto_file,
|
||||
auto,
|
||||
serve_connection,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_mtr_ssl!(
|
||||
_serve_mtr_http_tls_autou_base,
|
||||
auto,
|
||||
serve_connection_with_upgrades,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_mtr_ssl!(
|
||||
_serve_mtr_http_tls_autou_file,
|
||||
auto,
|
||||
serve_connection_with_upgrades,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_mtr_ssl!(
|
||||
_serve_mtr_http_tls_1_base,
|
||||
1,
|
||||
connection_builder_h1,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_mtr_ssl!(
|
||||
_serve_mtr_http_tls_1_file,
|
||||
1,
|
||||
connection_builder_h1,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_mtr_ssl!(
|
||||
_serve_mtr_http_tls_1u_base,
|
||||
1,
|
||||
connection_builder_h1u,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_mtr_ssl!(
|
||||
_serve_mtr_http_tls_1u_file,
|
||||
1,
|
||||
connection_builder_h1u,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_mtr_ssl!(
|
||||
_serve_mtr_http_tls_2_base,
|
||||
2,
|
||||
serve_connection,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_mtr_ssl!(
|
||||
_serve_mtr_http_tls_2_file,
|
||||
2,
|
||||
serve_connection,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_str!(
|
||||
_serve_str_http_plain_auto_base,
|
||||
auto,
|
||||
serve_connection,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_str!(
|
||||
_serve_str_http_plain_auto_file,
|
||||
auto,
|
||||
serve_connection,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_str!(
|
||||
_serve_str_http_plain_autou_base,
|
||||
auto,
|
||||
serve_connection_with_upgrades,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_str!(
|
||||
_serve_str_http_plain_autou_file,
|
||||
auto,
|
||||
serve_connection_with_upgrades,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_str!(
|
||||
_serve_str_http_plain_1_base,
|
||||
1,
|
||||
connection_builder_h1,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_str!(
|
||||
_serve_str_http_plain_1_file,
|
||||
1,
|
||||
connection_builder_h1,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_str!(
|
||||
_serve_str_http_plain_1u_base,
|
||||
1,
|
||||
connection_builder_h1u,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_str!(
|
||||
_serve_str_http_plain_1u_file,
|
||||
1,
|
||||
connection_builder_h1u,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_str!(
|
||||
_serve_str_http_plain_2_base,
|
||||
2,
|
||||
serve_connection,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_str!(
|
||||
_serve_str_http_plain_2_file,
|
||||
2,
|
||||
serve_connection,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_str_ssl!(
|
||||
_serve_str_http_tls_auto_base,
|
||||
auto,
|
||||
serve_connection,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_str_ssl!(
|
||||
_serve_str_http_tls_auto_file,
|
||||
auto,
|
||||
serve_connection,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_str_ssl!(
|
||||
_serve_str_http_tls_autou_base,
|
||||
auto,
|
||||
serve_connection_with_upgrades,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_str_ssl!(
|
||||
_serve_str_http_tls_autou_file,
|
||||
auto,
|
||||
serve_connection_with_upgrades,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_str_ssl!(
|
||||
_serve_str_http_tls_1_base,
|
||||
1,
|
||||
connection_builder_h1,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_str_ssl!(
|
||||
_serve_str_http_tls_1_file,
|
||||
1,
|
||||
connection_builder_h1,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_str_ssl!(
|
||||
_serve_str_http_tls_1u_base,
|
||||
1,
|
||||
connection_builder_h1u,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_str_ssl!(
|
||||
_serve_str_http_tls_1u_file,
|
||||
1,
|
||||
connection_builder_h1u,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
serve_str_ssl!(
|
||||
_serve_str_http_tls_2_base,
|
||||
2,
|
||||
serve_connection,
|
||||
crate::workers::WorkerCTXBase,
|
||||
service_app
|
||||
);
|
||||
serve_str_ssl!(
|
||||
_serve_str_http_tls_2_file,
|
||||
2,
|
||||
serve_connection,
|
||||
crate::workers::WorkerCTXFiles,
|
||||
service_files
|
||||
);
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
|
@ -253,6 +583,7 @@ impl WSGIWorker {
|
|||
http_mode="1",
|
||||
http1_opts=None,
|
||||
http2_opts=None,
|
||||
static_files=None,
|
||||
ssl_enabled=false,
|
||||
ssl_cert=None,
|
||||
ssl_key=None,
|
||||
|
@ -271,6 +602,7 @@ impl WSGIWorker {
|
|||
http_mode: &str,
|
||||
http1_opts: Option<PyObject>,
|
||||
http2_opts: Option<PyObject>,
|
||||
static_files: Option<(String, String, String)>,
|
||||
ssl_enabled: bool,
|
||||
ssl_cert: Option<&str>,
|
||||
ssl_key: Option<&str>,
|
||||
|
@ -289,6 +621,7 @@ impl WSGIWorker {
|
|||
worker_http1_config_from_py(py, http1_opts)?,
|
||||
worker_http2_config_from_py(py, http2_opts)?,
|
||||
false,
|
||||
static_files,
|
||||
ssl_enabled,
|
||||
ssl_cert,
|
||||
ssl_key,
|
||||
|
@ -304,9 +637,24 @@ impl WSGIWorker {
|
|||
event_loop: &Bound<PyAny>,
|
||||
signal: Py<WorkerSignalSync>,
|
||||
) {
|
||||
match self.config.ssl_enabled {
|
||||
false => self._serve_mtr(py, callback, event_loop, signal),
|
||||
true => self._serve_mtr_ssl(py, callback, event_loop, signal),
|
||||
match (
|
||||
&self.config.http_mode[..],
|
||||
self.config.ssl_enabled,
|
||||
self.config.static_files.is_some(),
|
||||
) {
|
||||
("auto", false, false) => self._serve_mtr_http_plain_auto_base(py, callback, event_loop, signal),
|
||||
("auto", false, true) => self._serve_mtr_http_plain_auto_file(py, callback, event_loop, signal),
|
||||
("auto", true, false) => self._serve_mtr_http_tls_auto_base(py, callback, event_loop, signal),
|
||||
("auto", true, true) => self._serve_mtr_http_tls_auto_file(py, callback, event_loop, signal),
|
||||
("1", false, false) => self._serve_mtr_http_plain_1_base(py, callback, event_loop, signal),
|
||||
("1", false, true) => self._serve_mtr_http_plain_1_file(py, callback, event_loop, signal),
|
||||
("1", true, false) => self._serve_mtr_http_tls_1_base(py, callback, event_loop, signal),
|
||||
("1", true, true) => self._serve_mtr_http_tls_1_file(py, callback, event_loop, signal),
|
||||
("2", false, false) => self._serve_mtr_http_plain_2_base(py, callback, event_loop, signal),
|
||||
("2", false, true) => self._serve_mtr_http_plain_2_file(py, callback, event_loop, signal),
|
||||
("2", true, false) => self._serve_mtr_http_tls_2_base(py, callback, event_loop, signal),
|
||||
("2", true, true) => self._serve_mtr_http_tls_2_file(py, callback, event_loop, signal),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -317,9 +665,24 @@ impl WSGIWorker {
|
|||
event_loop: &Bound<PyAny>,
|
||||
signal: Py<WorkerSignalSync>,
|
||||
) {
|
||||
match self.config.ssl_enabled {
|
||||
false => self._serve_str(py, callback, event_loop, signal),
|
||||
true => self._serve_str_ssl(py, callback, event_loop, signal),
|
||||
match (
|
||||
&self.config.http_mode[..],
|
||||
self.config.ssl_enabled,
|
||||
self.config.static_files.is_some(),
|
||||
) {
|
||||
("auto", false, false) => self._serve_str_http_plain_auto_base(py, callback, event_loop, signal),
|
||||
("auto", false, true) => self._serve_str_http_plain_auto_file(py, callback, event_loop, signal),
|
||||
("auto", true, false) => self._serve_str_http_tls_auto_base(py, callback, event_loop, signal),
|
||||
("auto", true, true) => self._serve_str_http_tls_auto_file(py, callback, event_loop, signal),
|
||||
("1", false, false) => self._serve_str_http_plain_1_base(py, callback, event_loop, signal),
|
||||
("1", false, true) => self._serve_str_http_plain_1_file(py, callback, event_loop, signal),
|
||||
("1", true, false) => self._serve_str_http_tls_1_base(py, callback, event_loop, signal),
|
||||
("1", true, true) => self._serve_str_http_tls_1_file(py, callback, event_loop, signal),
|
||||
("2", false, false) => self._serve_str_http_plain_2_base(py, callback, event_loop, signal),
|
||||
("2", false, true) => self._serve_str_http_plain_2_file(py, callback, event_loop, signal),
|
||||
("2", true, false) => self._serve_str_http_tls_2_base(py, callback, event_loop, signal),
|
||||
("2", true, true) => self._serve_str_http_tls_2_file(py, callback, event_loop, signal),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ def info(environ, protocol):
|
|||
'method': environ['REQUEST_METHOD'],
|
||||
'path': environ['PATH_INFO'],
|
||||
'query_string': environ['QUERY_STRING'],
|
||||
'content_length': environ['CONTENT_LENGTH'],
|
||||
'content_length': environ.get('CONTENT_LENGTH'),
|
||||
'headers': {k: v for k, v in environ.items() if k.startswith('HTTP_')},
|
||||
}
|
||||
).encode('utf8')
|
||||
|
|
|
@ -16,7 +16,7 @@ def _serve(**kwargs):
|
|||
|
||||
|
||||
@asynccontextmanager
|
||||
async def _server(interface, port, runtime_mode, tls=False, task_impl='asyncio'):
|
||||
async def _server(interface, port, runtime_mode, tls=False, task_impl='asyncio', static_mount=False):
|
||||
certs_path = Path.cwd() / 'tests' / 'fixtures' / 'tls'
|
||||
kwargs = {
|
||||
'interface': interface,
|
||||
|
@ -34,6 +34,8 @@ async def _server(interface, port, runtime_mode, tls=False, task_impl='asyncio')
|
|||
else:
|
||||
kwargs['ssl_cert'] = certs_path / 'cert.pem'
|
||||
kwargs['ssl_key'] = certs_path / 'key.pem'
|
||||
if static_mount:
|
||||
kwargs['static_path_mount'] = Path.cwd() / 'tests' / 'fixtures'
|
||||
|
||||
succeeded, spawn_failures = False, 0
|
||||
while spawn_failures < 3:
|
||||
|
@ -102,3 +104,8 @@ def server(server_port, request):
|
|||
@pytest.fixture(scope='function')
|
||||
def server_tls(server_port, request):
|
||||
return partial(_server, request.param, server_port, tls=True)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def server_static_files(server_port, request):
|
||||
return partial(_server, request.param, server_port, static_mount=True)
|
||||
|
|
46
tests/test_static_files.py
Normal file
46
tests/test_static_files.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
import httpx
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize('server_static_files', ['asgi', 'rsgi', 'wsgi'], indirect=True)
|
||||
@pytest.mark.parametrize('runtime_mode', ['mt', 'st'])
|
||||
async def test_static_files(server_static_files, runtime_mode):
|
||||
async with server_static_files(runtime_mode) as port:
|
||||
res = httpx.get(f'http://localhost:{port}/static/media.png')
|
||||
|
||||
assert res.status_code == 200
|
||||
assert res.headers.get('content-type') == 'image/png'
|
||||
assert res.headers.get('cache-control')
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize('server_static_files', ['asgi', 'rsgi', 'wsgi'], indirect=True)
|
||||
@pytest.mark.parametrize('runtime_mode', ['mt', 'st'])
|
||||
async def test_static_files_notfound(server_static_files, runtime_mode):
|
||||
async with server_static_files(runtime_mode) as port:
|
||||
res = httpx.get(f'http://localhost:{port}/static/missing.png')
|
||||
|
||||
assert res.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize('server_static_files', ['asgi', 'rsgi', 'wsgi'], indirect=True)
|
||||
@pytest.mark.parametrize('runtime_mode', ['mt', 'st'])
|
||||
async def test_static_files_outsidemount(monkeypatch, server_static_files, runtime_mode):
|
||||
monkeypatch.setattr(httpx._urlparse, 'normalize_path', lambda v: v)
|
||||
|
||||
async with server_static_files(runtime_mode) as port:
|
||||
res = httpx.get(f'http://localhost:{port}/static/../conftest.py')
|
||||
|
||||
assert res.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize('server_static_files', ['asgi', 'rsgi', 'wsgi'], indirect=True)
|
||||
@pytest.mark.parametrize('runtime_mode', ['mt', 'st'])
|
||||
async def test_static_files_approute(server_static_files, runtime_mode):
|
||||
async with server_static_files(runtime_mode) as port:
|
||||
res = httpx.get(f'http://localhost:{port}/info')
|
||||
|
||||
assert res.status_code == 200
|
Loading…
Add table
Add a link
Reference in a new issue