# Nimble
一个简单优雅的 Rust Web 框架,灵感来自 Express,基于 Hyper。
## 特性
- **简单直观** - 类似 Express 的路由定义方式
- **基于 Hyper** - 建立在可靠的 HTTP 库之上
- **零成本抽象** - 使用 Rust 的强大类型系统
- **类型安全** - 编译时确保路由和处理器的类型正确
- **实用功能** - 内置 JSON、HTML、文件服务、重定向等响应类型
- **自动静态文件服务** - 自动挂载 `./static` 目录下的文件
## 快速开始
在 `Cargo.toml` 中添加依赖:
```toml
[dependencies]
nimble = { git = "https://github.com/yourusername/nimble" }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] } # 如果需要处理 JSON
```
创建一个简单的 Web 应用:
```rust
use nimble::{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()
// GET 根路径,返回 HTML
.route("/", get(|_| async {
Html("<h1>Hello World</h1>".to_string())
}))
// GET 返回 JSON
.route("/json", get(|_| async {
Json(vec!["苹果", "香蕉", "橙子"])
}))
// POST 处理表单(application/x-www-form-urlencoded)
.route("/user", post(|params| async move {
let name = params.get("name").unwrap_or(&"匿名".to_string()).clone();
Text(format!("Hello, {}!", name))
}))
// POST 处理 JSON
.route("/api/user", post_json(|user: User| async move {
Json(format!("创建用户: {},年龄: {}", user.name, user.age))
}))
// 重定向到百度
.route("/baidu", get(|_| async {
Redirect("https://www.baidu.com".to_string())
}));
// 启动服务器
app.run("127.0.0.1", 3000).await;
}
```
## 路由
Nimble 目前支持 `GET` 和 `POST` 方法,其中 POST 又分为普通表单和 JSON 两种形式。
```rust
use nimble::{get, post, post_json};
Router::new()
.route("/", get(handler_get))
.route("/submit", post(handler_post))
.route("/api/data", post_json(handler_post_json));
```
> **注意**:当前版本**不支持**路径参数(如 `/users/:id`),也不支持 `PUT`、`DELETE` 等方法。
## 响应类型
Nimble 提供了多种内置响应类型,均实现了 `IntoResponse` trait:
```rust
use nimble::{Html, Json, Text, Redirect, File, StatusCode};
// HTML 响应
Html("<h1>标题</h1>".to_string())
// JSON 响应(要求类型实现 Serialize)
Json(vec!["苹果", "香蕉", "橙子"])
// 纯文本响应
Text("Hello".to_string())
// 临时重定向 (302)
Redirect("https://example.com".to_string())
// 永久重定向 (301)
Redirect::perm("https://example.com".to_string())
// 文件响应(第一个参数为文件路径,第二个为是否强制下载)
File("static/image.jpg".to_string(), false) // 直接显示
File("file.zip".to_string(), true) // 作为附件下载
// 仅状态码(空响应)
StatusCode::NOT_FOUND
```
此外,以下类型也自动实现了 `IntoResponse`:
- `&'static str`
- `String`
- `Vec<u8>`
- `()`
- `Result<T, E>` 其中 `T` 和 `E` 都实现了 `IntoResponse`
## 静态文件服务
`Router::new()` 会自动扫描项目根目录下的 `./static` 文件夹,并将所有文件映射为路由。
例如,目录结构如下:
```
├── static/
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── app.js
│ └── images/
│ └── logo.png
└── src/
└── main.rs
```
启动应用后,可通过以下 URL 访问:
- `http://localhost:3000/css/style.css`
- `http://localhost:3000/js/app.js`
- `http://localhost:3000/images/logo.png`
### 文件下载
静态文件路由支持通过查询参数 `?download=true` 强制下载:
```
http://localhost:3000/images/logo.png?download=true
```
## 请求参数
### GET 请求
GET 处理函数接收一个 `HashMap<String, String>`,包含 URL 查询字符串中的参数。
```rust
use std::collections::HashMap;
async fn search(params: HashMap<String, String>) -> impl IntoResponse {
let query = params.get("q").unwrap_or(&"".to_string());
let page = params.get("page").and_then(|p| p.parse::<u32>().ok()).unwrap_or(1);
Text(format!("搜索: {},页码: {}", query, page))
}
Router::new().route("/search", get(search));
```
### POST 表单
普通 POST 处理函数同样接收 `HashMap<String, String>`,数据来自 `application/x-www-form-urlencoded` 格式的请求体。
```rust
async fn login(params: HashMap<String, String>) -> impl IntoResponse {
let username = params.get("username").cloned().unwrap_or_default();
let password = params.get("password").cloned().unwrap_or_default();
// 处理登录...
Text("登录成功".to_string())
}
```
### POST JSON
使用 `post_json` 可以自动将 JSON 请求体反序列化为指定的类型(需实现 `Deserialize`)。
```rust
use serde::Deserialize;
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
async fn create_user(data: CreateUser) -> impl IntoResponse {
// 使用 data.name 和 data.email
Json(format!("创建用户: {}", data.name))
}
Router::new().route("/users", post_json(create_user));
```
## 错误处理
通过返回 `Result<T, E>` 可以方便地处理错误,其中 `T` 和 `E` 都必须实现 `IntoResponse`。
```rust
use nimble::{Text, StatusCode};
async fn get_user() -> Result<Text, StatusCode> {
// 模拟用户查找
let user = find_user().await.ok_or(StatusCode::NOT_FOUND)?;
Ok(Text(format!("用户名: {}", user)))
}