may_minihttp

repository·master·Indexed 21 days ago

https://github.com/xudong-huang/may_minihttp

A high-performance, lightweight HTTP server implementation for Rust built on top of the may runtime. It provides a port of tokio_minihttp that allows developers to call MAY block APIs directly within services. The library includes the HttpService and HttpServiceFactory traits for handling shared or connection-scoped services, as well as configurable header limits via HttpConfig and MaxHeaders to prevent resource exhaustion.

Tokens
4.6K
Snippets
18
Records
26
Agent score
75%

What's inside may_minihttp

  1. Implement and run an HTTP service with may_minihttp

    master

    To create an HTTP server, you must implement the HttpService trait for your service struct. The call method receives a Request and a mutable reference to a Response, where you can set the response body. You then wrap your service in an HttpServer and call .start(address) to begin listening on the specified network address.

    extern crate may_minihttp;
    
    use std::io;
    use may_minihttp::{HttpServer, HttpService, Request, Response};
    
    #[derive(Clone)]
    struct HelloWorld;
    
    impl HttpService for HelloWorld {
        fn call(&mut self, _req: Request, res: &mut Response) -> io::Result<()> {
            res.body("Hello, world!");
            Ok(())
        }
    }
    
    // Start the server in `main`.
    fn main() {
        let server = HttpServer(HelloWorld).start("0.0.0.0:8080").unwrap();
        server.join().unwrap();
    }
  2. How `HttpService` and `HttpServiceFactory` work together

    master

    The project provides two primary ways to run an HTTP server depending on whether your service is shared or connection-specific:

    1. Shared Service (HttpServer): Use this when your service implements Clone, Send, and Sync. The same service instance (cloned) is used to handle multiple concurrent connections. This is ideal for stateless services or services that manage their own internal concurrency.

    2. Connection-Scoped Service (HttpServiceFactory): Use this when you need a fresh service instance for every single connection. The factory's new_service(id) method is called for every new TCP stream, allowing you to associate the service with the connection's unique identifier (like a file descriptor).

  3. How ResponseHeader handles static and owned values

    master

    The ResponseHeader enum is designed to optimize for both performance and ease of use when managing HTTP headers:

    • ResponseHeader::Static(&'static str): Used for headers with a 'static lifetime. This is a zero-allocation path that stores a fat pointer, making it extremely efficient for common static headers.
    • ResponseHeader::Owned(Box<str>): Used for headers computed at runtime (like request IDs or dynamic content types). These values are owned by the Response and are automatically dropped when the response is dropped, preventing memory leaks without requiring manual Box::leak calls.

    Developers can use the IntoResponseHeader trait to pass various string types into the Response::header method seamlessly.

  4. Configure and start an HTTP server with HttpServer

    master

    The HttpServer struct uses a builder pattern to configure and launch an HTTP server. You provide a service factory that implements HttpServiceFactory, configure server limits or settings, and then call .bind() to start the server on a specific address. The .bind() method returns a coroutine::JoinHandle<()> which can be used to manage the server's lifecycle.

    To use HttpServer:

    1. Implement HttpService for your custom logic.
    2. Create a factory for that service.
    3. Use HttpServer::new(factory) to initialize.
    4. Apply configurations like .max_headers() or .config().
    5. Call .bind(addr) to start the server.
    use may_minihttp::{HttpServer, HttpService, Request, Response, MaxHeaders};
    use std::io;
    
    #[derive(Clone)]
    struct MyService;
    
    impl HttpService for MyService {
        fn call(&mut self, _req: Request, rsp: &mut Response) -> io::Result<()> {
            rsp.body("Hello World!");
            Ok(())
        }
    }
    
    // Start server with custom MaxHeaders
    let server = HttpServer::new(MyService)
        .max_headers(MaxHeaders::Large)
        .bind("127.0.0.1:8080")
        .unwrap();
  5. Implement the HttpService trait

    master

    The HttpService trait is the core interface for defining how your server handles incoming requests. You must implement the call method:

    fn call(&mut self, req: Request, res: &mut Response) -> io::Result<()>

    • req: The incoming Request object.
    • res: A mutable reference to the Response object used to construct the outgoing response (e.g., via res.body()).
  6. Configure the maximum number of HTTP headers

    master

    The MaxHeaders enum allows you to control how many individual HTTP header lines the parser will accept. This is useful for adjusting to different deployment environments (e.g., simple APIs vs. complex Kubernetes service meshes).

    Available Variants

    VariantCountRecommended Use Case
    Default16Simple APIs, testing
    Standard32Most web applications, single proxy
    Large64Applications behind CDNs or load balancers
    XLarge128Production infrastructure (Kubernetes, service mesh)
    Custom(n)1-256Specific requirements (clamped to 16-256 range)

    Memory Impact

    Increasing the limit has minimal memory overhead, typically consuming ~24 bytes per header slot on the stack.

    use may_minihttp::MaxHeaders;
    
    let default = MaxHeaders::Default;
    assert_eq!(default.value(), 16);
    
    let large = MaxHeaders::Large;
    assert_eq!(large.value(), 64);
    
    let custom = MaxHeaders::Custom(100);
    assert_eq!(custom.value(), 100);
  7. Configure HTTP server behavior with HttpConfig

    master

    The HttpConfig struct is used to define the operational parameters of the HTTP server. Currently, it allows you to control the maximum number of headers accepted per request to prevent resource exhaustion or header-based attacks. You can initialize it with default settings or use the builder-style with_max_headers method to customize it.

    use may_minihttp::config::HttpConfig;
    use may_minihttp::request::MaxHeaders;
    
    // Create a config with default settings
    let config = HttpConfig::new();
    
    // Create a custom config with a specific header limit
    let custom_config = HttpConfig::new()
        .with_max_headers(MaxHeaders::SomeValue(100)); // Assuming MaxHeaders follows this pattern
  8. Troubleshoot TooManyHeaders errors

    master

    If the parser encounters more header lines than the allocated buffer size, it will return an error.

    Error Message Format: TooManyHeaders: received {count} headers, limit is {limit} (over by {over_by})

    Resolution: If you see this error, your application is receiving more headers than your current configuration allows (e.g., due to multiple proxies or a service mesh). You should increase your header limit by switching to a larger MaxHeaders variant or using MaxHeaders::Custom(n).

  9. Access Request metadata and headers

    master

    The Request struct provides methods to inspect the parsed HTTP request:

    • method(): Returns the HTTP method as a &str (e.g., "GET", "POST").
    • path(): Returns the request path as a &str (e.g., "/api/v1/resource").
    • version(): Returns the HTTP version as a u8.
    • headers(): Returns a slice of httparse::Header objects for manual header inspection.
    • body(): Consumes the request to return a BodyReader for reading the payload.
  10. Decode request bodies with different size strategies

    master

    The crate provides several decoding functions to handle request bodies depending on the expected size and memory constraints:

    • decode_default: Standard decoding.
    • decode_standard: Standard size decoding.
    • decode_large: For larger payloads.
    • decode_xlarge: For very large payloads.

    Additionally, you can manage header limits using MaxHeaders.

  11. Configure custom header limits with `HttpServerWithHeaders`

    master

    By default, the server uses a fixed maximum number of headers. If your application requires handling more than the default (typically 16), use HttpServerWithHeaders<T, const N: usize>.

    Commonly used sizes:

    • 32: Standard
    • 64: Large
    • 128: XLarge
    use may_minihttp::HttpServerWithHeaders;
    
    // Create a server that supports up to 32 headers
    let server = HttpServerWithHeaders::<_, 32>(my_service);
    let handle = server.start("127.0.0.1:8080")?;