When using the reqwest
library in Rust, handling different content types in responses requires inspecting the Content-Type
header and parsing the response body accordingly. Reqwest provides convenient built-in methods for common content types like JSON and text, while other formats require manual parsing.
Basic Approach
The general workflow for handling different content types:
- Send Request: Make an HTTP request using reqwest
- Check Content-Type: Inspect the
Content-Type
header in the response - Parse Response: Use the appropriate parsing method based on the content type
- Handle Errors: Implement proper error handling for each content type
Setup
Add reqwest
with required features to your Cargo.toml
:
[dependencies]
reqwest = { version = "0.11", features = ["json", "blocking"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }
Comprehensive Content Type Handling
Blocking Example
use reqwest::blocking::Client;
use reqwest::header::{HeaderValue, CONTENT_TYPE};
use reqwest::Error;
use serde_json::Value;
fn handle_response_by_content_type(url: &str) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let response = client.get(url).send()?.error_for_status()?;
// Get Content-Type header
let content_type = response
.headers()
.get(CONTENT_TYPE)
.and_then(|ct| ct.to_str().ok())
.unwrap_or("");
match content_type {
// JSON responses
ct if ct.contains("application/json") => {
let json: Value = response.json()?;
println!("JSON data: {:#}", json);
}
// XML responses
ct if ct.contains("application/xml") || ct.contains("text/xml") => {
let xml_text = response.text()?;
println!("XML content: {}", xml_text);
// Use xml-rs or quick-xml crate for parsing
}
// HTML responses
ct if ct.contains("text/html") => {
let html = response.text()?;
println!("HTML content length: {}", html.len());
// Use scraper or select crate for HTML parsing
}
// Plain text responses
ct if ct.contains("text/plain") => {
let text = response.text()?;
println!("Text content: {}", text);
}
// Form data responses
ct if ct.contains("application/x-www-form-urlencoded") => {
let form_text = response.text()?;
println!("Form data: {}", form_text);
// Parse key=value&key2=value2 format manually or use url crate
}
// Binary content (images, files, etc.)
ct if ct.contains("image/") || ct.contains("application/octet-stream") => {
let bytes = response.bytes()?;
println!("Binary content size: {} bytes", bytes.len());
// Save to file or process binary data
}
// Default case for unknown content types
_ => {
let bytes = response.bytes()?;
println!("Unknown content type '{}', got {} bytes", content_type, bytes.len());
}
}
Ok(())
}
Async Example
use reqwest::Client;
use reqwest::header::CONTENT_TYPE;
use serde_json::Value;
async fn handle_response_async(url: &str) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let response = client.get(url).send().await?.error_for_status()?;
let content_type = response
.headers()
.get(CONTENT_TYPE)
.and_then(|ct| ct.to_str().ok())
.unwrap_or("");
match content_type {
ct if ct.contains("application/json") => {
let json: Value = response.json().await?;
println!("Async JSON: {:#}", json);
}
ct if ct.contains("text/") => {
let text = response.text().await?;
println!("Async text length: {}", text.len());
}
_ => {
let bytes = response.bytes().await?;
println!("Async binary size: {} bytes", bytes.len());
}
}
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
handle_response_async("https://api.example.com/data").await?;
Ok(())
}
Advanced Content Type Handling
Structured JSON with Serde
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Debug)]
struct ApiResponse {
id: u32,
name: String,
data: Vec<String>,
}
async fn parse_structured_json(url: &str) -> Result<ApiResponse, reqwest::Error> {
let client = Client::new();
let response = client.get(url).send().await?;
// Directly deserialize to struct if content type is JSON
let api_data: ApiResponse = response.json().await?;
Ok(api_data)
}
Content Type with Charset Handling
fn parse_content_type_header(ct_header: &HeaderValue) -> (String, Option<String>) {
let ct_str = ct_header.to_str().unwrap_or("");
let parts: Vec<&str> = ct_str.split(';').collect();
let mime_type = parts[0].trim().to_lowercase();
let charset = parts.iter()
.find(|part| part.trim().starts_with("charset="))
.map(|part| part.split('=').nth(1).unwrap_or("").trim().to_string());
(mime_type, charset)
}
fn handle_with_charset(response: reqwest::blocking::Response) -> Result<(), Box<dyn std::error::Error>> {
if let Some(ct_header) = response.headers().get(CONTENT_TYPE) {
let (mime_type, charset) = parse_content_type_header(ct_header);
match mime_type.as_str() {
"text/html" | "text/plain" => {
let text = response.text()?;
println!("Text with charset {:?}: {}", charset, text);
}
_ => {
let bytes = response.bytes()?;
println!("Binary content: {} bytes", bytes.len());
}
}
}
Ok(())
}
Error Handling Best Practices
use reqwest::Error as ReqwestError;
use serde_json::Error as JsonError;
#[derive(Debug)]
enum ContentError {
Network(ReqwestError),
Json(JsonError),
UnsupportedContentType(String),
}
impl From<ReqwestError> for ContentError {
fn from(err: ReqwestError) -> Self {
ContentError::Network(err)
}
}
impl From<JsonError> for ContentError {
fn from(err: JsonError) -> Self {
ContentError::Json(err)
}
}
async fn robust_content_handler(url: &str) -> Result<(), ContentError> {
let client = Client::new();
let response = client.get(url).send().await?.error_for_status()?;
let content_type = response
.headers()
.get(CONTENT_TYPE)
.and_then(|ct| ct.to_str().ok())
.unwrap_or("");
match content_type {
ct if ct.contains("application/json") => {
let json: Value = response.json().await?;
println!("Parsed JSON successfully");
Ok(())
}
ct if ct.contains("text/") => {
let _text = response.text().await?;
println!("Parsed text successfully");
Ok(())
}
ct => Err(ContentError::UnsupportedContentType(ct.to_string()))
}
}
Practical Usage Examples
File Download Based on Content Type
use std::fs::File;
use std::io::Write;
async fn download_by_content_type(url: &str, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let response = client.get(url).send().await?;
let content_type = response
.headers()
.get(CONTENT_TYPE)
.and_then(|ct| ct.to_str().ok())
.unwrap_or("");
let bytes = response.bytes().await?;
let extension = match content_type {
ct if ct.contains("application/json") => "json",
ct if ct.contains("text/html") => "html",
ct if ct.contains("text/plain") => "txt",
ct if ct.contains("image/png") => "png",
ct if ct.contains("image/jpeg") => "jpg",
_ => "bin",
};
let full_filename = format!("{}.{}", filename, extension);
let mut file = File::create(&full_filename)?;
file.write_all(&bytes)?;
println!("Downloaded {} as {}", content_type, full_filename);
Ok(())
}
Key Points
- Always check status: Use
error_for_status()
before processing content - Handle charset: Content-Type headers may include charset information
- Use appropriate methods:
json()
for JSON,text()
for text,bytes()
for binary - Error handling: Implement robust error handling for network and parsing errors
- Async support: Prefer async methods for better performance in concurrent applications
- Memory efficiency: Use
bytes()
for large files to avoid string conversion overhead
This comprehensive approach ensures your Rust applications can handle any content type gracefully while maintaining good performance and error handling.