use futures::StreamExt;
use reqwest::redirect::Policy;
use tokio::io::AsyncWriteExt;
use ulid::Ulid;
use crate::models::{HttpMethod, Request, Response, ResponseBody};
pub async fn handle_http_request(request: Request) -> anyhow::Result<Response> {
let client = reqwest::Client::builder()
.redirect(match request.follow_redirects {
true => Policy::default(),
false => Policy::none(),
})
.build()?;
let mut req = match request.method {
HttpMethod::Get => client.get(&request.url),
HttpMethod::Post => client.post(&request.url),
HttpMethod::Put => client.put(&request.url),
};
for (k, v) in request.headers.iter() {
req = req.header(k, v);
}
let http_res = req.send().await?;
let mut headers = http_res
.headers()
.iter()
.map(|(k, v)| {
(
k.as_str().to_string(),
v.to_str().unwrap_or_default().to_string(),
)
})
.collect::<Vec<_>>();
headers.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
let status_code = http_res.status().as_u16();
let body = match http_res.headers().get("content-type") {
None => flush_to_disk(http_res)
.await?
.map(ResponseBody::Binary)
.unwrap_or_else(|| ResponseBody::Empty),
Some(content_type) => {
let ct = content_type.to_str().unwrap_or_default();
if ct.contains("text") {
let body = http_res.text().await?;
ResponseBody::Text(body)
} else if ct.contains("json") {
let body = http_res.text().await?;
serde_json::from_str::<serde_json::Value>(&body)
.map(|value| ResponseBody::Json {
value,
length: body.len(),
})
.unwrap_or(ResponseBody::Text(body))
} else if ct.contains("image") {
flush_to_disk(http_res)
.await?
.map(ResponseBody::Image)
.unwrap_or_else(|| ResponseBody::Empty)
} else if http_res.content_length().unwrap_or_default() == 0 {
ResponseBody::Empty
} else {
flush_to_disk(http_res)
.await?
.map(ResponseBody::Binary)
.unwrap_or_else(|| ResponseBody::Empty)
}
}
};
Ok(Response {
status_code,
headers,
body,
})
}
async fn flush_to_disk(
http_res: reqwest::Response,
) -> Result<Option<std::path::PathBuf>, anyhow::Error> {
let tmp_file = std::env::temp_dir().join(format!("httpman_res_{}", Ulid::new().to_string()));
let mut file = tokio::fs::File::create(tmp_file.clone()).await?;
let mut stream = http_res.bytes_stream();
let mut bytes_written = false;
while let Some(chunk) = stream.next().await {
let chunk = chunk?;
file.write(&chunk).await?;
bytes_written = true;
}
if !bytes_written {
std::fs::remove_file(&tmp_file)?;
return Ok(None);
}
Ok(Some(tmp_file))
}