Rust web 框架 axum (二): 返回 HTML form 表单,并处理 post 请求

文章目录

    要实现一个小的在线网页工具,最基本的功能就是能够返回一个 HTML form 表单,
    让用户能填写数据,然后提交到 rust axum 后台,再将处理后的数据展示出来。

    相对 PHP,Python,Go 的 web 框架,Rust 的 web 框架就晦涩很多,下面的示例如果换成其他语言,
    在没有任何基础的情况下,估计一小时也能搞定,但是使用 rust axum 这个框架,我还是折腾了一晚上。

    主要原因是:

    • axum 的文档太简陋。如果不是 ChatGPT 帮助,我估计给我一天也搞不定。Google 也搜不到太多的资料。
    • rust 语法晦涩,需要具备大量的基础知识

    下面示例实现了一些基本的使用场景:

    • 返回纯文本
    • 返回 JSON
    • 返回 HTML
    • 返回 HTML form 表单
    • 显示处理 form 提交数据后的页面

    示例代码

    use axum::{
        routing::get,
        Router,
        response::{Json, Html},
        Form,
    };
    use serde_json::{Value, json};
    use serde::Deserialize;
    
    
    #[tokio::main]
    async fn main() {
        // build our application with a single route
        let app = Router::new()
            .route("/", get(hello_text))
            .route("/json", get(hello_json))
            .route("/html", get(hello_html))
            .route("/form", get(render_form).post(handle_form_submit))
            ;
    
        println!("Serving on http://localhost:3000 ...");
        axum::Server::bind(&"127.0.0.1:3000".parse().unwrap())
            .serve(app.into_make_service())
            .await
            .unwrap();
    }
    
    
    // `&'static str` becomes a `200 OK` with `content-type: text/plain; charset=utf-8`
    async fn hello_text() -> &'static str {
        "Hello, World!"
    }
    
    
    // `Json` gives a content-type of `application/json` and works with any type
    // that implements `serde::Serialize`
    async fn hello_json() -> Json<Value> {
        Json(json!({ "domain": "www.sunzhongwei.com", "since": 1573 }))
    }
    
    
    // `Html` will get a `text/html` content-type
    async fn hello_html() -> Html<&'static str> {
        Html("
            <h1>Hello HTML</h1>
            <p>Hello, World!</p>
        ")
    }
    
    async fn render_form() -> Html<&'static str> {
        Html(r#"
            <html>
            <head>
                <title>Form Example</title>
            </head>
            <body>
                <h1>Form Example</h1>
                <form method="post">
                    <label for="field1">Field 1:</label>
                    <input type="text" name="field1" id="field1"><br>
    
                    <label for="field2">Field 2:</label>
                    <input type="text" name="field2" id="field2"><br>
    
                    <input type="submit" value="Submit">
                </form>
            </body>
            </html>
        "#)
    }
    
    #[derive(Deserialize)]
    struct FormData {
        field1: String,
        field2: String,
    }
    
    async fn handle_form_submit(Form(form_data): Form<FormData>) -> Html<String> {
        let response_html = format!(
            r#"
            <html>
            <head>
                <title>Form Submission Result</title>
            </head>
            <body>
                <h1>Form Submission Result</h1>
                <p>Field 1: {}</p>
                <p>Field 2: {}</p>
            </body>
            </html>
        "#,
            form_data.field1, form_data.field2
        );
    
        Html(response_html)
    }
    

    为了简化,直接返回了 HTML 字符串,而没有使用正常框架内置的 HTML 模板。

    可是 axum 也没有内置 HTML 模板功能,后续我将尝试三方模块库 askama,
    非常类似 python jinja 的一个模板库。

    编译运行

    cargo run
    

    然后在浏览器里输入

    http://localhost:3000/form
    

    即可看到 form 表单,并提交。如下图:

    rust axum html form

    rust axum html form

    下面是遇到的一些问题,及相关知识点。

    cannot find derive macro Deserialize in this scope

    error: cannot find derive macro `Deserialize` in this scope
      --> src/main.rs:73:10
       |
    73 | #[derive(Deserialize)]
       |          ^^^^^^^^^^^
       |
    note: `Deserialize` is imported here, but it is only a trait, without a derive macro
    

    问题出在我配置的 serde 依赖不对。有问题的 Cargo.toml 配置文件:

    [dependencies]
    axum = "0.6"
    serde = "1.0.192"
    serde_json = "1.0.108"
    tokio = { version = "1.0", features = ["full"] }
    

    其中的 serde 依赖,是我通过命令

    cargo add serde
    

    添加的。实际这是错误的,应该手动修改 Cargo.toml 文件,参考:

    https://serde.rs/derive.html

    正确的配置为:

    serde = { version = "1.0", features = ["derive"] }
    

    为何引入 serde 及 serde_json

    在 Rust 中,标准库(std)没有提供内置的 JSON 序列化库。
    可以使用第三方库 serde_json 来进行 JSON 的序列化和反序列化操作。
    serde_json 是 Rust 社区中最常用的 JSON 序列化库之一,
    它基于 serde 库提供了方便的 JSON 序列化和反序列化功能。

    为何返回的 html 字符串前加上 r

    • r”some string” 是 rust 的原始字符串,及不识别任何转义。可以在里面随便写反斜杠和空白字符。
    • r#“some string”# 则可以在字符串中使用双引号,而不需要转义。# 的数量可以为任意个,只要前后的 # 数量一致即可。

    查看合集

    📖 Rust web 框架 axum 教程:从入门到遥遥领先

    参考

    • https://docs.rs/axum/latest/axum/response/index.html

    关于作者 🌱

    我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊,或者关注我的个人公众号“大象工具”, 查看更多联系方式