表单数据 #

表单是Web应用中收集用户输入的主要方式。Rocket提供了强大的表单处理功能,支持自动解析和验证。

FromForm Trait #

FromForm 是Rocket中用于表单数据解析的核心trait。

基本用法 #

rust
use rocket::form::Form;
use rocket::FromForm;

#[derive(FromForm)]
struct LoginForm {
    username: String,
    password: String,
}

#[post("/login", data = "<form>")]
fn login(form: Form<LoginForm>) -> String {
    format!("Login: {} / {}", form.username, form.password)
}

表单提交 #

html
<form action="/login" method="POST">
    <input type="text" name="username" />
    <input type="password" name="password" />
    <button type="submit">Login</button>
</form>

字段类型 #

基本类型 #

rust
#[derive(FromForm)]
struct BasicTypes {
    text: String,
    number: i32,
    float: f64,
    boolean: bool,
}

可选字段 #

rust
#[derive(FromForm)]
struct OptionalFields {
    required: String,
    optional: Option<String>,
    number: Option<i32>,
}

默认值 #

rust
#[derive(FromForm)]
struct WithDefaults {
    name: String,
    
    #[field(default = 1)]
    page: i32,
    
    #[field(default = 10)]
    per_page: i32,
    
    #[field(default = "guest")]
    role: String,
}

字段属性 #

重命名字段 #

rust
#[derive(FromForm)]
struct RenamedFields {
    #[field(name = "user_name")]
    username: String,
    
    #[field(name = "user_email")]
    email: String,
}

嵌套表单 #

rust
#[derive(FromForm)]
struct Address {
    street: String,
    city: String,
    zip: String,
}

#[derive(FromForm)]
struct UserForm {
    name: String,
    email: String,
    address: Address,
}

表单数据格式:

text
name=John&email=john@example.com&address.street=123+Main+St&address.city=NYC&address.zip=10001

数组字段 #

简单数组 #

rust
#[derive(FromForm)]
struct TagsForm {
    #[field(name = "tag")]
    tags: Vec<String>,
}

表单数据:

text
tag=rust&tag=web&tag=rocket

对象数组 #

rust
#[derive(FromForm)]
struct Item {
    name: String,
    quantity: i32,
}

#[derive(FromForm)]
struct OrderForm {
    #[field(name = "items")]
    items: Vec<Item>,
}

表单数据:

text
items[0].name=Apple&items[0].quantity=5&items[1].name=Orange&items[1].quantity=3

表单验证 #

内置验证 #

rust
use rocket::form::{self, FromForm, Result};

#[derive(FromForm)]
struct ValidatedForm {
    #[field(validate = len(3..=20))]
    username: String,
    
    #[field(validate = len(8..))]
    password: String,
    
    #[field(validate = contains('@'))]
    email: String,
    
    #[field(validate = range(18..=120))]
    age: u8,
}

自定义验证 #

rust
use rocket::form::{self, FromForm, Result};

#[derive(FromForm)]
struct CustomValidatedForm {
    #[field(validate = validate_username)]
    username: String,
    
    #[field(validate = validate_password)]
    password: String,
}

fn validate_username(username: &str) -> form::Result<'_, ()> {
    if username.len() < 3 {
        return Err(form::Error::validation("Username too short").into());
    }
    if username.len() > 20 {
        return Err(form::Error::validation("Username too long").into());
    }
    if !username.chars().all(|c| c.is_alphanumeric() || c == '_') {
        return Err(form::Error::validation("Invalid characters").into());
    }
    Ok(())
}

fn validate_password(password: &str) -> form::Result<'_, ()> {
    if password.len() < 8 {
        return Err(form::Error::validation("Password too short").into());
    }
    Ok(())
}

条件验证 #

rust
#[derive(FromForm)]
struct ConditionalForm {
    account_type: String,
    
    #[field(validate = validate_company_name(self.account_type.clone()))]
    company_name: Option<String>,
}

fn validate_company_name(name: Option<&String>, account_type: String) -> form::Result<'_, ()> {
    if account_type == "business" && name.is_none() {
        return Err(form::Error::validation("Company name required for business accounts").into());
    }
    Ok(())
}

错误处理 #

捕获表单错误 #

