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,