Add http version in response

This commit is contained in:
Fabrice Reix 2020-09-11 21:04:05 +02:00
parent 618634a025
commit abdbc6c13a
3 changed files with 51 additions and 23 deletions

View File

@ -48,7 +48,6 @@ pub struct ClientOptions {
impl Client { impl Client {
/// ///
/// Init HTTP hurl client /// Init HTTP hurl client
/// ///
@ -107,7 +106,7 @@ impl Client {
} }
} }
easy::InfoType::HeaderIn => { easy::InfoType::HeaderIn => {
eprint!("< {}", str::from_utf8(data).unwrap()); eprint!("< {}", str::from_utf8(data).unwrap());
} }
_ => {} _ => {}
} }
@ -147,8 +146,11 @@ impl Client {
} }
let status = self.handle.response_code().unwrap(); let status = self.handle.response_code().unwrap();
let first_line = lines.remove(0); // remove the status line
let version = self.parse_response_version(first_line)?;
let headers = self.parse_response_headers(&mut lines); let headers = self.parse_response_headers(&mut lines);
if let Some(url) = self.get_follow_location(headers.clone()) { if let Some(url) = self.get_follow_location(headers.clone()) {
let request = Request { let request = Request {
method: Method::Get, method: Method::Get,
@ -173,6 +175,7 @@ impl Client {
self.redirect_count = redirect_count; self.redirect_count = redirect_count;
Ok(Response { Ok(Response {
version,
status, status,
headers, headers,
body, body,
@ -203,7 +206,7 @@ impl Client {
/// ///
fn set_method(&mut self, method: &Method) { fn set_method(&mut self, method: &Method) {
match method { match method {
Method::Get => self.handle.custom_request("GET").unwrap(), Method::Get => self.handle.custom_request("GET").unwrap(),
Method::Post => self.handle.custom_request("POST").unwrap(), Method::Post => self.handle.custom_request("POST").unwrap(),
Method::Put => self.handle.custom_request("PUT").unwrap(), Method::Put => self.handle.custom_request("PUT").unwrap(),
Method::Head => self.handle.custom_request("HEAD").unwrap(), Method::Head => self.handle.custom_request("HEAD").unwrap(),
@ -308,12 +311,27 @@ impl Client {
} }
///
/// parse response version
///
fn parse_response_version(&mut self, line: String) -> Result<Version, HttpError> {
if line.starts_with("HTTP/1.0") {
Ok(Version::Http10)
} else if line.starts_with("HTTP/1.1") {
Ok(Version::Http11)
} else if line.starts_with("HTTP/2") {
Ok(Version::Http2)
} else {
Err(HttpError::CouldNotParseResponse)
}
}
/// ///
/// parse headers from libcurl responses /// parse headers from libcurl responses
/// ///
fn parse_response_headers(&mut self, lines: &mut Vec<String>) -> Vec<Header> { fn parse_response_headers(&mut self, lines: &mut Vec<String>) -> Vec<Header> {
let mut headers: Vec<Header> = vec![]; let mut headers: Vec<Header> = vec![];
lines.remove(0); // remove the status line
lines.pop(); // remove the blank line between headers and body lines.pop(); // remove the blank line between headers and body
for line in lines { for line in lines {
if let Some(header) = Header::parse(line.to_string()) { if let Some(header) = Header::parse(line.to_string()) {
@ -398,7 +416,6 @@ impl Header {
} }
/// ///
/// Split an array of bytes into http lines (\r\n separator) /// Split an array of bytes into http lines (\r\n separator)
/// ///
@ -448,6 +465,5 @@ mod tests {
assert_eq!(lines.get(0).unwrap().as_str(), "GET /hello HTTP/1.1"); assert_eq!(lines.get(0).unwrap().as_str(), "GET /hello HTTP/1.1");
assert_eq!(lines.get(1).unwrap().as_str(), "Host: localhost:8000"); assert_eq!(lines.get(1).unwrap().as_str(), "Host: localhost:8000");
assert_eq!(lines.get(2).unwrap().as_str(), ""); assert_eq!(lines.get(2).unwrap().as_str(), "");
} }
} }

View File

@ -32,6 +32,7 @@ pub struct Request {
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct Response { pub struct Response {
pub version: Version,
pub status: u32, pub status: u32,
pub headers: Vec<Header>, pub headers: Vec<Header>,
pub body: Vec<u8>, pub body: Vec<u8>,
@ -51,6 +52,13 @@ pub enum Method {
Patch, Patch,
} }
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Version {
Http10,
Http11,
Http2,
}
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct Header { pub struct Header {
pub name: String, pub name: String,
@ -96,12 +104,9 @@ pub struct Cookie {
} }
impl fmt::Display for RequestCookie { impl fmt::Display for RequestCookie {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}={}", self.name, self.value) write!(f, "{}={}", self.name, self.value)
} }
} }
@ -111,22 +116,18 @@ pub enum HttpError {
CouldNotResolveProxyName, CouldNotResolveProxyName,
CouldNotResolveHost, CouldNotResolveHost,
FailToConnect, FailToConnect,
TooManyRedirect TooManyRedirect,
CouldNotParseResponse,
} }
impl Response { impl Response {
/// ///
/// return a list of headers values for the given header name /// return a list of headers values for the given header name
/// ///
pub fn get_header_values(&self, expected_name: String) -> Vec<String> { pub fn get_header_values(&self, expected_name: String) -> Vec<String> {
self.headers get_header_values(self.headers.clone(), expected_name)
.iter()
.filter_map(|Header{ name, value}| if name.clone() == expected_name { Some(value.to_string())} else { None })
.collect()
} }
} }
@ -136,27 +137,38 @@ impl Response {
pub fn get_header_values(headers: Vec<Header>, expected_name: String) -> Vec<String> { pub fn get_header_values(headers: Vec<Header>, expected_name: String) -> Vec<String> {
headers headers
.iter() .iter()
.filter_map(|Header{ name, value}| if name.clone() == expected_name { Some(value.to_string())} else { None }) .filter_map(|Header { name, value }| if name.clone() == expected_name { Some(value.to_string()) } else { None })
.collect() .collect()
} }
#[cfg(test)] #[cfg(test)]
mod tests { pub mod tests {
use super::*; use super::*;
#[test] #[test]
fn get_header_values() { fn get_header_values() {
let response = Response { let response = Response {
version: Version::Http10,
status: 200, status: 200,
headers: vec![ headers: vec![
Header { name: "Content-Length".to_string(), value: "12".to_string() } Header { name: "Content-Length".to_string(), value: "12".to_string() }
], ],
body: vec![] body: vec![],
}; };
assert_eq!(response.get_header_values("Content-Length".to_string()), vec!["12".to_string()]); assert_eq!(response.get_header_values("Content-Length".to_string()), vec!["12".to_string()]);
assert!(response.get_header_values("Unknown".to_string()).is_empty()); assert!(response.get_header_values("Unknown".to_string()).is_empty());
} }
pub fn hello_http_request() -> Request {
Request {
method: Method::Get,
url: "http://localhost:8000/hello".to_string(),
querystring: vec![],
headers: vec![],
cookies: vec![],
body: vec![],
multipart: vec![],
form: vec![],
}
}
} }

View File

@ -88,6 +88,7 @@ fn test_hello() {
let mut client = default_client(); let mut client = default_client();
let request = default_get_request("http://localhost:8000/hello".to_string()); let request = default_get_request("http://localhost:8000/hello".to_string());
let response = client.execute(&request, 0).unwrap(); let response = client.execute(&request, 0).unwrap();
assert_eq!(response.version, Version::Http10);
assert_eq!(response.status, 200); assert_eq!(response.status, 200);
assert_eq!(response.body, b"Hello World!".to_vec()); assert_eq!(response.body, b"Hello World!".to_vec());
@ -95,7 +96,6 @@ fn test_hello() {
assert!(response.headers.contains(&Header { name: "Content-Length".to_string(), value: "12".to_string() })); assert!(response.headers.contains(&Header { name: "Content-Length".to_string(), value: "12".to_string() }));
assert!(response.headers.contains(&Header { name: "Content-Type".to_string(), value: "text/html; charset=utf-8".to_string() })); assert!(response.headers.contains(&Header { name: "Content-Type".to_string(), value: "text/html; charset=utf-8".to_string() }));
assert_eq!(response.get_header_values("Date".to_string()).len(), 1); assert_eq!(response.get_header_values("Date".to_string()).len(), 1);
} }
// endregion // endregion