rust
use rocket::form::Form;
use rocket::http::Status;

#[post("/submit", data = "<form>")]
fn submit(form: Result<Form<UserForm>, form::Error<'_>>) -> Result<String, Status> {
    match form {
        Ok(user) => Ok(format!("User: {}", user.name)),
        Err(e) => {
            eprintln!("Form error: {}", e);
            Err(Status::BadRequest)
        }
    }
}

自定义错误消息 #

rust
use rocket::form::{self, FromForm, Contextual};

#[derive(FromForm)]
struct RegisterForm {
    #[field(validate = len(3..=20).or_else(msg("Username must be 3-20 characters")))]
    username: String,
    
    #[field(validate = len(8..).or_else(msg("Password must be at least 8 characters")))]
    password: String,
}

#[post("/register", data = "<form>")]
fn register(form: Form<Contextual<RegisterForm>>) -> String {
    if let Some(ref err) = form.context {
        format!("Error: {}", err)
    } else {
        format!("Registered: {}", form.username)
    }
}

多部分表单 #

文件上传字段 #

rust
use rocket::form::Form;
use rocket::fs::TempFile;

#[derive(FromForm)]
struct UploadForm {
    name: String,
    file: TempFile<'static>,
}

#[post("/upload", data = "<form>")]
fn upload(mut form: Form<UploadForm>) -> String {
    format!("Uploaded '{}' ({:?})", form.name, form.file.name())
}

多文件上传 #

rust
#[derive(FromForm)]
struct MultiUploadForm {
    name: String,
    files: Vec<TempFile<'static>>,
}

#[post("/upload-multi", data = "<form>")]
fn upload_multi(form: Form<MultiUploadForm>) -> String {
    let count = form.files.len();
    format!("Uploaded {} files for '{}'", count, form.name)
}

完整示例 #

rust
#[macro_use] extern crate rocket;

use rocket::form::{Form, FromForm, Contextual};
use rocket::fs::TempFile;
use rocket::response::Redirect;

#[derive(FromForm)]
struct UserRegistration {
    #[field(validate = len(3..=20).or_else(msg("用户名需要3-20个字符")))]
    username: String,
    
    #[field(validate = len(8..).or_else(msg("密码至少8个字符")))]
    password: String,
    
    #[field(validate = contains('@').or_else(msg("邮箱格式不正确")))]
    email: String,
    
    #[field(default = 18)]
    age: u8,
    
    bio: Option<String>,
}

#[derive(FromForm)]
struct FileUpload {
    description: String,
    file: TempFile<'static>,
}

#[get("/register")]
fn register_form() -> &'static str {
    r#"
    <form action="/register" method="POST">
        <input name="username" placeholder="Username" />
        <input name="password" type="password" placeholder="Password" />
        <input name="email" type="email" placeholder="Email" />
        <input name="age" type="number" placeholder="Age" />
        <textarea name="bio" placeholder="Bio"></textarea>
        <button type="submit">Register</button>
    </form>
    "#
}

#[post("/register", data = "<form>")]
fn register(form: Form<Contextual<UserRegistration>>) -> String {
    if let Some(ref context) = form.context {
        let errors: Vec<_> = context.errors()
            .map(|e| e.to_string())
            .collect();
        format!("Errors: {:?}", errors)
    } else {
        let user = form.into_inner();
        format!("Registered: {} ({})", user.username, user.email)
    }
}

#[post("/upload", data = "<form>")]
fn upload(mut form: Form<FileUpload>) -> String {
    let filename = form.file.name().unwrap_or("unknown");
    let size = form.file.len();
    format!("Uploaded '{}' ({} bytes) - {}", filename, size, form.description)
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .mount("/", routes![register_form, register, upload])
}

测试示例 #

bash
# 注册用户
curl -X POST http://127.0.0.1:8000/register \
  -d "username=alice" \
  -d "password=password123" \
  -d "email=alice@example.com" \
  -d "age=25"

# 上传文件
curl -X POST http://127.0.0.1:8000/upload \
  -F "description=My file" \
  -F "file=@test.txt"

下一步 #

掌握了表单处理后,让我们继续学习 JSON处理,了解如何处理JSON请求数据。

最后更新:2026-03-28