rust-web-server 18.2.0

Single-binary web server and reverse proxy: automatic HTTPS (Let's Encrypt), HTTP/1.1, HTTP/2 and HTTP/3, load balancing, auth and rate limiting, configured in one TOML file. Also a Rust library: routing, middleware, MCP server, ORM, background jobs. No hyper, no Tower; HTTP/2 and HTTP/3 framing via h2 and quinn.
Documentation
[package]
name = "rust-web-server"
version = "18.2.0"
authors = ["Bohdan Tsap <bohdan.tsap@tutanota.com>"]
repository = "https://github.com/bohdaq/rust-web-server"
homepage = "https://rws8.tech/"
description = "Single-binary web server and reverse proxy: automatic HTTPS (Let's Encrypt), HTTP/1.1, HTTP/2 and HTTP/3, load balancing, auth and rate limiting, configured in one TOML file. Also a Rust library: routing, middleware, MCP server, ORM, background jobs. No hyper, no Tower; HTTP/2 and HTTP/3 framing via h2 and quinn."
edition = "2021"
rust-version = "1.88"
license = "MIT"
keywords = ["web-server", "reverse-proxy", "https", "http3", "acme"]
categories = ["network-programming", "web-programming::http-server"]

[workspace]
# Previously implicit (no [workspace] table at all — Cargo auto-added
# rws-macros as a member since it's a path dependency of this package).
# rws-wasm-shim depends ON rust-web-server rather than the other way around,
# so it can't be pulled in as a path *dependency* the way rws-macros is —
# it needs an explicit workspace membership instead. See spec/WASM_SHIM.md.
members = ["rws-macros", "rws-wasm-shim"]
# fuzz/ is its own workspace (cargo-fuzz, nightly-only) — see fuzz/README.md.
exclude = ["fuzz"]

[lints.rust]
# `cargo fuzz` builds with --cfg fuzzing (see src/lib.rs fuzz_api).
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }

[lib]
name = "rust_web_server"
path = "src/lib.rs"

[[bin]]
name = "rws"
path = "src/main.rs"

# Runnable with `cargo run --example <name> --features "..."`; `cargo test
# --all-features` (or with the listed features) also runs their tests.
[[example]]
name = "hello"
required-features = ["http2"]
test = true

[[example]]
name = "rest_api"
required-features = ["http2", "serde"]
test = true

[[example]]
name = "static_site"
required-features = ["http2"]
test = true

[[example]]
name = "reverse_proxy"
required-features = ["http2"]
test = true

[[example]]
name = "sse_ticker"
required-features = ["http2"]
test = true

[[example]]
name = "upload"
required-features = ["http2"]
test = true

[[example]]
name = "mcp_inventory"
required-features = ["macros", "serde", "sso"]
test = true

[[example]]
name = "mcp_gateway"
required-features = ["http2"]
test = true

[[example]]
name = "mcp_conformance"
required-features = ["http2"]
test = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

# `cargo binstall rust-web-server` downloads the prebuilt archive that
# .github/workflows/release.yml attaches to each GitHub Release instead of
# compiling. Linux archives are static musl builds; binstall falls back to
# them on glibc hosts automatically.
[package.metadata.binstall]
pkg-url = "{ repo }/releases/download/{ version }/rws-{ version }-{ target }{ archive-suffix }"
bin-dir = "rws-{ version }-{ target }/{ bin }{ binary-ext }"
pkg-fmt = "tgz"

