Merge pull request #186 from Orange-OpenSource/feature/refacto-http-client

Refacto http client
This commit is contained in:
Fabrice Reix 2021-05-16 19:23:47 +02:00 committed by GitHub
commit 8e1fdafca6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 115 additions and 116 deletions

View File

@ -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<easy::Easy>,
/// 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<usize>,
pub verbose: bool,
pub verify: bool,
pub proxy: Option<String>,
pub no_proxy: Option<String>,
pub timeout: Duration,
pub connect_timeout: Duration,
pub authorization: Option<String>,
pub accept_encoding: Option<String>,
// 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<String>,
pub accept_encoding: Option<String>,
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<Header>) -> Option<String> {
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();

View File

@ -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};

View File

@ -29,7 +29,7 @@ pub struct Request {
pub form: Vec<Param>,
pub multipart: Vec<MultipartParam>,
pub cookies: Vec<RequestCookie>,
pub body: Vec<u8>,
pub body: Body,
pub content_type: Option<String>,
}
@ -72,6 +72,23 @@ pub struct RequestCookie {
pub value: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Body {
Text(String),
Binary(Vec<u8>),
File(Vec<u8>, String),
}
impl Body {
pub fn bytes(&self) -> Vec<u8> {
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&param2=&param3=a%3db&param4=a%253db"
.to_string()
.into_bytes(),
body: Body::Text("param1=value1&param2=&param3=a%3db&param4=a%253db".to_string()),
multipart: vec![],
form: vec![],
content_type: Some("multipart/form-data".to_string()),

View File

@ -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);

View File

@ -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<String, Value>,
context_dir: String,
) -> Result<Vec<u8>, Error> {
) -> Result<http::Body, Error> {
eval_bytes(body.value, variables, context_dir)
}
@ -38,31 +39,37 @@ pub fn eval_bytes(
bytes: Bytes,
variables: &HashMap<String, Value>,
context_dir: String,
) -> Result<Vec<u8>, Error> {
) -> Result<http::Body, Error> {
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())
);
}

View File

@ -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);
///

View File

@ -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<Request, ParseError> {
// 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&param2=a%20b"
.to_string(),
querystring: vec![],
headers: vec![],
cookies: vec![],
body: vec![],
body: http::Body::Binary(vec![]),
form: vec![],
multipart: vec![],
content_type: None,

View File

@ -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()
}

View File

@ -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,
})
}

View File

@ -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()));

View File

@ -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());

View File

@ -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 {