Return HTML 或 JSON 来自 Actix Web 处理程序

Return Either HTML or JSON From Actix Web Handler

我希望在收到请求时我的所有路线都到 return Json 而不是 HTML。

例如,当请求 /index.json 时,它应该 return 构建 /index html 响应的对象。

我目前正在第 11 行的路由宏中使用“尾部匹配”处理此问题:

  5 #[derive(Serialize)]
  6 struct FollowerPresenter {
  7     follower_name: String,
  8     followee_name: String,
  9 }
 10
 11 #[get("/index{tail:.*}")]
 12 async fn index(
 13     mime: web::Path<String>,
 14     template: web::Data<tera::Tera>,
 15 ) -> Result<HttpResponse, Error> {
 16     let presenter = FollowerPresenter {
 17         follower_name: "Jill".into(),
 18         followee_name: "Jim".into(),
 19     };
 20
 21     match mime.as_str() {
 22         "" | "/" => {
 23             let body = template
 24                 .render("index.html", &Context::from_serialize(&presenter).unwrap())
 25                 .map_err(|_| error::ErrorInternalServerError("Template error"))?;
 26
 27             Ok(HttpResponse::Ok().content_type("text/html").body(body))
 28         }
 29         ".json" => Ok(HttpResponse::Ok().json(presenter)),
 30         _ => Err(error::ErrorNotFound("Resource Not Found")),
 31     }
 32 }
 33
 34 #[actix_web::main]
 35 async fn main() -> std::io::Result<()> {
 36     HttpServer::new(|| {
 37         let tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/src/views/**/*")).unwrap();
 38
 39         App::new().data(tera).service(index)
 40     })
 41     .bind("127.0.0.1:3000")?
 42     .run()
 43     .await
 44 }

必须有一种方法,使用中间件或其他东西,避免将整个控制结构放在每个处理程序的第 21-30 行。

你的问题(我的回答也是)依赖于 req.query("tail") 并且可以通过检查 url 本身(你可以在其中使用 str::ends_with)来改进。

这是一个基于 Responder 的可行解决方案。

use actix_http::{Error, Response};
use actix_web::*;
use futures_util::future::{err, ok, Ready};
use serde::Serialize;
use tera::{Context, Tera};

struct MyResponder<P: Serialize, T: Into<String>> {
    presenter: P,
    template: T,
}

impl<P: Serialize, T: Into<String>> Responder for MyResponder<P, T> {
    type Error = Error;
    type Future = Ready<Result<Response, Error>>;

    fn respond_to(self, req: &HttpRequest) -> Self::Future {
        let mime = req.match_info().query("tail");
        let template = req.app_data::<web::Data<tera::Tera>>().unwrap();
        let presenter = serde_json::to_value(&self.presenter).unwrap();
        match mime {
            "" | "/" => {
                let body = template
                    .render(
                        &self.template.into(),
                        &Context::from_serialize(&presenter).unwrap(),
                    )
                    .unwrap();
                ok(HttpResponse::Ok().content_type("text/html").body(body))
            }
            ".json" => ok(HttpResponse::Ok().json(&presenter)),
            _ => err(error::ErrorNotFound("Resource Not Found")),
        }
    }
}

#[derive(Serialize)]
struct FollowerPresenter {
    follower_name: String,
    followee_name: String,
}

#[get("/index{tail:.*}")]
async fn index() -> impl Responder {
    let presenter = FollowerPresenter {
        follower_name: "Jill".into(),
        followee_name: "Jim".into(),
    };

    MyResponder {
        presenter,
        template: "index.html",
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        let tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/src/views/**/*")).unwrap();
        App::new().data(tera).service(index)
    })
    .bind("127.0.0.1:3000")?
    .run()
    .await
}

#[cfg(test)]
mod tests {
    use super::*;
    use actix_web::{body::Body, test, App};
    use serde_json::json;

    #[actix_rt::test]
    async fn test_json_get() {
        let tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/src/views/**/*")).unwrap();
        let mut app = test::init_service(App::new().data(tera).service(index)).await;
        let req = test::TestRequest::with_uri("/index.json").to_request();
        let mut resp = test::call_service(&mut app, req).await;
        let body = resp.take_body();
        let body = body.as_ref().unwrap();
        assert!(resp.status().is_success());
        assert_eq!(
            &Body::from(json!({"follower_name":"Jill", "followee_name": "Jim" })), // or serde.....
            body
        );
    }
    #[actix_rt::test]
    #[should_panic] /// Template doesnt exist
    async fn test_web_get() {
        let tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/src/views/**/*")).unwrap();
        let mut app = test::init_service(App::new().data(tera).service(index)).await;
        let req = test::TestRequest::with_uri("/index").to_request();
        let _resp = test::call_service(&mut app, req).await;

    }
}

运行 测试结果:

running 2 tests
test tests::test_json_get ... ok
test tests::test_web_get ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out