Rust 从Actix Web处理程序返回HTML或JSON

Rust 从Actix Web处理程序返回HTML或JSON,rust,actix-web,rust-actix,Rust,Actix Web,Rust Actix,我希望我的所有路由在被请求时返回Json而不是HTML 例如,当请求/index.json时,它应该返回生成/indexhtml响应的对象 我目前正在第11行的routing宏中使用“tail match”来处理此问题: 5 #[derive(Serialize)] 6 struct FollowerPresenter { 7 follower_name: String, 8 followee_name: String, 9 } 10 11 #[get(&

我希望我的所有路由在被请求时返回Json而不是HTML

例如,当请求
/index.json
时,它应该返回生成
/index
html响应的对象

我目前正在第11行的routing宏中使用“tail match”来处理此问题:

  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 }
5#[派生(序列化)]
6结构跟随者中心{
7跟随者名称:字符串,
8 followee_name:String,
9 }
10
11#[get(“/index{tail:.*}”)]
12异步fn索引(
13 mime:web::Path,
14模板:web::Data,
15)->结果{
16让演示者=FollowerPresenter{
17追随者的名字:“吉尔”。进入(),
18跟随名字:“吉姆”。进入(),
19     };
20
21匹配mime.as_str(){
22         "" | "/" => {
23让主体=模板
24.render(“index.html”、&Context::from_serialize(&presenter).unwrap())
25.映射错误(| |错误::ErrorInternalServerError(“模板错误”))?;
26
27 Ok(HttpResponse::Ok().content_type(“text/html”).body(body))
28         }
29.json“=>Ok(HttpResponse::Ok().json(presenter)),
30=>Err(error::ErrorNotFound(“未找到资源”),
31     }
32 }
33
34#[actix_web::main]
35异步fn main()->std::io::Result{
36 HttpServer::新建{
37让tera=tera::new(concat!(env!(“货物舱单目录”),“/src/views/***”)展开();
38
39 App::new().data(tera).service(索引)
40     })
41.绑定(“127.0.0.1:3000”)?
42.运行()
43.等待
44 }
必须有一种方法,使用中间件或其他东西,避免将整个控制结构放在每个处理程序的第21-30行。

好的,您的问题(以及我的答案)依赖于
req.query(“tail”)
,可以通过检查url本身来改进(您可以使用
str::ends\u with

下面是一个基于的工作解决方案


这是一个很好的答案。非常感谢。
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