Skip to content

secheng722/s_web

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

100 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

s_web

πŸ“– δΈ­ζ–‡ζ–‡ζ‘£: README_CN.md

A modern, lightweight Rust HTTP framework built on top of Hyper.
Designed around plain async functions β€” no macros, no boilerplate, no magic.

[dependencies]
s_web  = { git = "https://github.com/secheng722/s_web" }
tokio  = { version = "1", features = ["full"] }
serde_json = "1"

Features

Zero-boilerplate handlers Return &str, String, serde_json::Value, (StatusCode, T), Result, Option directly
Functional middleware Plain async fn(ctx, next) -> Response β€” no traits, no wrappers
Route groups Prefix-scoped groups with per-group middleware
Lifecycle hooks on_startup / on_shutdown for resource init & cleanup
Swagger UI Built-in /docs/ UI with swagger() builder for documentation
Graceful shutdown Ctrl-C signal handling with configurable drain timeout

Quick Start

use s_web::{Engine, RequestCtx};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut app = Engine::new();

    app.get("/",           |_: RequestCtx| async { "Hello, World!" });
    app.get("/hello/:name", |ctx: RequestCtx| async move {
        format!("Hello, {}!", ctx.get_param("name").map(|s| s.as_str()).unwrap_or("stranger"))
    });
    app.get("/json", |_: RequestCtx| async {
        json!({ "framework": "s_web", "status": "ok" })
    });

    app.run("127.0.0.1:3000").await
}

Middleware

Middleware is a plain async function β€” capture whatever you need via closures:

use s_web::{Engine, Next, RequestCtx, Response};
use std::time::Instant;

async fn logging(ctx: RequestCtx, next: Next) -> Response {
    let path  = ctx.request.uri().path().to_owned();
    let start = Instant::now();
    let resp  = next(ctx).await;
    println!("{} β†’ {} ({:.1}ms)", path, resp.status(), start.elapsed().as_secs_f64() * 1000.0);
    resp
}

async fn require_api_key(ctx: RequestCtx, next: Next) -> Response {
    use s_web::{IntoResponse, StatusCode};
    use serde_json::json;
    match ctx.header("x-api-key") {
        Some(k) if k == "secret" => next(ctx).await,
        _ => (StatusCode::UNAUTHORIZED, json!({ "error": "invalid API key" })).into_response(),
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut app = Engine::new();

    // Global middleware
    app.use_middleware(logging);

    // Route group with its own middleware
    {
        let api = app.group("/api/v1");
        api.use_middleware(require_api_key);
        api.get("/users", |_: RequestCtx| async { serde_json::json!(["alice", "bob"]) });
    }

    app.run("127.0.0.1:3000").await
}

Route Groups

{
    let admin = app.group("/admin");
    admin.use_middleware(require_api_key);
    admin.get("/dashboard", |_: RequestCtx| async { "Dashboard" });
    admin.delete("/users/:id", |ctx: RequestCtx| async move {
        format!("deleted {}", ctx.get_param("id").unwrap())
    });
}

Request Body

// JSON
app.post("/users", |mut ctx: RequestCtx| async move {
    #[derive(serde::Deserialize)]
    struct Payload { name: String }

    let p: Payload = ctx.json().await?;
    Ok::<_, Box<dyn std::error::Error + Send + Sync>>(format!("Hello, {}", p.name))
});

// Query params
app.get("/search", |ctx: RequestCtx| async move {
    let q = ctx.query_param("q").unwrap_or_default();
    format!("search: {q}")
});

Lifecycle Hooks

let app = Engine::new()
    .on_startup(|| async { println!("DB connected") })
    .on_shutdown(|| async { println!("DB closed") });

Swagger UI

use s_web::{Engine, RequestCtx, swagger};
use serde_json::json;

let mut app = Engine::new();
app.enable_swagger();   // mounts /docs/ and /docs/swagger.json

app.get_with_swagger(
    "/users",
    |_: RequestCtx| async { json!([]) },
    swagger()
        .summary("List users")
        .tag("Users")
        .json_response("200", "User list", Some(json!([])))
        .build(),
);
// Open http://127.0.0.1:3000/docs/

Custom Responses

use s_web::{ResponseBuilder, StatusCode};

// HTML
ResponseBuilder::html("<h1>Hello</h1>");

// Full control
ResponseBuilder::new()
    .status(StatusCode::CREATED)
    .content_type("application/json; charset=utf-8")
    .header("X-Request-Id", "abc123")
    .body(r#"{"id":1}"#);

Examples

Eight runnable projects, each a self-contained Cargo package β€” copy any one out and use it standalone.

# Directory Topics Run
1 01_hello_world Routes, path params, HTML cargo run -p example_hello_world
2 02_json_api JSON, query params, POST body cargo run -p example_json_api
3 03_middleware Global & group middleware, auth cargo run -p example_middleware
4 04_todo_app Shared state, full CRUD, lifecycle hooks cargo run -p example_todo_app
5 05_swagger Swagger UI, bearer auth docs cargo run -p example_swagger
6 06_sqlx_sqlite_crud sqlx + SQLite, connection pool cargo run -p example_sqlx_sqlite_crud
7 07_seaorm_sqlite_crud SeaORM entity, auto migration cargo run -p example_seaorm_sqlite_crud
8 08_mini_blog Multi-file mini blog, layered architecture, sqlx + SQLite cargo run -p mini_blog

Contributing

Pull requests are welcome.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages