Use HeaderVec in http::Response.

This commit is contained in:
jcamiel 2024-02-12 11:07:40 +01:00 committed by hurl-bot
parent dd3068cd73
commit a0dffe429a
No known key found for this signature in database
GPG Key ID: 1283A2B4A0DCAF8D
8 changed files with 103 additions and 88 deletions

View File

@ -30,7 +30,7 @@ use url::Url;
use crate::http::certificate::Certificate;
use crate::http::core::*;
use crate::http::header::{
HeaderVec, ACCEPT_ENCODING, AUTHORIZATION, CONTENT_TYPE, EXPECT, USER_AGENT,
HeaderVec, ACCEPT_ENCODING, AUTHORIZATION, CONTENT_TYPE, EXPECT, LOCATION, USER_AGENT,
};
use crate::http::options::ClientOptions;
use crate::http::request::*;
@ -676,8 +676,8 @@ impl Client {
}
/// Parse headers from libcurl responses.
fn parse_response_headers(&mut self, lines: &[String]) -> Vec<Header> {
let mut headers: Vec<Header> = vec![];
fn parse_response_headers(&mut self, lines: &[String]) -> HeaderVec {
let mut headers = HeaderVec::new();
for line in lines {
if let Some(header) = Header::parse(line) {
headers.push(header);
@ -697,16 +697,10 @@ impl Client {
if !(300..400).contains(&response_code) {
return None;
}
let location = match response.get_header_values("Location").first() {
None => return None,
Some(value) => get_redirect_url(value, base_url),
};
if location.is_empty() {
None
} else {
Some(location)
}
response
.headers
.get(LOCATION)
.map(|h| get_redirect_url(&h.value, base_url))
}
/// Returns cookie storage.

View File

@ -23,6 +23,7 @@ pub const AUTHORIZATION: &str = "Authorization";
pub const COOKIE: &str = "Cookie";
pub const CONTENT_TYPE: &str = "Content-Type";
pub const EXPECT: &str = "Expect";
pub const LOCATION: &str = "Location";
pub const USER_AGENT: &str = "User-Agent";
/// Represents an HTTP header
@ -47,20 +48,6 @@ impl Header {
}
}
/// Returns all `headers` values for given `name`.
pub fn get_values(headers: &[Header], name: &str) -> Vec<String> {
headers
.iter()
.filter_map(|Header { name: key, value }| {
if key.to_lowercase() == name.to_lowercase() {
Some(value.to_string())
} else {
None
}
})
.collect()
}
/// Represents an ordered list of [`Header`].
/// The headers are sorted by insertion order.
#[derive(Clone, Debug, Default, PartialEq, Eq)]

View File

@ -20,7 +20,7 @@ use std::time::Duration;
use crate::http::certificate::Certificate;
use crate::http::header::CONTENT_TYPE;
use crate::http::{header, Header};
use crate::http::HeaderVec;
/// Represents a runtime HTTP response.
/// This is a real response, that has been executed by our HTTP client.
@ -28,7 +28,7 @@ use crate::http::{header, Header};
pub struct Response {
pub version: HttpVersion,
pub status: u32,
pub headers: Vec<Header>,
pub headers: HeaderVec,
pub body: Vec<u8>,
pub duration: Duration,
pub url: String,
@ -41,7 +41,7 @@ impl Default for Response {
Response {
version: HttpVersion::Http10,
status: 200,
headers: vec![],
headers: HeaderVec::new(),
body: vec![],
duration: Default::default(),
url: String::new(),
@ -55,7 +55,7 @@ impl Response {
pub fn new(
version: HttpVersion,
status: u32,
headers: Vec<Header>,
headers: HeaderVec,
body: Vec<u8>,
duration: Duration,
url: &str,
@ -73,15 +73,17 @@ impl Response {
}
/// Returns all header values.
pub fn get_header_values(&self, name: &str) -> Vec<String> {
header::get_values(&self.headers, name)
pub fn get_header_values(&self, name: &str) -> Vec<&str> {
self.headers
.get_all(name)
.iter()
.map(|h| h.value.as_str())
.collect::<Vec<_>>()
}
/// Returns optional Content-type header value.
pub fn content_type(&self) -> Option<String> {
header::get_values(&self.headers, CONTENT_TYPE)
.first()
.cloned()
pub fn content_type(&self) -> Option<&str> {
self.headers.get(CONTENT_TYPE).map(|h| h.value.as_str())
}
}
@ -110,17 +112,18 @@ impl fmt::Display for HttpVersion {
#[cfg(test)]
mod tests {
use super::*;
use crate::http::Header;
#[test]
fn get_header_values() {
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Length", "12"));
let response = Response {
headers: vec![Header::new("Content-Length", "12")],
headers,
..Default::default()
};
assert_eq!(
response.get_header_values("Content-Length"),
vec!["12".to_string()]
);
assert_eq!(response.get_header_values("Content-Length"), vec!["12"]);
assert!(response.get_header_values("Unknown").is_empty());
}
}

View File

@ -32,7 +32,7 @@ impl Response {
// If it ok, we print each line of the body in debug format. Otherwise, we
// print the body first 64 bytes.
if let Some(content_type) = self.content_type() {
if !mimetype::is_kind_of_text(&content_type) {
if !mimetype::is_kind_of_text(content_type) {
debug::log_bytes(&self.body, 64, debug, logger);
return;
}

View File

@ -64,7 +64,7 @@ impl Response {
/// Returns character encoding of the HTTP response.
fn character_encoding(&self) -> Result<EncodingRef, HttpError> {
match self.content_type() {
Some(content_type) => match mimetype::charset(&content_type) {
Some(content_type) => match mimetype::charset(content_type) {
Some(charset) => {
match encoding::label::encoding_from_whatwg_label(charset.as_str()) {
None => Err(HttpError::InvalidCharset { charset }),
@ -91,10 +91,7 @@ impl Response {
/// Returns true if response is an HTML response.
pub fn is_html(&self) -> bool {
match self.content_type() {
None => false,
Some(s) => mimetype::is_html(&s),
}
self.content_type().map_or(false, mimetype::is_html)
}
/// Returns list of content encoding from HTTP response headers.
@ -179,7 +176,7 @@ fn uncompress_zlib(data: &[u8]) -> Result<Vec<u8>, HttpError> {
#[cfg(test)]
pub mod tests {
use super::*;
use crate::http::{Header, Response};
use crate::http::{Header, HeaderVec, Response};
#[test]
fn test_parse_content_encoding() {
@ -200,8 +197,11 @@ pub mod tests {
let response = Response::default();
assert_eq!(response.content_encoding().unwrap(), vec![]);
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Encoding", "xx"));
let response = Response {
headers: vec![Header::new("Content-Encoding", "xx")],
headers,
..Default::default()
};
assert_eq!(
@ -211,8 +211,11 @@ pub mod tests {
}
);
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Encoding", "br"));
let response = Response {
headers: vec![Header::new("Content-Encoding", "br")],
headers,
..Default::default()
};
assert_eq!(
@ -223,8 +226,10 @@ pub mod tests {
#[test]
fn test_multiple_content_encoding() {
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Encoding", "br, identity"));
let response = Response {
headers: vec![Header::new("Content-Encoding", "br, identity")],
headers,
..Default::default()
};
assert_eq!(
@ -235,8 +240,11 @@ pub mod tests {
#[test]
fn test_uncompress_body() {
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Encoding", "br"));
let response = Response {
headers: vec![Header::new("Content-Encoding", "br")],
headers,
body: vec![
0x21, 0x2c, 0x00, 0x04, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c,
0x64, 0x21, 0x03,
@ -245,8 +253,10 @@ pub mod tests {
};
assert_eq!(response.uncompress_body().unwrap(), b"Hello World!");
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Encoding", "br, identity"));
let response = Response {
headers: vec![Header::new("Content-Encoding", "br, identity")],
headers,
body: vec![
0x21, 0x2c, 0x00, 0x04, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c,
0x64, 0x21, 0x03,
@ -315,19 +325,25 @@ pub mod tests {
}
fn utf8_encoding_response() -> Response {
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Type", "text/plain; charset=utf-8"));
Response {
headers: vec![Header::new("Content-Type", "text/plain; charset=utf-8")],
headers,
body: vec![0x63, 0x61, 0x66, 0xc3, 0xa9],
..Default::default()
}
}
fn latin1_encoding_response() -> Response {
let mut headers = HeaderVec::new();
headers.push(Header::new(
"Content-Type",
"text/plain; charset=ISO-8859-1",
));
Response {
headers: vec![Header::new(
"Content-Type",
"text/plain; charset=ISO-8859-1",
)],
headers,
body: vec![0x63, 0x61, 0x66, 0xe9],
..Default::default()
}
@ -338,11 +354,11 @@ pub mod tests {
assert_eq!(hello_response().content_type(), None);
assert_eq!(
utf8_encoding_response().content_type(),
Some("text/plain; charset=utf-8".to_string())
Some("text/plain; charset=utf-8")
);
assert_eq!(
latin1_encoding_response().content_type(),
Some("text/plain; charset=ISO-8859-1".to_string())
Some("text/plain; charset=ISO-8859-1")
);
}
@ -380,9 +396,12 @@ pub mod tests {
#[test]
pub fn test_invalid_charset() {
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Type", "test/plain; charset=xxx"));
assert_eq!(
Response {
headers: vec![Header::new("Content-Type", "test/plain; charset=xxx"),],
headers,
body: b"Hello World!".to_vec(),
..Default::default()
}
@ -410,12 +429,15 @@ pub mod tests {
}
);
let mut headers = HeaderVec::new();
headers.push(Header::new(
"Content-Type",
"text/plain; charset=ISO-8859-1",
));
assert_eq!(
Response {
headers: vec![Header::new(
"Content-Type",
"text/plain; charset=ISO-8859-1"
),],
headers,
body: vec![0x63, 0x61, 0x66, 0xc3, 0xa9],
..Default::default()
}

View File

@ -47,11 +47,12 @@ pub fn json_http_response() -> Response {
}
pub fn xml_two_users_http_response() -> Response {
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Type", "text/html; charset=utf-8"));
headers.push(Header::new("Content-Length", "12"));
Response {
headers: vec![
Header::new("Content-Type", "text/html; charset=utf-8"),
Header::new("Content-Length", "12"),
],
headers,
body: String::into_bytes(
r#"
<?xml version="1.0"?>
@ -67,11 +68,12 @@ pub fn xml_two_users_http_response() -> Response {
}
pub fn xml_three_users_http_response() -> Response {
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Type", "text/html; charset=utf-8"));
headers.push(Header::new("Content-Length", "12"));
Response {
headers: vec![
Header::new("Content-Type", "text/html; charset=utf-8"),
Header::new("Content-Length", "12"),
],
headers,
body: String::into_bytes(
r#"
<?xml version="1.0"?>
@ -88,30 +90,35 @@ pub fn xml_three_users_http_response() -> Response {
}
pub fn hello_http_response() -> Response {
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Type", "text/html; charset=utf-8"));
headers.push(Header::new("Content-Length", "12"));
Response {
headers: vec![
Header::new("Content-Type", "text/html; charset=utf-8"),
Header::new("Content-Length", "12"),
],
headers,
body: String::into_bytes(String::from("Hello World!")),
..Default::default()
}
}
pub fn bytes_http_response() -> Response {
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Type", "application/octet-stream"));
headers.push(Header::new("Content-Length", "1"));
Response {
headers: vec![
Header::new("Content-Type", "application/octet-stream"),
Header::new("Content-Length", "1"),
],
headers,
body: vec![255],
..Default::default()
}
}
pub fn html_http_response() -> Response {
let mut headers = HeaderVec::new();
headers.push(Header::new("Content-Type", "application/octet-stream"));
Response {
headers: vec![Header::new("Content-Type", "text/html; charset=utf-8")],
headers,
body: String::into_bytes(String::from(
"<html><head><meta charset=\"UTF-8\"></head><body><br></body></html>",
)),

View File

@ -313,6 +313,7 @@ impl Value {
#[cfg(test)]
pub mod tests {
use crate::http::HeaderVec;
use hex_literal::hex;
use hurl_core::ast::{Pos, SourceInfo};
@ -600,10 +601,11 @@ pub mod tests {
value: String::new(),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
};
let mut headers = HeaderVec::new();
headers.push(http::Header::new("Set-Cookie", "LSID=DQAAAKEaem_vYg; Path=/accounts; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly"));
let response = http::Response {
headers: vec![
http::Header::new("Set-Cookie", "LSID=DQAAAKEaem_vYg; Path=/accounts; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly")
],
headers,
..Default::default()
};

View File

@ -112,12 +112,12 @@ pub fn eval_asserts(
actuals
.iter()
.map(|v| format!("\"{v}\""))
.collect::<Vec<String>>()
.collect::<Vec<_>>()
.join(", ")
);
for value in actuals {
if value == expected {
actual = value;
actual = value.to_string();
break;
}
}