Learning how to send HTTP responses from Actix-web to clients will help us craft the responses according to our needs. For instance, we could send JSON responses for specific URIs. Although we could just set the response content type and send text contents, some use-cases require sending bytes instead.
Cargo.toml
We have these dependencies, and we are using Rust 1.44.0.
1 2 3 4 5 6 7 8 9 10 11 | [package] name = "actix-web-http-responses" version = "0.1.0" authors = ["Karl San Gabriel"] edition = "2018" [dependencies] actix-web = "2.0.0" actix-rt = "1.1.1" serde = { version = "1.0.114", features = ["derive"] } actix-files = "0.2.2" |
Send HTTP Body Response – HTML, XML, JSON
To send an HTTP response with HTML body from Actix-Web, we need to set the Content-Type to text/html with HTML codes.
1 2 3 4 5 6 | #[get("/html")] async fn html() -> impl Responder { HttpResponse::Ok() .content_type("text/html") .body("<html><head><title>Turreta.com</title></head><body><h1>Hello from Turreta.com</h1></body></html>") } |
The request handler generates the following output.
In the same token, we set the Content-Type accordingly for JSON and XML.
For XML
For XML, we use application/xml.
1 2 3 4 5 6 7 | #[get("/xml")] async fn xml() -> HttpResponse { HttpResponse::Ok() .content_type("application/xml") .body("<?xml version=\"1.0\" encoding=\"UTF-8\"?><note><to>Tove</to><from>Jani</from> <heading>Reminder</heading><body>Don't forget me this weekend!</body></note>") } |
The codes return the following response to the browser.
For JSON
For JSON, we use application/json.
1 2 3 4 5 6 | #[get("/json1")] async fn json1() -> HttpResponse { HttpResponse::Ok() .content_type("application/json") .body("{\"name\":\"Karl\"}") } |
Alternatively, we can use Serde to generate JSON from struct instances automatically.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | ... #[derive(Serialize)] struct Country { country_code: String, country_name: String, } ... #[get("/json2")] async fn json2() -> impl Responder { let mut vec: Vec<Country> = Vec::new(); vec.push(Country { country_code: "PH".to_string(), country_name: "Philippines".to_string() }); vec.push(Country { country_code: "MY".to_string(), country_name: "Malaysia".to_string() }); vec.push(Country { country_code: "ID".to_string(), country_name: "Indonesia".to_string() }); return web::Json(vec); } ... |
The browser will display the following.
Any File Using Actix-Web NamedFile
We can send the content of any file as part of the response in Actix-web using NamedFile. NameFile comes with the Actix-file crate, for example.
1 2 3 4 5 | #[get("/pdf")] async fn pdf() -> Result<NamedFile> { let path = Path::new("C:\\Users\\karldev\\Desktop\\trpl2.pdf"); Ok(NamedFile::open(path)?) } |
Send File As Bytes in HTTP Responses
Sometimes a file’s content is stored in a database as a blob data. In such a case, we only bytes data in memory. We could either first write the data to a file and use NamedFile or send them as bytes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #[get("/zip2")] async fn zip2() -> HttpResponse { // File content may come from a database as a blob data let mut f = File::open("C:\\Users\\karldev\\Desktop\\angular.zip").unwrap(); let mut buffer = Vec::new(); // read the whole file f.read_to_end(&mut buffer); HttpResponse::Ok() .encoding(ContentEncoding::Identity) .content_type("application/zip") .header("accept-ranges", "bytes") .header("content-disposition", "attachment; filename=\"download-angular.zip\"") .body(buffer) } |
When we access the URL, we would get the following.
The Complete Rust Codes
The followings are the imports.
1 2 3 4 5 6 7 8 9 | use actix_web::{App, get, HttpServer, Responder, web, HttpResponse, Result}; use serde::Serialize; use actix_files::NamedFile; use std::path::Path; use std::fs::File; use actix_web::http::ContentEncoding; use actix_web::dev::BodyEncoding; use std::io::Read; ... |
We need a struct to work with Serde to convert an instance to a JSON automatically.
1 2 3 4 5 6 7 | ... #[derive(Serialize)] struct Country { country_code: String, country_name: String, } ... |
Then, we have the request handlers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | ... #[get("/xml")] async fn xml() -> HttpResponse { HttpResponse::Ok() .content_type("application/xml") .body("<?xml version=\"1.0\" encoding=\"UTF-8\"?><note><to>Tove</to><from>Jani</from> <heading>Reminder</heading><body>Don't forget me this weekend!</body></note>") } #[get("/json1")] async fn json1() -> HttpResponse { HttpResponse::Ok() .content_type("application/json") .body("{\"name\":\"Karl\"}") } #[get("/json2")] async fn json2() -> impl Responder { let mut vec: Vec<Country> = Vec::new(); vec.push(Country { country_code: "PH".to_string(), country_name: "Philippines".to_string() }); vec.push(Country { country_code: "MY".to_string(), country_name: "Malaysia".to_string() }); vec.push(Country { country_code: "ID".to_string(), country_name: "Indonesia".to_string() }); return web::Json(vec); } #[get("/html")] async fn html() -> impl Responder { HttpResponse::Ok() .content_type("text/html") .body("<html><head><title>Turreta.com</title></head><body><h1>Hello from Turreta.com</h1></body></html>") } #[get("/pdf")] async fn pdf() -> Result<NamedFile> { let path = Path::new("C:\\Users\\karldev\\Desktop\\trpl2.pdf"); Ok(NamedFile::open(path)?) } #[get("/zip")] async fn zip() -> Result<NamedFile> { let path = Path::new("C:\\Users\\karldev\\Downloads\\drive-download-20200629T031736Z-001.zip"); Ok(NamedFile::open(path)?) } #[get("/zip2")] async fn zip2() -> HttpResponse { // File content may come from a database as a blob data let mut f = File::open("C:\\Users\\karldev\\Desktop\\angular.zip").unwrap(); let mut buffer = Vec::new(); // read the whole file f.read_to_end(&mut buffer); HttpResponse::Ok() .encoding(ContentEncoding::Identity) .content_type("application/zip") .header("accept-ranges", "bytes") .header("content-disposition", "attachment; filename=\"download-angular.zip\"") .body(buffer) } ... |
Lastly, we have the main function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ... #[actix_rt::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| App::new() .service(xml) .service(json1) .service(json2) .service(html) .service(pdf) .service(zip) ) .bind("127.0.0.1:8080")? .run() .await } |