# nimble **Repository Path**: wxy-code/nimble ## Basic Information - **Project Name**: nimble - **Description**: 一个Rust编写的Web框架 - **Primary Language**: Rust - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: https://crates.io/crates/nimble-http - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-02-18 - **Last Updated**: 2026-02-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Nimble A simple and elegant Rust web framework inspired by Express, built on Hyper. ## Features - **Simple & Intuitive** - Express-like route definition style - **Hyper-Powered** - Built on a reliable HTTP library - **Zero-Cost Abstractions** - Leverages Rust's powerful type system - **Type Safe** - Compile-time guarantee of correct types for routes and handlers - **Practical Utilities** - Built-in response types for JSON, HTML, file serving, redirects, etc. - **Automatic Static File Serving** - Automatically mounts files from the `./static` directory - **CORS Support** - Flexible CORS configuration with simple `.cors()` for development and detailed control for production ## Quick Start Add the dependency to your `Cargo.toml`: ```toml [dependencies] nimble-http = { version = "2", features = ["full"] } tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } # If handling JSON ``` > **features**: > **full**: Enable all > **cors**: Enable CORS > **default**: Enable core Create a simple web application: ```rust use nimble_http::{Router, get, post, post_json, Html, Json, Redirect, Text}; use serde::Deserialize; #[derive(Deserialize)] struct User { name: String, age: u8, } #[tokio::main] async fn main() { let app = Router::new() // Set Cors .cors() // GET root path, returns HTML .route("/", get(|_| async { Html("

Hello World

".to_string()) })) // GET returns JSON .route("/json", get(|_| async { Json(vec!["apple", "banana", "orange"]) })) // POST handles form (application/x-www-form-urlencoded) .route("/user", post(|params| async move { let name = params.get("name").unwrap_or(&"Anonymous".to_string()).clone(); Text(format!("Hello, {}!", name)) })) // POST handles JSON .route("/api/user", post_json(|user: User| async move { Json(format!("Created user: {}, age: {}", user.name, user.age)) })) // Redirect to Baidu .route("/baidu", get(|_| async { Redirect("https://www.baidu.com".to_string()) })); // Start the server app.run("127.0.0.1", 3000).await; } ``` ## Routes Nimble currently supports `GET` and `POST` methods, with POST further divided into regular form and JSON types. ```rust use nimble_http::{get, post, post_json}; Router::new() .route("/", get(handler_get)) .route("/submit", post(handler_post)) .route("/api/data", post_json(handler_post_json)); ``` > **Note**: The current version **does not support** path parameters (e.g., `/users/:id`) or methods like `PUT` and `DELETE`. ## Response Types Nimble provides various built-in response types, all implementing the `IntoResponse` trait: ```rust use nimble_http::{Html, Json, Text, Redirect, File, StatusCode}; // HTML response Html("

Title

".to_string()) // JSON response (requires the type to implement Serialize) Json(vec!["apple", "banana", "orange"]) // Plain text response Text("Hello".to_string()) // Temporary redirect (302) Redirect("https://example.com".to_string()) // Permanent redirect (301) Redirect::perm("https://example.com".to_string()) // File response (first parameter: file path, second: force download) File("static/image.jpg".to_string(), false) // Display directly File("file.zip".to_string(), true) // Download as attachment // Status code only (empty response) StatusCode::NOT_FOUND // Content with request headers ( [ (header::CONTENT_TYPE, "text/html; charset=utf-8"), (header::CACHE_CONTROL, "no-cache"), (header::HeaderName::from_static("x-powered-by"), "Nimble") ], "

Hello