[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
pkg-fmt = "zip"

[features]
default = ["http3", "brotli", "zstd"]
http1 = ["dep:ctrlc", "dep:libc"]
http2 = ["dep:h2", "dep:rustls", "dep:tokio-rustls", "dep:tokio", "dep:bytes", "dep:http", "dep:webpki-roots", "dep:libc"]
http3 = ["http2", "dep:quinn", "dep:h3", "dep:h3-quinn"]
http-client = ["dep:rustls", "dep:webpki-roots"]
# Content codings besides gzip (18.1.0): `br` (pure Rust) and `zstd` (C
# libzstd, built with the crate). Both are in the default set.
brotli = ["dep:brotli"]
zstd   = ["dep:zstd"]
acme   = ["http2", "dep:rcgen", "dep:webpki-roots", "dep:libc", "dep:aws-lc-rs"]
serde  = ["dep:serde", "dep:serde_json"]
auth   = ["dep:hmac", "dep:sha2"]
auth-asymmetric = ["auth", "dep:p256", "dep:rand_core"]  # RS256 (in-crate rsa_key) / ES256 (p256) in JwtLayer
macros = ["dep:rws-macros"]
# The `rws` binary allocates with mimalloc instead of the system allocator
# (the library never sets a global allocator).
mimalloc = ["dep:mimalloc"]
tera   = ["dep:tera", "dep:serde", "dep:serde_json"]
model-sqlite   = ["dep:sqlx", "sqlx/sqlite",   "sqlx/runtime-tokio", "http2"]
model-postgres = ["dep:sqlx", "sqlx/postgres", "sqlx/runtime-tokio-rustls", "http2"]
model-mysql    = ["dep:sqlx", "sqlx/mysql",    "sqlx/runtime-tokio-rustls", "http2"]
crypto = ["dep:argon2", "dep:rand_core", "dep:hmac", "dep:sha2", "dep:aes-gcm"]
csrf   = ["dep:rand_core"]
mailer = []  # plain SMTP; STARTTLS/SMTPS additionally requires http-client or http2
sso    = ["dep:p256", "dep:sha2", "dep:rand_core", "dep:serde", "dep:serde_json", "http-client"]
sso-server = ["sso", "auth"]  # AuthServer: rws as its own OAuth 2.0 token issuer; reuses auth's HS256 build_jwt/verify_jwt (see src/sso/server.rs docs for why not RSA/EC)
sso-saml   = ["sso"]  # SamlSp: SAML 2.0 Service Provider; no new deps — hand-rolled XML parser, not quick-xml (see src/sso/saml/mod.rs docs)
jobs   = []  # in-memory JobQueue; PersistentJobQueue additionally requires a model-* feature
storage-local = []  # LocalStorage; no new deps
storage-s3    = ["dep:hmac", "dep:sha2", "http-client"]  # S3Storage; AWS SigV4 via hmac+sha2, no AWS SDK
storage-azure = ["dep:hmac", "dep:sha2", "http-client"]  # AzureBlobStorage; Shared Key HMAC signing + Managed Identity, no Azure SDK
openapi = []  # AppWithState::openapi() / AsyncAppWithState::openapi(); no new deps
webhook = ["dep:hmac", "dep:sha2"]  # verify_webhook_signature: GitHub, Shopify, Stripe
rewrite-regex = ["dep:regex"]  # RewriteLayer::request_uri_regex_rewrite
secrets = ["dep:hmac", "dep:sha2", "http-client"]  # secrets::resolve(): vault:// / aws-sm:// / azkv:// reference resolution (Vault, AWS Secrets Manager, Azure Key Vault); reuses storage-s3's SigV4 signer/credential chain and storage-azure's Managed Identity token fetch

[dependencies]
ctrlc = { version = "3", optional = true }
mimalloc = { version = "0.1", optional = true, default-features = false, features = ["v3"] }
libc = { version = "0.2", optional = true }
file-ext = "12.0.0"
flate2 = "1"
brotli = { version = "9", optional = true }
zstd = { version = "0.14.3", optional = true }  # >= 0.14.3: RUSTSEC-2026-0391
url-search-params = "12.0.0"
url-build-parse = "12.1.0"
tokio = { version = "1", features = ["net", "rt-multi-thread", "macros", "io-util", "signal", "time"], optional = true }
rcgen = { version = "0.14", default-features = false, features = ["pem", "aws_lc_rs"], optional = true }
tokio-rustls = { version = "0.26", optional = true }
h2 = { version = "0.4", optional = true }
bytes = { version = "1", optional = true }
http = { version = "1", optional = true }
# Not quinn's default "rustls-ring": two rustls crypto providers in one build
# make rustls::ClientConfig::builder() panic ("Could not automatically
# determine the process-level CryptoProvider") for every outbound TLS client.
quinn = { version = "0.11", default-features = false, features = ["runtime-tokio", "rustls-aws-lc-rs", "bloom"], optional = true }
h3 = { version = "0.0.8", optional = true }
h3-quinn = { version = "0.0.10", optional = true }
serde = { version = "1", features = ["derive"], optional = true }
serde_json = { version = "1", optional = true }
hmac  = { version = "0.12", optional = true }
sha2  = { version = "0.10", features = ["oid"], optional = true }
p256      = { version = "0.14", features = ["ecdsa"], optional = true }
aws-lc-rs = { version = "1", optional = true }
rws-macros = { path = "rws-macros", version = "17.28", optional = true }
tera = { version = "1", optional = true }
sqlx = { version = "0.8", default-features = false, optional = true }
argon2    = { version = "0.5", features = ["password-hash"], optional = true }
rand_core = { version = "0.6", features = ["getrandom"], optional = true }
aes-gcm   = { version = "0.10", optional = true }
regex     = { version = "1", optional = true }

[dev-dependencies]
# test-util: tokio::time::pause for timeout tests (src/server/http1_async.rs).
tokio = { version = "1", features = ["test-util", "macros", "rt"] }

# rustls's own "aws-lc-rs" crypto-backend feature compiles C/asm (aws-lc-sys)
# that doesn't build for wasm32-wasip2 — moved to a native-only target table
# so enabling `http-client` (and therefore `sso`/`secrets`/`storage-s3`/
# `storage-azure`) doesn't drag in a dependency that can't compile for wasm32.
# TLS itself still works there — the host performs it via `outgoing-handler`
# (see the #[cfg(target_arch = "wasm32")] send_once in src/http_client/mod.rs)
# instead of rustls. http2/http3/acme (which also use rustls) were already
# unsupported on wasm32 regardless, since tokio's rt-multi-thread requires
# real OS threads.
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
# Self-signed certificates for the in-process HTTP/3 test (src/h3_handler/tests.rs).
rcgen = { version = "0.14", default-features = false, features = ["pem", "aws_lc_rs"] }
# RSA keys and signatures for tests of rsa_key (src/rsa_key/test_support.rs).
aws-lc-rs = "1"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
webpki-roots = { version = "1.0.11", optional = true }
rustls = { version = "0.23.46", default-features = false, features = ["aws-lc-rs", "tls12", "logging", "std"], optional = true }

# wasm32-wasip2 outbound HTTP backend for http_client::Client::send() — see
# the #[cfg(target_arch = "wasm32")] send_once in src/http_client/mod.rs and
# spec/WASM_SHIM.md Phase 2 item 7. Native builds never see this dependency.
# `rws --windows-service=<dir>`: run under the Service Control Manager (src/main.rs).
[target.'cfg(windows)'.dependencies]
windows-service = "0.8.1"

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasip2 = "2.0"