diff --git a/packages/hurl/src/http/client.rs b/packages/hurl/src/http/client.rs index 126334652..216bbc6f9 100644 --- a/packages/hurl/src/http/client.rs +++ b/packages/hurl/src/http/client.rs @@ -15,7 +15,6 @@ * limitations under the License. * */ - use std::io::Read; use std::str; @@ -45,22 +44,12 @@ pub enum HttpError { #[derive(Debug)] pub struct Client { + pub options: ClientOptions, pub handle: Box, - - /// unfortunately, follow-location feature from libcurl can not be used - /// libcurl returns a single list of headers for the 2 responses - /// hurl needs the return the headers only for the second (last) response) - pub follow_location: bool, pub redirect_count: usize, - pub max_redirect: Option, - pub verbose: bool, - pub verify: bool, - pub proxy: Option, - pub no_proxy: Option, - pub timeout: Duration, - pub connect_timeout: Duration, - pub authorization: Option, - pub accept_encoding: Option, + // unfortunately, follow-location feature from libcurl can not be used + // libcurl returns a single list of headers for the 2 responses + // hurl needs the return the headers only for the second (last) response) } #[derive(Debug, Clone)] @@ -75,7 +64,7 @@ pub struct ClientOptions { pub timeout: Duration, pub connect_timeout: Duration, pub user: Option, - pub accept_encoding: Option, + pub compressed: bool, } impl Client { @@ -93,26 +82,16 @@ impl Client { h.cookie_file( options .cookie_input_file + .clone() .unwrap_or_else(|| "".to_string()) .as_str(), ) .unwrap(); - let authorization = options.user.map(|user| base64::encode(user.as_bytes())); - let accept_encoding = options.accept_encoding; Client { + options, handle: Box::new(h), - follow_location: options.follow_location, - max_redirect: options.max_redirect, redirect_count: 0, - verbose: options.verbose, - verify: !options.insecure, - authorization, - accept_encoding, - proxy: options.proxy, - no_proxy: options.no_proxy, - timeout: options.timeout, - connect_timeout: options.connect_timeout, } } @@ -127,17 +106,19 @@ impl Client { // set handle attributes // that have not been set or reset - self.handle.verbose(self.verbose).unwrap(); - self.handle.ssl_verify_host(self.verify).unwrap(); - self.handle.ssl_verify_peer(self.verify).unwrap(); - if let Some(proxy) = self.proxy.clone() { + self.handle.verbose(self.options.verbose).unwrap(); + self.handle.ssl_verify_host(!self.options.insecure).unwrap(); + self.handle.ssl_verify_peer(!self.options.insecure).unwrap(); + if let Some(proxy) = self.options.proxy.clone() { self.handle.proxy(proxy.as_str()).unwrap(); } - if let Some(s) = self.no_proxy.clone() { + if let Some(s) = self.options.no_proxy.clone() { self.handle.noproxy(s.as_str()).unwrap(); } - self.handle.timeout(self.timeout).unwrap(); - self.handle.connect_timeout(self.connect_timeout).unwrap(); + self.handle.timeout(self.options.timeout).unwrap(); + self.handle + .connect_timeout(self.options.connect_timeout) + .unwrap(); self.set_url(&request.url, &request.querystring); self.set_method(&request.method); @@ -146,9 +127,10 @@ impl Client { self.set_form(&request.form); self.set_multipart(&request.multipart); - let b = request.body.clone(); - let mut data: &[u8] = b.as_ref(); + let bytes = request.body.bytes(); + let mut data: &[u8] = bytes.as_ref(); self.set_body(data); + self.set_headers(&request); self.handle @@ -235,12 +217,12 @@ impl Client { form: vec![], multipart: vec![], cookies: vec![], - body: vec![], + body: Body::Binary(vec![]), content_type: None, }; let redirect_count = redirect_count + 1; - if let Some(max_redirect) = self.max_redirect { + if let Some(max_redirect) = self.options.max_redirect { if redirect_count > max_redirect { return Err(HttpError::TooManyRedirect); } @@ -327,18 +309,17 @@ impl Client { .unwrap(); } - if let Some(authorization) = self.authorization.clone() { + if let Some(user) = self.options.user.clone() { + let authorization = base64::encode(user.as_bytes()); if get_header_values(request.headers.clone(), "Authorization".to_string()).is_empty() { list.append(format!("Authorization: Basic {}", authorization).as_str()) .unwrap(); } } - if let Some(accept_encoding) = self.accept_encoding.clone() { - if get_header_values(request.headers.clone(), "Accept-Encoding".to_string()).is_empty() - { - list.append(format!("Accept-Encoding: {}", accept_encoding).as_str()) - .unwrap(); - } + if self.options.compressed + && get_header_values(request.headers.clone(), "Accept-Encoding".to_string()).is_empty() + { + list.append("Accept-Encoding: gzip, deflate, br").unwrap(); } self.handle.http_headers(list).unwrap(); @@ -457,7 +438,7 @@ impl Client { /// 3. a header Location /// fn get_follow_location(&mut self, headers: Vec
) -> Option { - if !self.follow_location { + if !self.options.follow_location { return None; } @@ -512,7 +493,7 @@ impl Client { /// Add cookie to Cookiejar /// pub fn add_cookie(&mut self, cookie: Cookie) { - if self.verbose { + if self.options.verbose { eprintln!("* add to cookie store: {}", cookie); } self.handle @@ -524,7 +505,7 @@ impl Client { /// Clear cookie storage /// pub fn clear_cookie_storage(&mut self) { - if self.verbose { + if self.options.verbose { eprintln!("* clear cookie storage"); } self.handle.cookie_list("ALL").unwrap(); diff --git a/packages/hurl/src/http/mod.rs b/packages/hurl/src/http/mod.rs index 8eab0e517..b5dd72f2b 100644 --- a/packages/hurl/src/http/mod.rs +++ b/packages/hurl/src/http/mod.rs @@ -20,7 +20,7 @@ pub use self::client::{Client, ClientOptions, HttpError}; pub use self::core::{Cookie, Header}; #[cfg(test)] pub use self::request::tests::*; -pub use self::request::{FileParam, Method, MultipartParam, Param, Request, RequestCookie}; +pub use self::request::{Body, FileParam, Method, MultipartParam, Param, Request, RequestCookie}; #[cfg(test)] pub use self::response::tests::*; pub use self::response::{Response, Version}; diff --git a/packages/hurl/src/http/request.rs b/packages/hurl/src/http/request.rs index bbe43b9d7..7c1beddb3 100644 --- a/packages/hurl/src/http/request.rs +++ b/packages/hurl/src/http/request.rs @@ -29,7 +29,7 @@ pub struct Request { pub form: Vec, pub multipart: Vec, pub cookies: Vec, - pub body: Vec, + pub body: Body, pub content_type: Option, } @@ -72,6 +72,23 @@ pub struct RequestCookie { pub value: String, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Body { + Text(String), + Binary(Vec), + File(Vec, String), +} + +impl Body { + pub fn bytes(&self) -> Vec { + match self { + Body::Text(s) => s.as_bytes().to_vec(), + Body::Binary(bs) => bs.clone(), + Body::File(bs, _) => bs.clone(), + } + } +} + impl fmt::Display for Method { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let value = match self { @@ -131,7 +148,7 @@ pub mod tests { querystring: vec![], headers: vec![], cookies: vec![], - body: vec![], + body: Body::Binary(vec![]), multipart: vec![], form: vec![], content_type: None, @@ -163,7 +180,7 @@ pub mod tests { value: String::from("abc123"), }, ], - body: vec![], + body: Body::Binary(vec![]), multipart: vec![], form: vec![], content_type: None, @@ -186,7 +203,7 @@ pub mod tests { ], headers: vec![], cookies: vec![], - body: vec![], + body: Body::Binary(vec![]), multipart: vec![], form: vec![], content_type: None, @@ -203,9 +220,7 @@ pub mod tests { value: String::from("application/x-www-form-urlencoded"), }], cookies: vec![], - body: "param1=value1¶m2=¶m3=a%3db¶m4=a%253db" - .to_string() - .into_bytes(), + body: Body::Text("param1=value1¶m2=¶m3=a%3db¶m4=a%253db".to_string()), multipart: vec![], form: vec![], content_type: Some("multipart/form-data".to_string()), diff --git a/packages/hurl/src/main.rs b/packages/hurl/src/main.rs index 9d40deb87..40b6cd216 100644 --- a/packages/hurl/src/main.rs +++ b/packages/hurl/src/main.rs @@ -145,11 +145,6 @@ fn execute( let connect_timeout = cli_options.connect_timeout; let user = cli_options.user; let compressed = cli_options.compressed; - let accept_encoding = if compressed { - Some("gzip, deflate, br".to_string()) - } else { - None - }; let options = http::ClientOptions { follow_location, max_redirect, @@ -161,7 +156,7 @@ fn execute( timeout, connect_timeout, user, - accept_encoding, + compressed, }; let mut client = http::Client::init(options); diff --git a/packages/hurl/src/runner/body.rs b/packages/hurl/src/runner/body.rs index cbe5b14f0..1a7dbf1a5 100644 --- a/packages/hurl/src/runner/body.rs +++ b/packages/hurl/src/runner/body.rs @@ -25,12 +25,13 @@ use super::core::{Error, RunnerError}; use super::json::eval_json_value; use super::template::eval_template; use super::value::Value; +use crate::http; pub fn eval_body( body: Body, variables: &HashMap, context_dir: String, -) -> Result, Error> { +) -> Result { eval_bytes(body.value, variables, context_dir) } @@ -38,31 +39,37 @@ pub fn eval_bytes( bytes: Bytes, variables: &HashMap, context_dir: String, -) -> Result, Error> { +) -> Result { match bytes { + // Body::Text Bytes::RawString { value, .. } => { let value = eval_template(value, variables)?; - Ok(value.into_bytes()) + Ok(http::Body::Text(value)) } - Bytes::Base64 { value, .. } => Ok(value), - Bytes::Xml { value, .. } => Ok(value.into_bytes()), + Bytes::Xml { value, .. } => Ok(http::Body::Text(value)), Bytes::Json { value, .. } => { let value = eval_json_value(value, variables)?; - Ok(value.into_bytes()) + Ok(http::Body::Text(value)) } + + // Body:: Binary + Bytes::Base64 { value, .. } => Ok(http::Body::Binary(value)), + + // Body::File Bytes::File { filename, .. } => { - let path = Path::new(filename.value.as_str()); + let f = filename.value.as_str(); + let path = Path::new(f); let absolute_filename = if path.is_absolute() { filename.clone().value } else { Path::new(context_dir.as_str()) - .join(filename.value) + .join(f) .to_str() .unwrap() .to_string() }; match std::fs::read(absolute_filename.clone()) { - Ok(bytes) => Ok(bytes), + Ok(value) => Ok(http::Body::File(value, f.to_string())), Err(_) => Err(Error { source_info: filename.source_info, inner: RunnerError::FileReadAccess { @@ -101,7 +108,7 @@ mod tests { let variables = HashMap::new(); assert_eq!( eval_bytes(bytes, &variables, ".".to_string()).unwrap(), - b"Hello World!" + http::Body::File(b"Hello World!".to_vec(), "tests/data.bin".to_string()) ); } diff --git a/packages/hurl/src/runner/hurl_file.rs b/packages/hurl/src/runner/hurl_file.rs index 13692fd24..11008961b 100644 --- a/packages/hurl/src/runner/hurl_file.rs +++ b/packages/hurl/src/runner/hurl_file.rs @@ -58,7 +58,7 @@ use super::entry; /// timeout: Default::default(), /// connect_timeout: Default::default(), /// user: None, -/// accept_encoding: None +/// compressed: false /// }; /// let mut client = http::Client::init(options); /// diff --git a/packages/hurl/src/runner/log_deserialize.rs b/packages/hurl/src/runner/log_deserialize.rs index 209a15d0a..97275a71f 100644 --- a/packages/hurl/src/runner/log_deserialize.rs +++ b/packages/hurl/src/runner/log_deserialize.rs @@ -16,6 +16,7 @@ * */ +use crate::http; use crate::http::*; use super::cookie::*; @@ -160,7 +161,7 @@ pub fn parse_request(value: serde_json::Value) -> Result { // TODO let multipart = vec![]; - let body = vec![]; + let body = http::Body::Binary(vec![]); let content_type = None; Ok(Request { @@ -404,14 +405,14 @@ mod tests { .unwrap(); assert_eq!( parse_request(v).unwrap(), - Request { - method: Method::Get, + http::Request { + method: http::Method::Get, url: "http://localhost:8000/querystring-params?param1=value1¶m2=a%20b" .to_string(), querystring: vec![], headers: vec![], cookies: vec![], - body: vec![], + body: http::Body::Binary(vec![]), form: vec![], multipart: vec![], content_type: None, diff --git a/packages/hurl/src/runner/log_serialize.rs b/packages/hurl/src/runner/log_serialize.rs index 0633728b4..2b752b492 100644 --- a/packages/hurl/src/runner/log_serialize.rs +++ b/packages/hurl/src/runner/log_serialize.rs @@ -106,7 +106,7 @@ impl Serialize for Request { if !self.clone().form.is_empty() { state.serialize_field("form", &self.clone().form)?; } - state.serialize_field("body", &base64::encode(&self.body))?; + state.serialize_field("body", &base64::encode(&self.body.bytes()))?; state.end() } diff --git a/packages/hurl/src/runner/request.rs b/packages/hurl/src/runner/request.rs index aca6f1194..eaa47816b 100644 --- a/packages/hurl/src/runner/request.rs +++ b/packages/hurl/src/runner/request.rs @@ -78,9 +78,9 @@ pub fn eval_request( cookies.push(cookie); } - let bytes = match request.clone().body { + let body = match request.clone().body { Some(body) => eval_body(body, variables, context_dir.clone())?, - None => vec![], + None => http::Body::Binary(vec![]), }; let mut multipart = vec![]; @@ -118,12 +118,12 @@ pub fn eval_request( Ok(http::Request { method, url, - querystring, headers, - cookies, - body: bytes, - multipart, + querystring, form, + multipart, + cookies, + body, content_type, }) } diff --git a/packages/hurl/src/runner/response.rs b/packages/hurl/src/runner/response.rs index 2f5104998..4d441d5b2 100644 --- a/packages/hurl/src/runner/response.rs +++ b/packages/hurl/src/runner/response.rs @@ -190,7 +190,7 @@ pub fn eval_asserts( }), Bytes::File { .. } => { let expected = match eval_body(body.clone(), variables, context_dir) { - Ok(bytes) => Ok(Value::Bytes(bytes)), + Ok(body) => Ok(Value::Bytes(body.bytes())), Err(e) => Err(e), }; let actual = Ok(Value::Bytes(http_response.body.clone())); diff --git a/packages/hurl/tests/libcurl.rs b/packages/hurl/tests/libcurl.rs index dd6bcd24a..f51c5d587 100644 --- a/packages/hurl/tests/libcurl.rs +++ b/packages/hurl/tests/libcurl.rs @@ -21,7 +21,7 @@ fn default_client() -> Client { timeout: Default::default(), connect_timeout: Duration::from_secs(300), user: None, - accept_encoding: None, + compressed: false, }; Client::init(options) } @@ -35,7 +35,7 @@ fn default_get_request(url: String) -> Request { form: vec![], multipart: vec![], cookies: vec![], - body: vec![], + body: Body::Binary(vec![]), content_type: None, } } @@ -78,7 +78,7 @@ fn test_put() { form: vec![], multipart: vec![], cookies: vec![], - body: vec![], + body: Body::Binary(vec![]), content_type: None, }; let response = client.execute(&request, 0).unwrap(); @@ -110,7 +110,7 @@ fn test_patch() { form: vec![], multipart: vec![], cookies: vec![], - body: vec![], + body: Body::Binary(vec![]), content_type: None, }; let response = client.execute(&request, 0).unwrap(); @@ -139,7 +139,7 @@ fn test_custom_headers() { form: vec![], multipart: vec![], cookies: vec![], - body: vec![], + body: Body::Binary(vec![]), content_type: None, }; let response = client.execute(&request, 0).unwrap(); @@ -179,7 +179,7 @@ fn test_querystring_params() { form: vec![], multipart: vec![], cookies: vec![], - body: vec![], + body: Body::Binary(vec![]), content_type: None, }; let response = client.execute(&request, 0).unwrap(); @@ -219,7 +219,7 @@ fn test_form_params() { ], multipart: vec![], cookies: vec![], - body: vec![], + body: Body::Binary(vec![]), content_type: Some("application/x-www-form-urlencoded".to_string()), }; let response = client.execute(&request, 0).unwrap(); @@ -264,7 +264,7 @@ fn test_follow_location() { timeout: Default::default(), connect_timeout: Default::default(), user: None, - accept_encoding: None, + compressed: false, }; let mut client = Client::init(options); let response = client.execute(&request, 0).unwrap(); @@ -299,7 +299,7 @@ fn test_max_redirect() { timeout: Default::default(), connect_timeout: Default::default(), user: None, - accept_encoding: None, + compressed: false, }; let mut client = Client::init(options); let request = default_get_request("http://localhost:8000/redirect".to_string()); @@ -349,7 +349,7 @@ fn test_multipart_form_data() { }), ], cookies: vec![], - body: vec![], + body: Body::Binary(vec![]), content_type: Some("multipart/form-data".to_string()), }; let response = client.execute(&request, 0).unwrap(); @@ -378,7 +378,7 @@ fn test_post_bytes() { form: vec![], multipart: vec![], cookies: vec![], - body: b"Hello World!".to_vec(), + body: Body::Binary(b"Hello World!".to_vec()), content_type: None, }; let response = client.execute(&request, 0).unwrap(); @@ -402,7 +402,7 @@ fn test_expect() { form: vec![], multipart: vec![], cookies: vec![], - body: b"data".to_vec(), + body: Body::Text("data".to_string()), content_type: None, }; let response = client.execute(&request, 0).unwrap(); @@ -424,7 +424,7 @@ fn test_basic_authentication() { timeout: Default::default(), connect_timeout: Duration::from_secs(300), user: Some("bob:secret".to_string()), - accept_encoding: None, + compressed: false, }; let mut client = Client::init(options); let request = Request { @@ -435,7 +435,7 @@ fn test_basic_authentication() { form: vec![], multipart: vec![], cookies: vec![], - body: vec![], + body: Body::Binary(vec![]), content_type: None, }; let response = client.execute(&request, 0).unwrap(); @@ -452,7 +452,7 @@ fn test_basic_authentication() { form: vec![], multipart: vec![], cookies: vec![], - body: vec![], + body: Body::Binary(vec![]), content_type: None, }; let response = client.execute(&request, 0).unwrap(); @@ -490,7 +490,7 @@ fn test_error_fail_to_connect() { timeout: Default::default(), connect_timeout: Default::default(), user: None, - accept_encoding: None, + compressed: false, }; let mut client = Client::init(options); let request = default_get_request("http://localhost:8000/hello".to_string()); @@ -511,7 +511,7 @@ fn test_error_could_not_resolve_proxy_name() { timeout: Default::default(), connect_timeout: Default::default(), user: None, - accept_encoding: None, + compressed: false, }; let mut client = Client::init(options); let request = default_get_request("http://localhost:8000/hello".to_string()); @@ -532,7 +532,7 @@ fn test_error_ssl() { timeout: Default::default(), connect_timeout: Default::default(), user: None, - accept_encoding: None, + compressed: false, }; let mut client = Client::init(options); let request = default_get_request("https://localhost:8001/hello".to_string()); @@ -558,7 +558,7 @@ fn test_timeout() { timeout: Duration::from_millis(100), connect_timeout: Default::default(), user: None, - accept_encoding: None, + compressed: false, }; let mut client = Client::init(options); let request = default_get_request("http://localhost:8000/timeout".to_string()); @@ -579,7 +579,7 @@ fn test_accept_encoding() { timeout: Default::default(), connect_timeout: Duration::from_secs(300), user: None, - accept_encoding: Some("gzip".to_string()), + compressed: true, }; let mut client = Client::init(options); let request = Request { @@ -590,7 +590,7 @@ fn test_accept_encoding() { form: vec![], multipart: vec![], cookies: vec![], - body: vec![], + body: Body::Binary(vec![]), content_type: None, }; let response = client.execute(&request, 0).unwrap(); @@ -614,7 +614,7 @@ fn test_connect_timeout() { timeout: Default::default(), connect_timeout: Duration::from_secs(1), user: None, - accept_encoding: None, + compressed: false, }; let mut client = Client::init(options); let request = default_get_request("http://10.0.0.0".to_string()); @@ -643,7 +643,7 @@ fn test_cookie() { name: "cookie1".to_string(), value: "valueA".to_string(), }], - body: vec![], + body: Body::Binary(vec![]), content_type: None, }; @@ -659,7 +659,7 @@ fn test_cookie() { form: vec![], multipart: vec![], cookies: vec![], - body: vec![], + body: Body::Binary(vec![]), content_type: None, }; let response = client.execute(&request, 0).unwrap(); @@ -686,7 +686,7 @@ fn test_multiple_request_cookies() { value: "Bill".to_string(), }, ], - body: vec![], + body: Body::Binary(vec![]), content_type: None, }; let response = client.execute(&request, 0).unwrap(); @@ -738,7 +738,7 @@ fn test_cookie_file() { timeout: Default::default(), connect_timeout: Default::default(), user: None, - accept_encoding: None, + compressed: false, }; let mut client = Client::init(options); let request = default_get_request( @@ -767,7 +767,7 @@ fn test_proxy() { timeout: Default::default(), connect_timeout: Default::default(), user: None, - accept_encoding: None, + compressed: false, }; let mut client = Client::init(options); let request = default_get_request("http://localhost:8000/proxy".to_string()); @@ -790,7 +790,7 @@ fn test_insecure() { timeout: Default::default(), connect_timeout: Default::default(), user: None, - accept_encoding: None, + compressed: false, }; let mut client = Client::init(options); let request = default_get_request("https://localhost:8001/hello".to_string()); diff --git a/packages/hurl/tests/runner.rs b/packages/hurl/tests/runner.rs index ab5a2714d..d2e19f092 100644 --- a/packages/hurl/tests/runner.rs +++ b/packages/hurl/tests/runner.rs @@ -53,7 +53,7 @@ fn test_hurl_file() { timeout: Default::default(), connect_timeout: Default::default(), user: None, - accept_encoding: None, + compressed: false, }; let mut client = http::Client::init(options); let mut lines: Vec<&str> = regex::Regex::new(r"\n|\r\n") @@ -163,7 +163,7 @@ fn test_hello() { timeout: Default::default(), connect_timeout: Default::default(), user: None, - accept_encoding: None, + compressed: false, }; let mut client = http::Client::init(options); let source_info = SourceInfo {