".to_string() ) // Content with request headers and StatusCode ( [ (header::CONTENT_TYPE, "text/html; charset=utf-8"), (header::CACHE_CONTROL, "no-cache"), (header::HeaderName::from_static("x-powered-by"), "Nimble") ], StatusCode::NO_CONTENT, String::new() ) ``` Additionally, the following types automatically implement `IntoResponse`: - `&'static str` - `String` - `Vec` - `()` - `Result` where both `T` and `E` implement `IntoResponse` ## Static File Serving `Router::new()` automatically scans the `./static` folder in your project root and maps all files to routes. For example, with the following directory structure: ``` ├── static/ │ ├── css/ │ │ └── style.css │ ├── js/ │ │ └── app.js │ └── images/ │ └── logo.png └── src/ └── main.rs ``` After starting the application, you can access files via: - `http://localhost:3000/css/style.css` - `http://localhost:3000/js/app.js` - `http://localhost:3000/images/logo.png` ### File Download Static file routes support force download via the query parameter `?download=true`: ``` http://localhost:3000/images/logo.png?download=true ``` ## CORS Configuration Nimble provides flexible CORS (Cross-Origin Resource Sharing) support: ### Development Mode - One Line Setup ```rust use nimble_http::{Router, get}; #[tokio::main] async fn main() { Router::new() .route("/api/hello", get(|_| async { "Hello" })) .cors() // Allows all origins, perfect for development .run("127.0.0.1", 3000) .await; } ``` ### Production Mode - Fine-grained Control ```rust use nimble_http::{Router, get, Cors}; use std::time::Duration; #[tokio::main] async fn main() { let cors = Cors::new() .allow_origins(["https://myapp.com", "https://admin.myapp.com"]) .allow_methods(["GET", "POST", "PUT", "DELETE"]) .allow_headers(["Content-Type", "Authorization"]) .allow_credentials(true) .max_age(Duration::from_secs(3600)); // Cache preflight for 1 hour Router::new() .route("/api/user", get(get_user)) .with_cors(cors) .run("127.0.0.1", 3000) .await; } ``` ### How CORS Works When a browser makes a cross-origin request, it first sends an `OPTIONS` preflight request. Nimble automatically handles these and returns the appropriate CORS headers based on your configuration. ```rust // The CORS headers returned for a successful preflight request: // Access-Control-Allow-Origin: https://myapp.com // Access-Control-Allow-Methods: GET, POST // Access-Control-Allow-Headers: Content-Type, Authorization // Access-Control-Max-Age: 3600 // Access-Control-Allow-Credentials: true ``` ### Testing CORS with curl ```bash # Test preflight request curl -X OPTIONS http://localhost:3000/api/hello \ -H "Origin: https://myapp.com" \ -H "Access-Control-Request-Method: GET" \ -v # Test actual request curl http://localhost:3000/api/hello \ -H "Origin: https://myapp.com" \ -v ``` ### CORS Configuration Methods | Method | Description | Example | |--------|-------------|---------| | `.cors()` | Quick setup for development, allows all origins | `.cors()` | | `.with_cors(cors)` | Apply custom CORS configuration | `.with_cors(my_cors)` | ### Cors Builder Methods | Method | Description | Default | |--------|-------------|---------| | `.allow_origins(origins)` | Specify allowed origins | `None` (allows all) | | `.allow_methods(methods)` | Specify allowed HTTP methods | `["GET", "POST", "OPTIONS"]` | | `.allow_headers(headers)` | Specify allowed request headers | `["Content-Type", "Authorization"]` | | `.allow_credentials(true)` | Allow cookies/auth headers | `false` | | `.max_age(duration)` | Cache preflight response | `None` | ## Debug Logging Add `.debug()` to enable request logging and debugging features: ```rust use nimble_http::{Router, get}; #[tokio::main] async fn main() { Router::new() .route("/", get(|_| async { "Hello" })) .debug() // Enable logging and debugging .run("127.0.0.1", 8080) .await; } ``` ### Console Output When the application starts, the console displays: ``` Nimble server running on http://127.0.0.1:8080 Press Ctrl+C to stop Debug PIN: 845 ``` As requests come in, the console shows real-time logs: ``` 127.0.0.1 GET /debug -> 200 (OK) 127.0.0.1 GET /debug -> 200 (OK) 127.0.0.1 GET /favicon.ico -> 404 (Not Found) 127.0.0.1 GET /debug -> 200 (OK) 127.0.0.1 GET /favicon.ico -> 404 (Not Found) ``` ### Live Log Viewer When debug is enabled, you can access a live log viewer at `http://localhost:8080/debug?pin=845`. The page displays all request logs in real-time, matching the console output: ``` Nimble server running on http://127.0.0.1:8080 Press Ctrl+C to stop Debug PIN: 249 127.0.0.1 GET / -> 200 (OK) 127.0.0.1 GET /debug -> 401 (Unauthorized) 127.0.0.1 GET /debug -> 200 (OK) 127.0.0.1 GET /favicon.ico -> 404 (Not Found) ``` **Log Viewer Features:** - **Real-time log stream** - All requests appear live on the web page - **Secure access** - Requires the randomly generated PIN from server startup - **Dual output** - Logs appear in both console and web interface - **Auto-refresh** - New logs automatically appear at the top of the page ### Log Format Each log entry contains: - **Client IP** - Address of the requesting client (e.g., `127.0.0.1`) - **HTTP Method** - `GET`, `POST`, etc. - **Request Path** - URL path accessed (e.g., `/debug`) - **Status Code** - HTTP response status (e.g., `200`) - **Status Text** - Text description of the status (e.g., `OK`) ## Request Parameters ### GET Requests GET handlers receive a `HashMap` containing query string parameters from the URL. ```rust use std::collections::HashMap; async fn search(params: HashMap) -> impl IntoResponse { let query = params.get("q").unwrap_or(&"".to_string()); let page = params.get("page").and_then(|p| p.parse::().ok()).unwrap_or(1); Text(format!("Search: {}, Page: {}", query, page)) } Router::new().route("/search", get(search)); ``` ### POST Forms Regular POST handlers also receive a `HashMap`, with data from the `application/x-www-form-urlencoded` request body. ```rust async fn login(params: HashMap) -> impl IntoResponse { let username = params.get("username").cloned().unwrap_or_default(); let password = params.get("password").cloned().unwrap_or_default(); // Process login... Text("Login successful".to_string()) } ``` ### POST JSON Use `post_json` to automatically deserialize JSON request bodies into the specified type (must implement `Deserialize`). ```rust use serde::Deserialize; #[derive(Deserialize)] struct CreateUser { name: String, email: String, } async fn create_user(data: CreateUser) -> impl IntoResponse { // Use data.name and data.email Json(format!("Created user: {}", data.name)) } Router::new().route("/users", post_json(create_user)); ``` ## Error Handling By returning `Result`, you can easily handle errors, where both `T` and `E` must implement `IntoResponse`. ```rust use nimble_http::{Text, StatusCode}; async fn get_user() -> Result { // Simulate user lookup let user = find_user().await.ok_or(StatusCode::NOT_FOUND)?; Ok(Text(format!("Username: {}", user))) } Router::new().route("/profile", get(|_| get_user())); ``` ## License This project is licensed under: - MIT - Apache-2.0 Author: Wang Xiaoyu Email: wxy6987@outlook.com You are free to choose either license.