1use tracing::{debug, trace};
2use ureq::{
3 http::{
4 header::{CONTENT_LENGTH, CONTENT_TYPE, LOCATION},
5 Response,
6 },
7 Body,
8};
9
10use crate::{error::DownloadError, http_client::SHARED_AGENT};
11
12pub struct Http;
13
14impl Http {
15 pub fn head(url: &str) -> Result<Response<Body>, DownloadError> {
16 trace!(url = url, "sending HEAD request");
17 let result = SHARED_AGENT.head(url).call().map_err(DownloadError::from);
18 if let Ok(ref resp) = result {
19 trace!(status = resp.status().as_u16(), "HEAD response received");
20 Self::log_response_headers(resp, "HEAD");
21 }
22 result
23 }
24
25 fn log_response_headers(resp: &Response<Body>, method: &str) {
26 let status = resp.status();
27 let headers = resp.headers();
28
29 debug!(
30 "{} {} {}",
31 method,
32 status.as_u16(),
33 status.canonical_reason().unwrap_or("")
34 );
35
36 if let Some(content_length) = headers.get(CONTENT_LENGTH) {
37 if let Ok(len) = content_length.to_str() {
38 trace!(" Content-Length: {}", len);
39 }
40 }
41 if let Some(content_type) = headers.get(CONTENT_TYPE) {
42 if let Ok(ct) = content_type.to_str() {
43 trace!(" Content-Type: {}", ct);
44 }
45 }
46 if let Some(location) = headers.get(LOCATION) {
47 if let Ok(loc) = location.to_str() {
48 debug!(" Location: {}", loc);
49 }
50 }
51 }
52
53 pub fn fetch(
76 url: &str,
77 resume_from: Option<u64>,
78 etag: Option<&str>,
79 ghcr_blob: bool,
80 ) -> Result<Response<Body>, DownloadError> {
81 debug!("GET {}", url);
82 trace!(resume_from = ?resume_from, ghcr_blob = ghcr_blob, "request details");
83 let mut req = SHARED_AGENT.get(url);
84
85 if ghcr_blob {
86 trace!("adding GHCR authorization header");
87 req = req.header("Authorization", "Bearer QQ==");
88 }
89
90 if let Some(pos) = resume_from {
91 debug!(" Range: bytes={}-", pos);
92 req = req.header("Range", &format!("bytes={}-", pos));
93 if let Some(tag) = etag {
94 trace!(etag = tag, "adding If-Range header");
95 req = req.header("If-Range", tag);
96 }
97 }
98
99 let result = req.call().map_err(DownloadError::from);
100 if let Ok(ref resp) = result {
101 Self::log_response_headers(resp, "GET");
102 }
103 result
104 }
105
106 pub fn json<T: serde::de::DeserializeOwned>(url: &str) -> Result<T, DownloadError> {
123 debug!(url = url, "fetching JSON");
124 let result = SHARED_AGENT
125 .get(url)
126 .call()?
127 .body_mut()
128 .read_json()
129 .map_err(|_| DownloadError::InvalidResponse);
130 if result.is_ok() {
131 trace!(url = url, "JSON parsed successfully");
132 }
133 result
134 }
135}