feat(http): add per-request headers option (#196)

* http: Add per-request headers option

* http: Improve tests

---------

Co-authored-by: Douile <douile@douile.com>
This commit is contained in:
CosminPerRam 2024-03-16 17:57:07 +02:00 committed by GitHub
parent f54321da18
commit 6e53ef0c22
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 43 additions and 15 deletions

View file

@ -31,7 +31,7 @@ pub fn query_with_timeout_and_extra_settings(
extra_settings.unwrap_or_default().into(), extra_settings.unwrap_or_default().into(),
)?; )?;
let response = client.get_json::<Root>("/frontpage")?; let response = client.get_json::<Root>("/frontpage", None)?;
Ok(response.into()) Ok(response.into())
} }

View file

@ -37,6 +37,9 @@ pub struct HttpClient {
headers: Vec<(String, String)>, headers: Vec<(String, String)>,
} }
/// HttpHeaders for use with a single request.
pub type HttpHeaders<'a> = Option<&'a [(&'a str, &'a str)]>;
/// HTTP Protocols. /// HTTP Protocols.
/// ///
/// Note: if the `tls` feature is disabled this will only contain Http. /// Note: if the `tls` feature is disabled this will only contain Http.
@ -65,10 +68,10 @@ impl HttpProtocol {
/// ///
/// # Can be created using builder functions: /// # Can be created using builder functions:
/// ```ignore, We cannot test private functionality /// ```ignore, We cannot test private functionality
/// use gamedig::http::{HttpSettings, Protocol}; /// use gamedig::http::{HttpSettings, HttpProtocol};
/// ///
/// let _ = HttpSettings::default() /// let _ = HttpSettings::default()
/// .protocol(Protocol::Http) /// .protocol(HttpProtocol::Http)
/// .hostname(String::from("test.com")) /// .hostname(String::from("test.com"))
/// .header(String::from("Authorization"), String::from("Bearer Token")); /// .header(String::from("Authorization"), String::from("Bearer Token"));
/// ``` /// ```
@ -230,21 +233,29 @@ impl HttpClient {
} }
/// Send a HTTP GET request and return the response data as a buffer. /// Send a HTTP GET request and return the response data as a buffer.
pub fn get(&mut self, path: &str) -> GDResult<Vec<u8>> { self.request("GET", path) } pub fn get(&mut self, path: &str, headers: HttpHeaders) -> GDResult<Vec<u8>> { self.request("GET", path, headers) }
/// Send a HTTP GET request and parse the JSON resonse. /// Send a HTTP GET request and parse the JSON resonse.
pub fn get_json<T: DeserializeOwned>(&mut self, path: &str) -> GDResult<T> { self.request_json("GET", path) } pub fn get_json<T: DeserializeOwned>(&mut self, path: &str, headers: HttpHeaders) -> GDResult<T> {
self.request_json("GET", path, headers)
}
/// Send a HTTP Post request with JSON data and parse a JSON response. /// Send a HTTP Post request with JSON data and parse a JSON response.
pub fn post_json<T: DeserializeOwned, S: Serialize>(&mut self, path: &str, data: S) -> GDResult<T> { pub fn post_json<T: DeserializeOwned, S: Serialize>(
self.request_with_json_data("POST", path, data) &mut self,
path: &str,
headers: HttpHeaders,
data: S,
) -> GDResult<T> {
self.request_with_json_data("POST", path, headers, data)
} }
// NOTE: More methods can be added here as required using the request_json or // NOTE: More methods can be added here as required using the request_json or
// request_with_json methods // request_with_json methods
/// Internal request method, makes a request with an arbitrary HTTP method.
#[inline] #[inline]
fn request(&mut self, method: &str, path: &str) -> GDResult<Vec<u8>> { fn request(&mut self, method: &str, path: &str, headers: HttpHeaders) -> GDResult<Vec<u8>> {
// Append the path to the pre-parsed URL and create a request object. // Append the path to the pre-parsed URL and create a request object.
self.address.set_path(path); self.address.set_path(path);
let mut request = self.client.request_url(method, &self.address); let mut request = self.client.request_url(method, &self.address);
@ -254,6 +265,12 @@ impl HttpClient {
request = request.set(key, value); request = request.set(key, value);
} }
if let Some(headers) = headers {
for (key, value) in headers {
request = request.set(key, value);
}
}
// Send the request. // Send the request.
let http_response = request.call().map_err(|e| PacketSend.context(e))?; let http_response = request.call().map_err(|e| PacketSend.context(e))?;
@ -279,7 +296,7 @@ impl HttpClient {
/// Send a HTTP request without any data and parse the JSON response. /// Send a HTTP request without any data and parse the JSON response.
#[inline] #[inline]
fn request_json<T: DeserializeOwned>(&mut self, method: &str, path: &str) -> GDResult<T> { fn request_json<T: DeserializeOwned>(&mut self, method: &str, path: &str, headers: HttpHeaders) -> GDResult<T> {
// Append the path to the pre-parsed URL and create a request object. // Append the path to the pre-parsed URL and create a request object.
self.address.set_path(path); self.address.set_path(path);
let mut request = self.client.request_url(method, &self.address); let mut request = self.client.request_url(method, &self.address);
@ -288,6 +305,11 @@ impl HttpClient {
for (key, value) in self.headers.iter() { for (key, value) in self.headers.iter() {
request = request.set(key, value); request = request.set(key, value);
} }
if let Some(headers) = headers {
for (key, value) in headers {
request = request.set(key, value);
}
}
// Send the request and parse the response as JSON. // Send the request and parse the response as JSON.
request request
@ -303,6 +325,7 @@ impl HttpClient {
&mut self, &mut self,
method: &str, method: &str,
path: &str, path: &str,
headers: HttpHeaders,
data: S, data: S,
) -> GDResult<T> { ) -> GDResult<T> {
self.address.set_path(path); self.address.set_path(path);
@ -311,6 +334,11 @@ impl HttpClient {
for (key, value) in self.headers.iter() { for (key, value) in self.headers.iter() {
request = request.set(key, value); request = request.set(key, value);
} }
if let Some(headers) = headers {
for (key, value) in headers {
request = request.set(key, value);
}
}
request request
.send_json(data) .send_json(data)
@ -384,7 +412,7 @@ mod tests {
let mut client = HttpClient::new(&address, &None, settings).unwrap(); let mut client = HttpClient::new(&address, &None, settings).unwrap();
let response: serde_json::Value = client.get_json("/events").unwrap(); let response: serde_json::Value = client.get_json("/events", None).unwrap();
println!("{:?}", response); println!("{:?}", response);
} }
@ -402,7 +430,7 @@ mod tests {
let mut client = HttpClient::new(&address, &None, settings).unwrap(); let mut client = HttpClient::new(&address, &None, settings).unwrap();
let response: serde_json::Value = client.get_json("/get").unwrap(); let response: serde_json::Value = client.get_json("/get", None).unwrap();
println!("{:?}", response); println!("{:?}", response);
} }
@ -418,7 +446,7 @@ mod tests {
let mut client = HttpClient::new(&address, &None, settings).unwrap(); let mut client = HttpClient::new(&address, &None, settings).unwrap();
let response = client.get("/").unwrap(); let response = client.get("/", None).unwrap();
println!("{:?}", std::str::from_utf8(&response)); println!("{:?}", std::str::from_utf8(&response));
} }
@ -428,7 +456,7 @@ mod tests {
fn http_get_from_url() { fn http_get_from_url() {
let mut client = HttpClient::from_url("http://postman-echo.com/path-is-ignored", &None, None).unwrap(); let mut client = HttpClient::from_url("http://postman-echo.com/path-is-ignored", &None, None).unwrap();
let response: serde_json::Value = client.get_json("/get").unwrap(); let response: serde_json::Value = client.get_json("/get", None).unwrap();
println!("{:?}", response); println!("{:?}", response);
} }
@ -436,11 +464,11 @@ mod tests {
#[test] #[test]
#[ignore = "HTTP requests won't work without internet"] #[ignore = "HTTP requests won't work without internet"]
fn http_get_from_url_parsed() { fn http_get_from_url_parsed() {
let url = Url::parse("http://postman-echo.com:443/path-is-ignored").unwrap(); let url = Url::parse("http://postman-echo.com/path-is-ignored").unwrap();
let mut client = HttpClient::from_url(url, &None, None).unwrap(); let mut client = HttpClient::from_url(url, &None, None).unwrap();
let response: serde_json::Value = client.get_json("/get").unwrap(); let response: serde_json::Value = client.get_json("/get", None).unwrap();
println!("{:?}", response); println!("{:?}", response);
} }