Skip to content

A modular HTTP server library in Rust.

License

Notifications You must be signed in to change notification settings

mleonhard/servlin

Repository files navigation

Servlin

crates.io version license: Apache 2.0 unsafe forbidden pipeline status

A modular HTTP server library in Rust.

Features

  • forbid(unsafe_code)
  • Threaded request handlers:
    FnOnce(Request) -> Response + 'static + Clone + Send + Sync
  • Uses async code internally for excellent performance under load
  • JSON
  • Server-Sent Events (SSE)
  • Saves large request bodies to temp files
  • Sends 100-Continue
  • Limits number of threads and connections
  • Modular: roll your own logging, write custom versions of internal methods, etc.
  • No macros or complicated type params
  • Good test coverage (63%)

Limitations

  • New, not proven in production.
  • To do:
    • Request timeouts
    • chunked transfer-encoding for request bodies
    • gzip
    • brotli
    • TLS
    • automatically getting TLS certs via ACME
    • Drop idle connections when approaching connection limit.
    • Denial-of-Service mitigation: source throttling, minimum throughput
    • Complete functional test suite
    • Missing load tests
    • Disk space usage limits

Examples

Complete examples: examples/.

Simple example:

use serde::Deserialize;
use serde_json::json;
use servlin::{
    socket_addr_127_0_0_1,
    Error,
    HttpServerBuilder,
    Request,
    Response
};
use servlin::log::log_request_and_response;
use servlin::reexport::{safina_executor, safina_timer};
use std::sync::Arc;
use temp_dir::TempDir;

struct State {}

fn hello(_state: Arc<State>, req: Request) -> Result<Response, Error> {
    #[derive(Deserialize)]
    struct Input {
        name: String,
    }
    let input: Input = req.json()?;
    Ok(Response::json(200, json!({"message": format!("Hello, {}!", input.name)}))
    .unwrap())
}

fn handle_req(state: Arc<State>, req: Request) -> Result<Response, Error> {
    match (req.method(), req.url().path()) {
        ("GET", "/ping") => Ok(Response::text(200, "ok")),
        ("POST", "/hello") => hello(state, req),
        _ => Ok(Response::text(404, "Not found")),
    }
}

let state = Arc::new(State {});
let request_handler = move |req: Request| {
    log_request_and_response(req, |req| handle_req(state, req)).unwrap()
};
let cache_dir = TempDir::new().unwrap();
safina_timer::start_timer_thread();
let executor = safina_executor::Executor::new(1, 9).unwrap();
executor.block_on(
    HttpServerBuilder::new()
        .listen_addr(socket_addr_127_0_0_1(8271))
        .max_conns(1000)
        .small_body_len(64 * 1024)
        .receive_large_bodies(cache_dir.path())
        .spawn_and_join(request_handler)
).unwrap();

Cargo Geiger Safety Report


Metric output format: x/y
    x = unsafe code used by the build
    y = total unsafe code found in the crate

Symbols: 
    πŸ”’  = No `unsafe` usage found, declares #![forbid(unsafe_code)]
    ❓  = No `unsafe` usage found, missing #![forbid(unsafe_code)]
    ☒️  = `unsafe` usage found

Functions  Expressions  Impls  Traits  Methods  Dependency

