{"id":20544,"date":"2024-03-28T02:27:56","date_gmt":"2024-03-28T06:27:56","guid":{"rendered":"https:\/\/qxf2.com\/blog\/?p=20544"},"modified":"2024-03-28T02:27:56","modified_gmt":"2024-03-28T06:27:56","slug":"http-requests-using-rust-reqwest","status":"publish","type":"post","link":"https:\/\/qxf2.com\/blog\/http-requests-using-rust-reqwest\/","title":{"rendered":"HTTP requests using Rust Reqwest"},"content":{"rendered":"<h3>About Reqwest<\/h3>\n<p>\tAt <a href=\"https:\/\/qxf2.com\/?utm_source=reqwest_crate&#038;utm_medium=click&#038;utm_campaign=From%20blog\" rel=\"noopener\" target=\"_blank\">Qxf2<\/a>, we have always been curious to constantly learn and discover new possibilities and insights. This led us to explore the <a href=\"https:\/\/docs.rs\/reqwest\/latest\/reqwest\/\" rel=\"noopener\" target=\"_blank\">Reqwest<\/a> crate, a standout tool for Rust, further enriching our technical toolkit. Reqwest is a client solution for Rust for making asynchronous HTTP requests. It provides API&#8217;s for interacting with various HTTP operations. It&#8217;s ease of use and its key features and capabilities make it a popular choice for handling HTTP communication. By default it supports HTTPS and ensures secure communication, handles timeouts and automatic retries. In this article, we will showcase few basic capabilities of reqwest library. <\/p>\n<h3>Our Application<\/h3>\n<p>For this post, we&#8217;ll utilize our <a href=\"https:\/\/cars-app.qxf2.com\/\" rel=\"noopener\" target=\"_blank\">Cars API Web Application<\/a> specifically designed for our internal API Automation training. The application has endpoints for you to practice automating GET, POST, PUT and DELETE methods. In the following sections, we&#8217;ll use the GET (get cars list) and POST (add new car) requests on the Cars API using Reqwest crate.<\/p>\n<h3>Installation<\/h3>\n<p>To use Reqwest, you must include <a href=\"https:\/\/docs.rs\/reqwest\/latest\/reqwest\/\" rel=\"noopener\" target=\"_blank\">Reqwest<\/a> and <a href=\"https:\/\/docs.rs\/tokio\/latest\/tokio\/\" rel=\"noopener\" target=\"_blank\">Tokio<\/a> libraries in your Cargo.toml file. Tokio serves as the asynchronous runtime used under the hood of reqwest. Cargo will install them when you build your program.<\/p>\n<pre lang=\"Rust\">\r\n[dependencies]\r\nreqwest = { version = \"0.11\", features = [\"json\"] }\r\ntokio = { version = \"1.12.0\", features = [\"full\"] } \r\nserde = \"1.0\"\r\nserde_derive = \"1.0\"\r\nserde_json = \"1.0\"\r\ntoml = \"0.5.10\"\r\n<\/pre>\n<h3>Making a GET request<\/h3>\n<p>Lets use a <a href=\"https:\/\/docs.rs\/reqwest\/latest\/reqwest\/fn.get.html\" rel=\"noopener\" target=\"_blank\">GET<\/a> request to retrieve the cars data from the server. The provided code snippet demonstrates a simple Rust program that fetches information about cars from the server. The <em>get_cars()<\/em> function makes a GET request to the specified URL (https:\/\/cars-app.qxf2.com\/cars) with basic authentication and retrieves the response body as json. The <code><strong>async\/await<\/strong><\/code> ensures continuous execution.<\/p>\n<pre lang=\"Rust\">\r\nasync fn get_cars(username: &str, password: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> {\r\n    \/\/ Read the content of the \"config.toml\" file into a string\r\n    let client = reqwest::Client::new();\r\n    let response = match client.get(\"https:\/\/cars-app.qxf2.com\/cars\")\r\n        .basic_auth(username, Some(password))      \r\n        .timeout(Duration::from_secs(10))  \r\n        .send()\r\n        .await {\r\n            Ok(res) => {                \r\n                \/\/ Check for specific status codes\r\n                if res.status().is_client_error() {\r\n                    Err(format!(\"Server returned a client error: {}\", res.status()).into())\r\n                } else if res.status().is_server_error() {\r\n                    return Err(format!(\"Server returned a error, check your request data: {}\", res.status()).into());\r\n                }               \r\n                else {\r\n                    Ok(res)\r\n                }\r\n            }\r\n            Err(err) => {\r\n                \/\/ Handle specific error types\r\n                if err.is_connect() {\r\n                    return Err(\"Failed to connect to the server. Please make sure the server is running.\".into());\r\n                }\r\n                else if err.is_timeout() {\r\n                    return Err(format!(\"Request timed out: {}\", err).into());\r\n                } else {\r\n                    return Err(Box::new(err));\r\n                }\r\n            }\r\n    };\r\n\r\n    \/\/ Handle the response and extract JSON\r\n    let result_json: serde_json::Value = match response {\r\n        Ok(res) => match res.json().await {\r\n            Ok(json) => json,\r\n            Err(err) => return Err(Box::new(err)),\r\n        },\r\n        Err(err) => return Err(err),\r\n    };\r\n \r\n    Ok(result_json)\r\n}\r\n  \r\n<\/pre>\n<p>This function uses a configuration file &#8220;<strong>config.toml<\/strong>&#8221; to store the username and password details. It performs an HTTP GET request with basic authentication and extracts the username and password from the Config struct. Upon a successful request (returning Ok(res)), we check for specific HTTP status codes in the response. We have implemented error-handling mechanism to manage various error scenarios and ensure that the system responds appropriately to different types of issues. Specifically, our error-handling includes <\/p>\n<li>Client Errors (4xx status codes) like invalid requests or unauthorized access attempts<\/li>\n<li>Server Errors (5xx status codes) like unexpected failures in server-side processing <\/li>\n<li>Timeout Errors for requests exceeding time limits<\/li>\n<li>Connection Errors in cases of failed server connections<\/li>\n<p>If reading the response text is successful (returns Ok(json)), store it in the body variable. If there is an error while reading the response (returns Err(err)), return the error wrapped in a Box.<\/p>\n<p>The result looks below<\/p>\n<figure id=\"attachment_21712\" aria-describedby=\"caption-attachment-21712\" style=\"width: 726px\" class=\"wp-caption alignleft\"><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/03\/cars_api_get_response.png\" data-rel=\"lightbox-image-0\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/03\/cars_api_get_response.png\" alt=\"Cars api get response json\" width=\"726\" height=\"598\" class=\"size-full wp-image-21712\" srcset=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/03\/cars_api_get_response.png 726w, https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/03\/cars_api_get_response-300x247.png 300w\" sizes=\"auto, (max-width: 726px) 100vw, 726px\" \/><\/a><figcaption id=\"caption-attachment-21712\" class=\"wp-caption-text\">Cars api get response json<\/figcaption><\/figure>\n<h3>Making a POST request<\/h3>\n<p>Moving on to the <a href=\"https:\/\/docs.rs\/reqwest\/latest\/reqwest\/index.html#making-post-requests-or-setting-request-bodies\" rel=\"noopener\" target=\"_blank\">POST<\/a> request, <em>add_cars()<\/em> function is responsible for adding cars to a server through an HTTP POST request. Basic authentication is applied, and the request includes a JSON payload (provided as the json_data parameter). The content type header is set to &#8220;application\/json&#8221;, to indicate that the payload is in JSON format. The function returns a Result containing either the response body (if successful) or an error (if any step encountered an error). The error is wrapped in a Box<dyn std::error::Error> for handling various error types.<\/p>\n<pre lang=\"Rust\">\r\n#[tokio::main]\r\nasync fn add_cars(username: &str, password: &str, json_data: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> {\r\n    let client = reqwest::Client::new();\r\n\r\n    let response = match client.post(\"https:\/\/cars-app.qxf2.com\/cars\/add\")\r\n        .basic_auth(username, Some(password))\r\n        .timeout(Duration::from_secs(10))\r\n        .header(reqwest::header::CONTENT_TYPE, \"application\/json\")\r\n        .body(json_data.to_string())\r\n        .send()\r\n        .await {\r\n            Ok(res) => {\r\n                \/\/ Check for specific status codes\r\n                if res.status().is_client_error() {\r\n                    return Err(format!(\"Server returned a client error: {}\", res.status()).into());\r\n                }\r\n                else if res.status().is_server_error() {\r\n                    return Err(format!(\"Server returned a error, check your request data: {}\", res.status()).into());\r\n                } else {\r\n                Ok(res)\r\n            }\r\n            }\r\n            Err(err) => {\r\n                \/\/ Handle specific error types\r\n                if err.is_connect() {\r\n                    return Err(\"Failed to connect to the server. Please make sure the server is running.\".into());\r\n                } else if err.is_timeout() {\r\n                    return Err(format!(\"Request timed out: {}\", err).into());\r\n                }\r\n                else {\r\n                    return Err(Box::new(err));\r\n                }\r\n            }\r\n    };\r\n\r\n    \/\/ Handle the response and extract JSON\r\n    let result_json: serde_json::Value = match response {\r\n        Ok(res) => match res.json().await {\r\n            Ok(json) => json,\r\n            Err(err) => return Err(Box::new(err)),\r\n        },\r\n        Err(err) => return Err(err),\r\n    };\r\n \r\n    Ok(result_json)\r\n}<\/pre>\n<p>The output looks like this.<br \/>\n<figure id=\"attachment_21717\" aria-describedby=\"caption-attachment-21717\" style=\"width: 503px\" class=\"wp-caption alignleft\"><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/03\/cars_api_post_response.png\" data-rel=\"lightbox-image-1\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/03\/cars_api_post_response.png\" alt=\"Cars api post response json\" width=\"503\" height=\"195\" class=\"size-full wp-image-21717\" srcset=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/03\/cars_api_post_response.png 503w, https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/03\/cars_api_post_response-300x116.png 300w\" sizes=\"auto, (max-width: 503px) 100vw, 503px\" \/><\/a><figcaption id=\"caption-attachment-21717\" class=\"wp-caption-text\">Cars api post response json<\/figcaption><\/figure><br \/>\n<\/br><br \/>\n<\/br><br \/>\n<\/br><br \/>\n<\/br><br \/>\n<\/br><\/p>\n<p>Here is the main.rs function illustrates the asynchronous execution of two functions: one for retrieving cars <em><strong>get_cars<\/strong>()<\/em> and another for adding a new car <em><strong>add_cars<\/strong>()<\/em>. The two helper functions<br \/>\n<code>validate_get_response<\/code> Validates the JSON response from the GET request to ensure it conforms to the expected structure (CarsResponse), including checking for required fields in each car item.<br \/>\n<code>validate_add_response<\/code> Validates the JSON response from the POST request for a &#8220;successful&#8221; field that indicates whether the addition was successful. You can find the complete code <a href=\"https:\/\/gist.github.com\/indiranell\/de278fccde27f095ee7563afbf24690e\" rel=\"noopener\" target=\"_blank\">here<\/a><\/p>\n<h4>main.rs<\/h4>\n<pre lang=\"Rust\">\r\n\/\/Rust script using reqwests to make Get and Post call to cars api to get and add cars\r\nuse std::fs;\r\nuse serde_derive::{Deserialize, Serialize};\r\nuse std::time::Duration;\r\nuse serde_json::Value as JsonValue;\r\n\r\n#[derive(Debug, Deserialize)]\r\nstruct ApiConfig {\r\n    username: String,\r\n    password: String,\r\n}\r\n#[derive(Debug, Deserialize)]\r\nstruct Config {\r\n    api: ApiConfig,\r\n}\r\n#[derive(Debug, Deserialize, Serialize)]\r\nstruct Car {\r\n    brand: String,\r\n    car_type: String,\r\n    name: String,\r\n    price_range: String,\r\n}\r\n#[derive(Debug, Deserialize, Serialize)]\r\nstruct CarsResponse {\r\n    cars_list: Vec<Car>,\r\n    successful: bool,\r\n}\r\n#[tokio::main]\r\nasync fn main() {\r\n    let config_str = fs::read_to_string(\"config.toml\").expect(\"Failed to read config file\");\r\n    \/\/ convert string into a Config struct\r\n    let config: Config = toml::from_str(&config_str).expect(\"Failed to parse config\");\r\n    \/\/ Read the username and password\r\n    let username = &config.api.username;\r\n    let password = &config.api.password;\r\n    match get_cars(username, password).await {\r\n        Ok(json_response) => {\r\n            if validate_get_response(&json_response) {\r\n                println!(\"Cars response is valid.\");\r\n                println!(\"Cars: {:#?}\", json_response);\r\n            } else {\r\n                println!(\"Invalid cars response structure.\");\r\n            }\r\n        }\r\n        Err(err) => println!(\"Error: {}\", err),\r\n    }\r\n\r\n    let json_data = r#\"\r\n        {\r\n            \"name\": \"Figo\",\r\n            \"brand\": \"Ford\",\r\n            \"price_range\": \"5-6lacs\",\r\n            \"car_type\": \"hatchback\"\r\n        }\r\n    \"#;\r\n\r\n    match add_cars(username, password, json_data).await {\r\n        Ok(response) => {\r\n            if validate_add_response(&response) {\r\n                println!(\"Car addition successful.\");\r\n                println!(\"Response: {:#?}\", response);\r\n            } else {\r\n                println!(\"Invalid car addition response structure.\");\r\n            }\r\n        }\r\n        Err(err) => println!(\"Error: {}\", err),\r\n    }\r\n}\r\n\r\nfn validate_get_response(cars_response: &JsonValue) -> bool {\r\n    \/\/ Validate that the JSON response has the expected structure\r\n    if let Ok(parsed_response) = serde_json::from_value::<CarsResponse>(cars_response.clone()) {\r\n        \/\/ Check if each Car object in cars_list has all required fields\r\n        for car in &parsed_response.cars_list {\r\n            if car.brand.is_empty() || car.car_type.is_empty() || car.name.is_empty() || car.price_range.is_empty() {\r\n                return false;\r\n            }\r\n        }\r\n        true\r\n    } else {\r\n        false \r\n    }\r\n}\r\n\r\nfn validate_add_response(response: &serde_json::Value) -> bool {\r\n    \/\/ Check if the response contains the successful response\r\n    if let Some(successful) = response.get(\"successful\") {\r\n        if let Some(successful_bool) = successful.as_bool() {\r\n            return successful_bool;\r\n        }\r\n    }\r\n    false \/\/ If the expected field or type is missing, consider the response as invalid\r\n}\r\n\r\nasync fn get_cars(username: &str, password: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> {\r\n    \/\/ Read the content of the \"config.toml\" file into a string\r\n\r\n    let client = reqwest::Client::new();\r\n    let response = match client.get(\"http:\/\/localhost:5000\/cars\")\r\n        .basic_auth(username, Some(password))      \r\n        .timeout(Duration::from_secs(10))  \r\n        .send()\r\n        .await {\r\n            Ok(res) => {                \r\n                \/\/ Check for specific status codes\r\n                if res.status().is_client_error() {\r\n                    Err(format!(\"Server returned a client error: {}\", res.status()).into())\r\n                } else if res.status().is_server_error() {\r\n                    return Err(format!(\"Server returned a error, check your request data: {}\", res.status()).into());\r\n                }               \r\n                else {\r\n                    Ok(res)\r\n                }\r\n            }\r\n            Err(err) => {\r\n                \/\/ Handle specific error types\r\n                if err.is_connect() {\r\n                    return Err(\"Failed to connect to the server. Please make sure the server is running.\".into());\r\n                }\r\n                else if err.is_timeout() {\r\n                    return Err(format!(\"Request timed out: {}\", err).into());\r\n                } else {\r\n                    return Err(Box::new(err));\r\n                }\r\n            }\r\n    };\r\n\r\n    \/\/ Handle the response and extract JSON\r\n    let result_json: serde_json::Value = match response {\r\n        Ok(res) => match res.json().await {\r\n            Ok(json) => json,\r\n            Err(err) => return Err(Box::new(err)),\r\n        },\r\n        Err(err) => return Err(err),\r\n    };\r\n \r\n    Ok(result_json)\r\n}\r\n\r\n\r\nasync fn add_cars(username: &str, password: &str, json_data: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> {\r\n    let client = reqwest::Client::new();\r\n\r\n    let response = match client.post(\"http:\/\/localhost:5000\/cars\/add\")\r\n        .basic_auth(username, Some(password))\r\n        .timeout(Duration::from_secs(10))\r\n        .header(reqwest::header::CONTENT_TYPE, \"application\/json\")\r\n        .body(json_data.to_string())\r\n        .send()\r\n        .await {\r\n            Ok(res) => {\r\n                \/\/ Check for specific status codes\r\n                if res.status().is_client_error() {\r\n                    return Err(format!(\"Server returned a client error: {}\", res.status()).into());\r\n                }\r\n                else if res.status().is_server_error() {\r\n                    return Err(format!(\"Server returned a error, check your request data: {}\", res.status()).into());\r\n                } else {\r\n                Ok(res)\r\n            }\r\n            }\r\n            Err(err) => {\r\n                \/\/ Handle specific error types\r\n                if err.is_connect() {\r\n                    return Err(\"Failed to connect to the server. Please make sure the server is running.\".into());\r\n                } else if err.is_timeout() {\r\n                    return Err(format!(\"Request timed out: {}\", err).into());\r\n                }\r\n                else {\r\n                    return Err(Box::new(err));\r\n                }\r\n            }\r\n    };\r\n\r\n    \/\/ Handle the response and extract JSON\r\n    let result_json: serde_json::Value = match response {\r\n        Ok(res) => match res.json().await {\r\n            Ok(json) => json,\r\n            Err(err) => return Err(Box::new(err)),\r\n        },\r\n        Err(err) => return Err(err),\r\n    };\r\n \r\n    Ok(result_json)\r\n}\r\n<\/pre>\n<p>The Reqwest crate is a powerful client solution for Rust designed to facilitate asynchronous HTTP requests. It supports HTTPS by default, ensuring secure communication, and includes features such as handling timeouts and automatic retries. In this article, we explored the application of Reqwest in the context of our Cars API Web Application and demonstrated the implementation of GET and POST requests in Rust, showcasing the ability to retrieve and add car information to the server. The code snippets illustrate how to handle errors, set up basic authentication, and structure requests with JSON payloads. <\/p>\n<hr\/>\n<h3>References<\/h3>\n<p>1. <a href=\"https:\/\/docs.rs\/reqwest\/latest\/reqwest\/\" rel=\"noopener\" target=\"_blank\">Reqwest Crate<\/a><br \/>\n2. <a href=\"https:\/\/blog.logrocket.com\/making-http-requests-rust-reqwest\/\" rel=\"noopener\" target=\"_blank\">Making HTTP requests in Rust with Reqwest<\/a><\/p>\n<hr>\n<h3>Hire testers from Qxf2<\/h3>\n<p>Hire technical testers from Qxf2. Our QA engineers go well beyond traditional test automation. We have a wealth of experience in testing critical parts of complex systems. We work well with small teams and early stage products. <a href=\"https:\/\/qxf2.com\/contact?utm_source=reqwest_crate&#038;utm_medium=click&#038;utm_campaign=From%20blog rel=\"noopener\" target=\"_blank\"\">Get in touch<\/a> with us to hire technical testers for your product.<\/p>\n<hr>\n","protected":false},"excerpt":{"rendered":"<p>About Reqwest At Qxf2, we have always been curious to constantly learn and discover new possibilities and insights. This led us to explore the Reqwest crate, a standout tool for Rust, further enriching our technical toolkit. Reqwest is a client solution for Rust for making asynchronous HTTP requests. It provides API&#8217;s for interacting with various HTTP operations. It&#8217;s ease of [&hellip;]<\/p>\n","protected":false},"author":16,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[415,306],"tags":[],"class_list":["post-20544","post","type-post","status-publish","format-standard","hentry","category-reqwest","category-rust"],"_links":{"self":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/20544","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/users\/16"}],"replies":[{"embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/comments?post=20544"}],"version-history":[{"count":66,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/20544\/revisions"}],"predecessor-version":[{"id":21732,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/20544\/revisions\/21732"}],"wp:attachment":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/media?parent=20544"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/categories?post=20544"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/tags?post=20544"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}