#web-server #tokio #server

bin+lib httpageboy

A lightweight library for handling raw HTTP request/response transmission. Good base for APIs. Supports both synchronous and asynchronous programming models.

16 stable releases

Uses new Rust 2024

new 1.0.19 Mar 16, 2026
1.0.17 Dec 21, 2025
1.0.13 Oct 4, 2025
1.0.11 Jul 19, 2025
0.1.0 Jul 11, 2025

#192 in HTTP server


Used in eqeqo-api-auth

MIT license

90KB
2.5K SLoC

HTTPageboy

Minimal HTTP server package for handling request/response transmission. Focuses only on transporting a well formed HTTP message; does not process or decide how the server behaves. Aspires to become runtime-agnostic, with minimal, solid, and flexible dependencies.

Example

The core logic resides in src/lib.rs.

See it working out of the box on this video

The following example is executable. Run cargo run to see the available variants and navigate to http://127.0.0.1:7878 in your browser.

A basic server setup (select a runtime feature when running, e.g. cargo run --features async_tokio):

#![cfg(feature = "async_tokio")]
use httpageboy::{Rt, Response, Server, StatusCode};

/// Minimal async handler: waits 100ms and replies "ok"
async fn demo(_req: &()) -> Response {
  tokio::time::sleep(std::time::Duration::from_millis(100)).await;
  Response {
    status: StatusCode::Ok.to_string(),
    headers: vec![("Content-Type".into(), "text/plain".into())],
    body: b"ok".to_vec(),
  }
}

#[tokio::main]
async fn main() {
  let mut srv = Server::new("127.0.0.1:7878", None).await.unwrap();
  srv.add_route("/", Rt::GET, handler!(demo));
  srv.run().await;
}

Response now supports arbitrary headers:

Response {
  status: StatusCode::Ok.to_string(),
  headers: vec![("Content-Type".into(), "application/json".into())],
  body: br#"{"ok":true}"#.to_vec(),
}

Response {
  status: StatusCode::TemporaryRedirect.to_string(),
  headers: vec![
    ("Location".into(), "https://example.com".into()),
    ("Content-Type".into(), "text/plain".into()),
  ],
  body: Vec::new(),
}

Testing

Test helpers live in httpageboy::test_utils and work the same for sync and async runtimes:

  • setup_test_server(server_url, factory) starts a server once per URL and marks it active (pass None to reuse the default 127.0.0.1:0 and let the OS pick a port).
  • run_test(request, expected, target_url) opens a TCP connection to the active server (or the URL you pass), writes a raw HTTP payload, and asserts the response contains the expected bytes.

Async tokio example mirroring the current helpers:

#![cfg(feature = "async_tokio")]
use httpageboy::test_utils::{run_test, setup_test_server};
use httpageboy::{handler, Request, Response, Rt, Server, StatusCode};

async fn server_factory() -> Server {
  let mut server = Server::new("127.0.0.1:0", None).await.unwrap();
  server.add_route("/", Rt::GET, handler!(home));
  server
}

async fn home(_req: &Request) -> Response {
  Response {
    status: StatusCode::Ok.to_string(),
    headers: vec![("Content-Type".into(), "text/plain".into())],
    body: b"home".to_vec(),
  }
}

#[tokio::test]
async fn test_home_ok() {
  setup_test_server(None, || server_factory()).await;
  let body = run_test(b"GET / HTTP/1.1\r\n\r\n", b"home", None).await;
  assert!(body.contains("home"));
}

CORS

Servers now ship with a permissive CORS policy by default (allow all origins, methods, and common headers). You can tighten it after constructing the server:

let mut server = Server::new("127.0.0.1:7878", None).await.unwrap();
server.set_cors_str("origin=http://localhost:3000,credentials=true,headers=Content-Type");
// or build it directly:
// server.set_cors(CorsPolicy::from_config_str("origin=http://localhost:3000"));

Preflights (OPTIONS) are answered automatically using the active policy.

Comandos:

cargo test --features sync --test test_sync
cargo test --features async_tokio --test test_async_tokio
cargo test --features async_std --test test_async_std
cargo test --features async_smol --test test_async_smol

Examples

Additional examples can be found within the tests.

License

Copyright (c) 2025 fahedsl. This project is licensed under the MIT License.

Dependencies

~0.7–15MB
~174K SLoC