0/0        0/0          0/0    0/0     0/0      πŸ”’  servlin 0.5.0
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”œβ”€β”€ async-fs 2.1.2
4/4        222/222      40/40  0/0     13/13    ☒️  β”‚   β”œβ”€β”€ async-lock 3.4.0
0/0        2/2          0/0    0/0     0/0      ☒️  β”‚   β”‚   β”œβ”€β”€ event-listener-strategy 0.5.2
0/0        39/49        8/12   0/0     2/2      ☒️  β”‚   β”‚   β”‚   β”œβ”€β”€ event-listener 5.3.1
0/0        183/183      2/2    0/0     1/1      ☒️  β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ concurrent-queue 2.5.0
4/4        12/75        4/16   0/0     0/3      ☒️  β”‚   β”‚   β”‚   β”‚   β”‚   └── crossbeam-utils 0.8.20
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ parking 2.2.1
0/0        11/191       0/0    0/0     2/2      ☒️  β”‚   β”‚   β”‚   β”‚   └── pin-project-lite 0.2.14
0/0        11/191       0/0    0/0     2/2      ☒️  β”‚   β”‚   β”‚   └── pin-project-lite 0.2.14
0/0        39/49        8/12   0/0     2/2      ☒️  β”‚   β”‚   β”œβ”€β”€ event-listener 5.3.1
0/0        11/191       0/0    0/0     2/2      ☒️  β”‚   β”‚   └── pin-project-lite 0.2.14
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”‚   β”œβ”€β”€ blocking 1.6.1
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”‚   β”‚   β”œβ”€β”€ async-channel 2.3.1
0/0        183/183      2/2    0/0     1/1      ☒️  β”‚   β”‚   β”‚   β”œβ”€β”€ concurrent-queue 2.5.0
0/0        2/2          0/0    0/0     0/0      ☒️  β”‚   β”‚   β”‚   β”œβ”€β”€ event-listener-strategy 0.5.2
0/0        36/36        2/2    0/0     0/0      ☒️  β”‚   β”‚   β”‚   β”œβ”€β”€ futures-core 0.3.31
0/0        11/191       0/0    0/0     2/2      ☒️  β”‚   β”‚   β”‚   └── pin-project-lite 0.2.14
1/1        860/866      4/4    0/0     13/13    ☒️  β”‚   β”‚   β”œβ”€β”€ async-task 4.7.1
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   β”‚   β”œβ”€β”€ futures-io 0.3.31
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   β”‚   β”œβ”€β”€ futures-lite 2.3.0
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”‚   β”‚   β”‚   β”œβ”€β”€ fastrand 2.1.1
0/0        36/36        2/2    0/0     0/0      ☒️  β”‚   β”‚   β”‚   β”œβ”€β”€ futures-core 0.3.31
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   β”‚   β”‚   β”œβ”€β”€ futures-io 0.3.31
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”‚   β”‚   β”‚   β”œβ”€β”€ parking 2.2.1
0/0        11/191       0/0    0/0     2/2      ☒️  β”‚   β”‚   β”‚   └── pin-project-lite 0.2.14
0/0        28/28        2/2    0/0     0/0      ☒️  β”‚   β”‚   β”œβ”€β”€ piper 0.2.4
0/0        32/32        2/2    0/0     0/0      ☒️  β”‚   β”‚   β”‚   β”œβ”€β”€ atomic-waker 1.1.2
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”‚   β”‚   β”‚   β”œβ”€β”€ fastrand 2.1.1
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   β”‚   β”‚   └── futures-io 0.3.31
0/0        14/14        1/1    0/0     0/0      ☒️  β”‚   β”‚   └── tracing 0.1.40
0/0        11/191       0/0    0/0     2/2      ☒️  β”‚   β”‚       β”œβ”€β”€ pin-project-lite 0.2.14
0/0        96/96        5/5    0/0     2/2      ☒️  β”‚   β”‚       └── tracing-core 0.1.32
0/0        74/117       5/9    0/0     2/4      ☒️  β”‚   β”‚           └── once_cell 1.20.2
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   └── futures-lite 2.3.0
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”œβ”€β”€ async-net 2.0.0
0/0        68/114       19/22  1/1     4/8      ☒️  β”‚   β”œβ”€β”€ async-io 2.3.4
4/4        222/222      40/40  0/0     13/13    ☒️  β”‚   β”‚   β”œβ”€β”€ async-lock 3.4.0
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   β”‚   β”œβ”€β”€ cfg-if 1.0.0
0/0        183/183      2/2    0/0     1/1      ☒️  β”‚   β”‚   β”œβ”€β”€ concurrent-queue 2.5.0
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   β”‚   β”œβ”€β”€ futures-io 0.3.31
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   β”‚   β”œβ”€β”€ futures-lite 2.3.0
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”‚   β”‚   β”œβ”€β”€ parking 2.2.1
0/2        34/420       5/20   1/4     5/14     ☒️  β”‚   β”‚   β”œβ”€β”€ polling 3.7.3
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   β”‚   β”‚   β”œβ”€β”€ cfg-if 1.0.0
53/389     2527/7252    6/8    1/1     20/35    ☒️  β”‚   β”‚   β”‚   β”œβ”€β”€ rustix 0.38.37
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ bitflags 2.6.0
0/0        35/103       0/0    0/0     0/0      ☒️  β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ errno 0.3.9
1/90       10/596       0/2    0/0     5/69     ☒️  β”‚   β”‚   β”‚   β”‚   β”‚   └── libc 0.2.161
1/90       10/596       0/2    0/0     5/69     ☒️  β”‚   β”‚   β”‚   β”‚   └── libc 0.2.161
0/0        14/14        1/1    0/0     0/0      ☒️  β”‚   β”‚   β”‚   └── tracing 0.1.40
53/389     2527/7252    6/8    1/1     20/35    ☒️  β”‚   β”‚   β”œβ”€β”€ rustix 0.38.37
0/0        23/23        0/0    0/0     3/3      ☒️  β”‚   β”‚   β”œβ”€β”€ slab 0.4.9
0/0        14/14        1/1    0/0     0/0      ☒️  β”‚   β”‚   └── tracing 0.1.40
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”‚   β”œβ”€β”€ blocking 1.6.1
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   └── futures-lite 2.3.0
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”œβ”€β”€ fixed-buffer 1.0.0
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   └── futures-io 0.3.31
0/0        0/0          0/0    0/0     0/0      ❓  β”œβ”€β”€ futures-io 0.3.31
0/0        0/0          0/0    0/0     0/0      ❓  β”œβ”€β”€ futures-lite 2.3.0
0/0        74/117       5/9    0/0     2/4      ☒️  β”œβ”€β”€ once_cell 1.20.2
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”œβ”€β”€ permit 0.2.1
0/0        32/32        0/0    0/0     0/0      ☒️  β”œβ”€β”€ rand 0.8.5
1/90       10/596       0/2    0/0     5/69     ☒️  β”‚   β”œβ”€β”€ libc 0.2.161
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   β”œβ”€β”€ rand_chacha 0.3.1
2/2        632/680      0/0    0/0     17/25    ☒️  β”‚   β”‚   β”œβ”€β”€ ppv-lite86 0.2.20
2/2        116/116      7/7    8/8     8/8      ☒️  β”‚   β”‚   β”‚   └── zerocopy 0.7.35
0/1        162/179      0/0    0/0     0/0      ☒️  β”‚   β”‚   β”‚       β”œβ”€β”€ byteorder 1.5.0
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   β”‚   β”‚       └── zerocopy-derive 0.7.35
0/0        15/15        0/0    0/0     3/3      ☒️  β”‚   β”‚   β”‚           β”œβ”€β”€ proc-macro2 1.0.88
0/0        4/4          0/0    0/0     0/0      ☒️  β”‚   β”‚   β”‚           β”‚   └── unicode-ident 1.0.13
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   β”‚   β”‚           β”œβ”€β”€ quote 1.0.37
0/0        15/15        0/0    0/0     3/3      ☒️  β”‚   β”‚   β”‚           β”‚   └── proc-macro2 1.0.88
0/0        88/88        3/3    0/0     2/2      ☒️  β”‚   β”‚   β”‚           └── syn 2.0.82
0/0        15/15        0/0    0/0     3/3      ☒️  β”‚   β”‚   β”‚               β”œβ”€β”€ proc-macro2 1.0.88
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   β”‚   β”‚               β”œβ”€β”€ quote 1.0.37
0/0        4/4          0/0    0/0     0/0      ☒️  β”‚   β”‚   β”‚               └── unicode-ident 1.0.13
0/0        2/2          0/0    0/0     0/0      ☒️  β”‚   β”‚   └── rand_core 0.6.4
3/6        51/192       0/1    0/0     1/3      ☒️  β”‚   β”‚       └── getrandom 0.2.15
0/0        0/0          0/0    0/0     0/0      ❓  β”‚   β”‚           β”œβ”€β”€ cfg-if 1.0.0
1/90       10/596       0/2    0/0     5/69     ☒️  β”‚   β”‚           └── libc 0.2.161
0/0        2/2          0/0    0/0     0/0      ☒️  β”‚   └── rand_core 0.6.4
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”œβ”€β”€ safe-regex 0.3.0
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”‚   └── safe-regex-macro 0.3.0
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”‚       β”œβ”€β”€ safe-proc-macro2 1.0.67
0/0        4/4          0/0    0/0     0/0      ☒️  β”‚       β”‚   └── unicode-ident 1.0.13
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”‚       └── safe-regex-compiler 0.3.0
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”‚           β”œβ”€β”€ safe-proc-macro2 1.0.67
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”‚           └── safe-quote 1.0.15
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”‚               └── safe-proc-macro2 1.0.67
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”œβ”€β”€ safina-executor 0.3.3
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”‚   β”œβ”€β”€ safina-sync 0.2.4
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”‚   └── safina-threadpool 0.2.4
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”œβ”€β”€ safina-sync 0.2.4
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”œβ”€β”€ safina-timer 0.1.11
0/0        74/117       5/9    0/0     2/4      ☒️  β”‚   └── once_cell 1.20.2
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”œβ”€β”€ temp-dir 0.1.14
0/0        0/0          0/0    0/0     0/0      πŸ”’  β”œβ”€β”€ temp-file 0.1.9
0/0        0/0          0/0    0/0     0/0      ❓  └── url 2.5.2
0/0        2/2          0/0    0/0     0/0      ☒️      β”œβ”€β”€ form_urlencoded 1.2.1
0/0        8/8          0/0    0/0     0/0      ☒️      β”‚   └── percent-encoding 2.3.1
0/0        0/0          0/0    0/0     0/0      ❓      β”œβ”€β”€ idna 0.5.0
0/0        5/5          0/0    0/0     0/0      ☒️      β”‚   β”œβ”€β”€ unicode-bidi 0.3.17
1/1        24/24        0/0    0/0     0/0      ☒️      β”‚   └── unicode-normalization 0.1.24
0/0        0/0          0/0    0/0     0/0      πŸ”’      β”‚       └── tinyvec 1.8.0
0/0        0/0          0/0    0/0     0/0      πŸ”’      β”‚           └── tinyvec_macros 0.1.1
0/0        8/8          0/0    0/0     0/0      ☒️      └── percent-encoding 2.3.1

