How to support content negotiation with Actix Web?

419 Views Asked by At

My Rust Actix Web application provides multiple routes to the same resource with different content types. The example below works fine with curl localhost:8080/index -H "Accept:text/html but not in the browser (tested with Firefox developer edition), because that sends Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8.

Is there a way to comfortably handle real-world accept headers such as those, including wildcards and priority scores with the "q" attribute, or do I have to implement this logic myself?

use actix_web::{guard, web, App, HttpResponse, HttpServer};                   

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new().service(
            web::resource("/index")
                .route(
                    web::get()  
                        .guard(guard::Header("Accept", "text/html"))
                        .to(|| async {
                            HttpResponse::Ok()
                                .content_type("text/html")
                                .body("<html><body>hello html</body></html>")
                        }),                                            
                )
                .route(
                    web::get()
                        .guard(guard::Header("Accept", "text/plain"))
                        .to(|| async {
                            HttpResponse::Ok()
                                .content_type("text/plain")
                                .body("hello text")
                        }),
                ),
        )
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

To clarify the expected behaviour:

  • the application defines multiple routes with contents C1...Cn with content types T1...Tn in order of preference
  • the client (e.g. web browser) sends a get request
  • if there is no accept header in the get request, return C1 with content type T1
  • if there is an accept header but there is no matching content type including wildcards, return an error response
  • if there are multiple matching content types, return the one with the highest q score, assume 1 if not given
  • if there are multiple matching content types with the same highest q score, return the one with the lowest index (highest preference by the application)

I could implement this logic myself but given that this seems to be a common case, I was wondering if there is some established method in place that already handles this (or a similar) use case with Actix Web.

1

There are 1 best solutions below

0
Aitch On

I guess you have to write your own guard, but you don't have to parse the header yourself. https://docs.rs/actix-web/latest/actix_web/guard/struct.GuardContext.html#method.header

// from https://docs.rs/actix-web/latest/actix_web/guard/struct.GuardContext.html#method.header
use actix_web::{guard::fn_guard, http::header};

let image_accept_guard = fn_guard(|ctx| {
    match ctx.header::<header::Accept>() {
        Some(hdr) => hdr.preference() == "image/*",
        None => false,
    }
});