diff --git a/integration/tests_ok/connect_to.curl b/integration/tests_ok/connect_to.curl new file mode 100644 index 000000000..df1f17102 --- /dev/null +++ b/integration/tests_ok/connect_to.curl @@ -0,0 +1,3 @@ +curl --connect-to foo.com:80:localhost:8000 --connect-to bar.com:80:localhost:8000 --connect-to baz.com:80:localhost:8000 'http://foo.com/hello' +curl --connect-to foo.com:80:localhost:8000 --connect-to bar.com:80:localhost:8000 --connect-to baz.com:80:localhost:8000 'http://bar.com/hello' +curl --connect-to foo.com:80:localhost:8000 --connect-to bar.com:80:localhost:8000 --connect-to baz.com:80:localhost:8000 'http://baz.com/hello' \ No newline at end of file diff --git a/integration/tests_ok/connect_to.html b/integration/tests_ok/connect_to.html new file mode 100644 index 000000000..d4c0a9e1a --- /dev/null +++ b/integration/tests_ok/connect_to.html @@ -0,0 +1,14 @@ +
GET http://foo.com/hello
+HTTP 200
+`Hello World!`
+
+
+GET http://bar.com/hello
+HTTP 200
+`Hello World!`
+
+
+GET http://baz.com/hello
+HTTP 200
+`Hello World!`
+
diff --git a/integration/tests_ok/connect_to.hurl b/integration/tests_ok/connect_to.hurl new file mode 100644 index 000000000..d2a2f8827 --- /dev/null +++ b/integration/tests_ok/connect_to.hurl @@ -0,0 +1,13 @@ +GET http://foo.com/hello +HTTP 200 +`Hello World!` + + +GET http://bar.com/hello +HTTP 200 +`Hello World!` + + +GET http://baz.com/hello +HTTP 200 +`Hello World!` diff --git a/integration/tests_ok/connect_to.json b/integration/tests_ok/connect_to.json new file mode 100644 index 000000000..f99d9c17b --- /dev/null +++ b/integration/tests_ok/connect_to.json @@ -0,0 +1 @@ +{"entries":[{"request":{"method":"GET","url":"http://foo.com/hello"},"response":{"status":200,"body":{"type":"text","value":"Hello World!"}}},{"request":{"method":"GET","url":"http://bar.com/hello"},"response":{"status":200,"body":{"type":"text","value":"Hello World!"}}},{"request":{"method":"GET","url":"http://baz.com/hello"},"response":{"status":200,"body":{"type":"text","value":"Hello World!"}}}]} diff --git a/integration/tests_ok/connect_to.options b/integration/tests_ok/connect_to.options new file mode 100644 index 000000000..edbf20cb9 --- /dev/null +++ b/integration/tests_ok/connect_to.options @@ -0,0 +1,6 @@ +--connect-to +foo.com:80:localhost:8000 +--connect-to +bar.com:80:localhost:8000 +--connect-to +baz.com:80:localhost:8000 diff --git a/integration/tests_ok/connect_to.out b/integration/tests_ok/connect_to.out new file mode 100644 index 000000000..c57eff55e --- /dev/null +++ b/integration/tests_ok/connect_to.out @@ -0,0 +1 @@ +Hello World! \ No newline at end of file diff --git a/packages/hurl/src/cli/options.rs b/packages/hurl/src/cli/options.rs index 52225282e..c160c894e 100644 --- a/packages/hurl/src/cli/options.rs +++ b/packages/hurl/src/cli/options.rs @@ -37,6 +37,7 @@ pub struct CliOptions { pub client_cert_file: Option, pub client_key_file: Option, pub color: bool, + pub connects_to: Vec, pub compressed: bool, pub connect_timeout: Duration, pub cookie_input_file: Option, @@ -145,6 +146,15 @@ pub fn app(version: &str) -> Command { .value_parser(value_parser!(u64)) .num_args(1) ) + .arg( + clap::Arg::new("connect_to") + .long("connect-to") + .value_name("HOST1:PORT1:HOST2:PORT2") + .help("For a request to the given HOST1:PORT1 pair, connect to HOST2:PORT2 instead") + .action(ArgAction::Append) + .number_of_values(1) + .num_args(1) + ) .arg( clap::Arg::new("cookies_input_file") .short('b') @@ -295,12 +305,6 @@ pub fn app(version: &str) -> Command { .help("Generate HTML report to DIR") .num_args(1) ) - .arg( - clap::Arg::new("retry") - .long("retry") - .help("Retry requests on errors") - .action(ArgAction::SetTrue) - ) .arg( clap::Arg::new("resolve") .long("resolve") @@ -310,6 +314,12 @@ pub fn app(version: &str) -> Command { .number_of_values(1) .num_args(1) ) + .arg( + clap::Arg::new("retry") + .long("retry") + .help("Retry requests on errors") + .action(ArgAction::SetTrue) + ) .arg( clap::Arg::new("retry_interval") .long("retry-interval") @@ -431,6 +441,7 @@ pub fn parse_options(matches: &ArgMatches) -> Result { let compressed = has_flag(matches, "compressed"); let connect_timeout = get::(matches, "connect_timeout").unwrap(); let connect_timeout = Duration::from_secs(connect_timeout); + let connects_to = get_strings(matches, "connect_to").unwrap_or_default(); let cookie_input_file = get::(matches, "cookies_input_file"); let cookie_output_file = get::(matches, "cookies_output_file"); let fail_fast = !has_flag(matches, "fail_at_end"); @@ -505,6 +516,7 @@ pub fn parse_options(matches: &ArgMatches) -> Result { color, compressed, connect_timeout, + connects_to, cookie_input_file, cookie_output_file, fail_fast, diff --git a/packages/hurl/src/http/client.rs b/packages/hurl/src/http/client.rs index b14d0c3fe..f99420ca0 100644 --- a/packages/hurl/src/http/client.rs +++ b/packages/hurl/src/http/client.rs @@ -127,28 +127,28 @@ impl Client { // way to get access to the outgoing headers. self.handle.verbose(true).unwrap(); + if !options.connects_to.is_empty() { + let connects = to_list(&options.connects_to); + self.handle.connect_to(connects).unwrap(); + } if !options.resolves.is_empty() { let resolves = to_list(&options.resolves); self.handle.resolve(resolves).unwrap(); } - self.handle.ssl_verify_host(!options.insecure).unwrap(); self.handle.ssl_verify_peer(!options.insecure).unwrap(); if let Some(cacert_file) = options.cacert_file.clone() { self.handle.cainfo(cacert_file).unwrap(); self.handle.ssl_cert_type("PEM").unwrap(); } - if let Some(client_cert_file) = options.client_cert_file.clone() { self.handle.ssl_cert(client_cert_file).unwrap(); self.handle.ssl_cert_type("PEM").unwrap(); } - if let Some(client_key_file) = options.client_key_file.clone() { self.handle.ssl_key(client_key_file).unwrap(); self.handle.ssl_cert_type("PEM").unwrap(); } - if let Some(proxy) = options.proxy.clone() { self.handle.proxy(proxy.as_str()).unwrap(); } diff --git a/packages/hurl/src/http/options.rs b/packages/hurl/src/http/options.rs index d86db7361..8e8a9cf59 100644 --- a/packages/hurl/src/http/options.rs +++ b/packages/hurl/src/http/options.rs @@ -22,6 +22,7 @@ pub struct ClientOptions { pub cacert_file: Option, pub client_cert_file: Option, pub client_key_file: Option, + pub connects_to: Vec, pub follow_location: bool, pub max_redirect: Option, pub cookie_input_file: Option, @@ -50,6 +51,7 @@ impl Default for ClientOptions { cacert_file: None, client_cert_file: None, client_key_file: None, + connects_to: vec![], follow_location: false, max_redirect: Some(50), cookie_input_file: None, @@ -77,31 +79,29 @@ impl ClientOptions { arguments.push("--cacert".to_string()); arguments.push(cacert_file.clone()); } - if let Some(ref client_cert_file) = self.client_cert_file { arguments.push("--cert".to_string()); arguments.push(client_cert_file.clone()); } - if let Some(ref client_key_file) = self.client_key_file { arguments.push("--key".to_string()); arguments.push(client_key_file.clone()); } - if self.compressed { arguments.push("--compressed".to_string()); } - if self.connect_timeout != ClientOptions::default().connect_timeout { arguments.push("--connect-timeout".to_string()); arguments.push(self.connect_timeout.as_secs().to_string()); } - + for connect in self.connects_to.iter() { + arguments.push("--connect-to".to_string()); + arguments.push(connect.clone()); + } if let Some(ref cookie_file) = self.cookie_input_file { arguments.push("--cookie".to_string()); arguments.push(cookie_file.clone()); } - if self.insecure { arguments.push("--insecure".to_string()); } @@ -153,6 +153,7 @@ mod tests { cacert_file: None, client_cert_file: None, client_key_file: None, + connects_to: vec!["example.com:443:host-47.example.com:443".to_string()], follow_location: true, max_redirect: Some(10), cookie_input_file: Some("cookie_file".to_string()), @@ -176,6 +177,8 @@ mod tests { "--compressed".to_string(), "--connect-timeout".to_string(), "20".to_string(), + "--connect-to".to_string(), + "example.com:443:host-47.example.com:443".to_string(), "--cookie".to_string(), "cookie_file".to_string(), "--insecure".to_string(), diff --git a/packages/hurl/src/runner/entry.rs b/packages/hurl/src/runner/entry.rs index 1b6183d77..f08980ddc 100644 --- a/packages/hurl/src/runner/entry.rs +++ b/packages/hurl/src/runner/entry.rs @@ -226,6 +226,7 @@ impl From<&RunnerOptions> for ClientOptions { cacert_file: runner_options.cacert_file.clone(), client_cert_file: runner_options.client_cert_file.clone(), client_key_file: runner_options.client_key_file.clone(), + connects_to: runner_options.connects_to.clone(), follow_location: runner_options.follow_location, max_redirect: runner_options.max_redirect, cookie_input_file: runner_options.cookie_input_file.clone(), diff --git a/packages/hurl/src/runner/runner_options.rs b/packages/hurl/src/runner/runner_options.rs index 45b657ee5..0863091d9 100644 --- a/packages/hurl/src/runner/runner_options.rs +++ b/packages/hurl/src/runner/runner_options.rs @@ -30,6 +30,7 @@ pub struct RunnerOptions { pub client_key_file: Option, pub compressed: bool, pub connect_timeout: Duration, + pub connects_to: Vec, pub context_dir: ContextDir, pub cookie_input_file: Option, pub fail_fast: bool, @@ -61,6 +62,7 @@ impl Default for RunnerOptions { client_key_file: None, compressed: false, connect_timeout: Duration::from_secs(300), + connects_to: vec![], context_dir: Default::default(), cookie_input_file: None, fail_fast: false, @@ -91,6 +93,7 @@ impl RunnerOptions { let cacert_file = cli_options.cacert_file.clone(); let client_cert_file = cli_options.client_cert_file.clone(); let client_key_file = cli_options.client_key_file.clone(); + let connects_to = cli_options.connects_to.clone(); let follow_location = cli_options.follow_location; let verbosity = match (cli_options.verbose, cli_options.very_verbose) { (true, true) => Some(Verbosity::VeryVerbose), @@ -143,6 +146,7 @@ impl RunnerOptions { client_key_file, compressed, connect_timeout, + connects_to, context_dir, cookie_input_file, fail_fast,