71/502     5447/11766   115/158 11/14   103/210

Alternatives

See rust-webserver-comparison.md.

Changelog

  • v0.5.0 2024-10-21 - Remove LogFileWriterBuilder.
  • v0.4.3 - Implement From<Cow<'_, str>> and From<&Path> for TagValue.
  • v0.4.2 - Implement Seek for BodyReader.
  • v0.4.1
    • Add Request::opt_json.
    • Implement From<LoggerStoppedError> for Error.
  • v0.4.0
    • Changed Response::json to return Result<Response, Error>.
    • Changed log_request_and_response to return Result.
    • Added Response::unprocessable_entity_422.
  • v0.3.2 - Fix bug in Response::include_dir redirects.
  • v0.3.1
    • Add Response::redirect_301
    • Response::include_dir to redirect from /somedir to /somedir/ so relative URLs will work.
  • v0.3.0 - Changed Response::include_dir to take &Request and look for index.html in dirs.
  • v0.2.0
    • Added:
      • log_request_and_response and other logging tooling
      • Response::ok_200()
      • Response::unauthorized_401()
      • Response::forbidden_403()
      • Response::internal_server_errror_500()
      • Response::not_implemented_501()
      • Response::service_unavailable_503()
      • EventSender::is_connected()
      • PORT_env()
    • Removed print_log_response and RequestBody::length_is_known
    • Changed RequestBody::len and is_empty to return Option.
    • Bugfixes
  • v0.1.1 - Add EventSender::unconnected.
  • v0.1.0 - Rename library to Servlin.

TO DO

  • Fix limitations above
  • Support HEAD responses that have Content-Length set and no body.
  • Add a server-wide limit on upload body size.
  • Limit disk usage for caching uploads.
  • Update rust-webserver-comparison.md

License: MIT OR Apache-2.0

About

A modular HTTP server library in Rust.

Resources

License

Stars

Watchers

Forks