From b60c43529b7583d8697a7c2670f22a36db94f259 Mon Sep 17 00:00:00 2001 From: Zandt Tittle Date: Wed, 3 Jan 2024 23:53:24 -0600 Subject: [PATCH] Add --unix-socket option This is a feature provided by cURL that allows sending requests to a web server via a Unix Domain Socket. Some use cases include: * Querying docker API * Running a web server w/o opening a TCP port for security and/or performance reasons https://curl.se/docs/manpage.html#--unix-socket --- README.md | 1 + bin/test/test_prerequisites.sh | 3 ++ contrib/npm/hurl/docs/hurl.1 | 4 +++ contrib/sample/src/main.rs | 1 + docs/manual.md | 1 + docs/manual/hurl.1 | 4 +++ docs/manual/hurl.md | 4 +++ docs/spec/grammar/hurl.grammar | 3 ++ docs/spec/options/hurl/unix_socket.option | 6 ++++ integration/README.md | 2 +- .../tests_error_parser/invalid_option.err | 2 +- .../hurl/tests_failed/unix_socket.err.pattern | 7 ++++ .../hurl/tests_failed/unix_socket.exit | 1 + integration/hurl/tests_failed/unix_socket.ps1 | 8 +++++ integration/hurl/tests_failed/unix_socket.sh | 3 ++ integration/hurl/tests_ok/help.out.pattern | 2 ++ integration/hurl/tests_ok/unix_socket.curl | 1 + integration/hurl/tests_ok/unix_socket.hurl | 4 +++ integration/hurl/tests_ok/unix_socket.out | 1 + integration/hurl/tests_ok/unix_socket.ps1 | 8 +++++ integration/hurl/tests_ok/unix_socket.sh | 3 ++ integration/hurl/unix_socket/server.py | 33 +++++++++++++++++++ .../tests_export/unix_socket_option.html | 9 +++++ .../tests_export/unix_socket_option.hurl | 8 +++++ .../tests_export/unix_socket_option.json | 1 + packages/hurl/README.md | 1 + packages/hurl/src/cli/options/commands.rs | 8 +++++ packages/hurl/src/cli/options/matches.rs | 4 +++ packages/hurl/src/cli/options/mod.rs | 6 ++++ packages/hurl/src/http/client.rs | 5 +++ packages/hurl/src/http/options.rs | 9 +++++ packages/hurl/src/runner/entry.rs | 1 + packages/hurl/src/runner/hurl_file.rs | 6 ++++ packages/hurl/src/runner/options.rs | 4 +++ packages/hurl/src/runner/runner_options.rs | 10 ++++++ packages/hurl/tests/sample.rs | 1 + packages/hurl_core/src/ast/core.rs | 3 ++ packages/hurl_core/src/format/html.rs | 1 + packages/hurl_core/src/parser/error.rs | 1 + packages/hurl_core/src/parser/option.rs | 6 ++++ packages/hurlfmt/src/format/json.rs | 1 + packages/hurlfmt/src/format/token.rs | 1 + 42 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 docs/spec/options/hurl/unix_socket.option create mode 100644 integration/hurl/tests_failed/unix_socket.err.pattern create mode 100644 integration/hurl/tests_failed/unix_socket.exit create mode 100644 integration/hurl/tests_failed/unix_socket.ps1 create mode 100755 integration/hurl/tests_failed/unix_socket.sh create mode 100644 integration/hurl/tests_ok/unix_socket.curl create mode 100644 integration/hurl/tests_ok/unix_socket.hurl create mode 100644 integration/hurl/tests_ok/unix_socket.out create mode 100644 integration/hurl/tests_ok/unix_socket.ps1 create mode 100755 integration/hurl/tests_ok/unix_socket.sh create mode 100644 integration/hurl/unix_socket/server.py create mode 100644 integration/hurlfmt/tests_export/unix_socket_option.html create mode 100644 integration/hurlfmt/tests_export/unix_socket_option.hurl create mode 100644 integration/hurlfmt/tests_export/unix_socket_option.json diff --git a/README.md b/README.md index 05b214a807b..49b75a61543 100644 --- a/README.md +++ b/README.md @@ -972,6 +972,7 @@ will follow a redirection only for the second entry. | --ssl-no-revoke | (Windows) This option tells Hurl to disable certificate revocation checks. WARNING: this option loosens the SSL security, and by using this flag you ask for exactly that.
| | --test | Activate test mode: with this, the HTTP response is not outputted anymore, progress is reported for each Hurl file tested, and a text summary is displayed when all files have been run.
| | --to-entry <ENTRY_NUMBER> | Execute Hurl file to ENTRY_NUMBER (starting at 1).
Ignore the remaining of the file. It is useful for debugging a session.
| +| --unix-socket <PATH> | (HTTP) Connect through this Unix domain socket, instead of using the network.
| | -u, --user <USER:PASSWORD> | Add basic Authentication header to each request.
| | -A, --user-agent <NAME> | Specify the User-Agent string to send to the HTTP server.
| | --variable <NAME=VALUE> | Define variable (name/value) to be used in Hurl templates.
| diff --git a/bin/test/test_prerequisites.sh b/bin/test/test_prerequisites.sh index bf8eba46d06..5feb3997abd 100755 --- a/bin/test/test_prerequisites.sh +++ b/bin/test/test_prerequisites.sh @@ -61,6 +61,9 @@ echo -e "\n------------------ Starting ssl/server.py (Self-signed certificate + nohup python3 ssl/server.py 8003 ssl/server/cert.selfsigned.pem true > build/server-ssl-client-authent.log 2>&1 & check_listen_port "ssl/server.py" 8003 || cat_and_exit_err build/server-ssl-client-authent.log +echo -e "\n------------------ Starting unix_socket/server.py" +python3 unix_socket/server.py > build/server-unix-socket.log 2>&1 & + echo -e "\n------------------ Starting squid (proxy)" if [ -f /var/run/squid.pid ] ; then sudo squid -k shutdown || true diff --git a/contrib/npm/hurl/docs/hurl.1 b/contrib/npm/hurl/docs/hurl.1 index 21b62bee353..0cad6e13030 100644 --- a/contrib/npm/hurl/docs/hurl.1 +++ b/contrib/npm/hurl/docs/hurl.1 @@ -306,6 +306,10 @@ Duration in milliseconds between each retry. Default is 1000 ms. (Windows) This option tells Hurl to disable certificate revocation checks. WARNING: this option loosens the SSL security, and by using this flag you ask for exactly that. +.IP "--unix-socket " + +(HTTP) Connect through this Unix domain socket, instead of using the network. + .IP "--test " Activate test mode: with this, the HTTP response is not outputted anymore, progress is reported for each Hurl file tested, and a text summary is displayed when all files have been run. diff --git a/contrib/sample/src/main.rs b/contrib/sample/src/main.rs index ebe3b510dff..7569f3b4763 100644 --- a/contrib/sample/src/main.rs +++ b/contrib/sample/src/main.rs @@ -76,6 +76,7 @@ fn main() { .retry(Retry::None) .retry_interval(Duration::from_secs(1)) .ssl_no_revoke(false) + .unix_socket(None) .timeout(Duration::from_secs(300)) .to_entry(None) .user(None) diff --git a/docs/manual.md b/docs/manual.md index 38612c694b7..4be7906c189 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -191,6 +191,7 @@ will follow a redirection only for the second entry. | --ssl-no-revoke | (Windows) This option tells Hurl to disable certificate revocation checks. WARNING: this option loosens the SSL security, and by using this flag you ask for exactly that.
| | --test | Activate test mode: with this, the HTTP response is not outputted anymore, progress is reported for each Hurl file tested, and a text summary is displayed when all files have been run.
| | --to-entry <ENTRY_NUMBER> | Execute Hurl file to ENTRY_NUMBER (starting at 1).
Ignore the remaining of the file. It is useful for debugging a session.
| +| --unix-socket <PATH> | (HTTP) Connect through this Unix domain socket, instead of using the network.
| | -u, --user <USER:PASSWORD> | Add basic Authentication header to each request.
| | -A, --user-agent <NAME> | Specify the User-Agent string to send to the HTTP server.
| | --variable <NAME=VALUE> | Define variable (name/value) to be used in Hurl templates.
| diff --git a/docs/manual/hurl.1 b/docs/manual/hurl.1 index ac462c3b959..2b231b8d536 100644 --- a/docs/manual/hurl.1 +++ b/docs/manual/hurl.1 @@ -332,6 +332,10 @@ Duration in milliseconds between each retry. Default is 1000 ms. (Windows) This option tells Hurl to disable certificate revocation checks. WARNING: this option loosens the SSL security, and by using this flag you ask for exactly that. +.IP "--unix-socket " + +(HTTP) Connect through this Unix domain socket, instead of using the network. + .IP "--test " Activate test mode: with this, the HTTP response is not outputted anymore, progress is reported for each Hurl file tested, and a text summary is displayed when all files have been run. diff --git a/docs/manual/hurl.md b/docs/manual/hurl.md index b4406a0a85e..3329aabd718 100644 --- a/docs/manual/hurl.md +++ b/docs/manual/hurl.md @@ -361,6 +361,10 @@ Activate test mode: with this, the HTTP response is not outputted anymore, progr Execute Hurl file to ENTRY_NUMBER (starting at 1). Ignore the remaining of the file. It is useful for debugging a session. +### --unix-socket {#unix-socket} + +(HTTP) Connect through this Unix domain socket, instead of using the network. + ### -u, --user {#user} Add basic Authentication header to each request. diff --git a/docs/spec/grammar/hurl.grammar b/docs/spec/grammar/hurl.grammar index e9f0376ae50..4a093ab8647 100644 --- a/docs/spec/grammar/hurl.grammar +++ b/docs/spec/grammar/hurl.grammar @@ -139,6 +139,7 @@ option: | retry-option | retry-interval-option | skip-option + | unix-socket-option | variable-option | verbose-option | very-verbose-option @@ -190,6 +191,8 @@ retry-interval-option: "retry-interval" ":" integer-option lt skip-option: "skip" ":" boolean-option lt +unix-socket-option: "unix-socket" ":" value-string lt + variable-option: "variable" ":" variable-definition lt verbose-option: "verbose" ":" boolean-option lt diff --git a/docs/spec/options/hurl/unix_socket.option b/docs/spec/options/hurl/unix_socket.option new file mode 100644 index 00000000000..0bdb1731fa9 --- /dev/null +++ b/docs/spec/options/hurl/unix_socket.option @@ -0,0 +1,6 @@ +name: unix_socket +long: unix-socket +value: +help: (HTTP) Connect through this Unix domain socket, instead of using the network +--- +(HTTP) Connect through this Unix domain socket, instead of using the network. diff --git a/integration/README.md b/integration/README.md index dfd264f9a96..43dd355c0de 100644 --- a/integration/README.md +++ b/integration/README.md @@ -25,7 +25,7 @@ virtual environment and install required dependencies: ```shell $ python3 -m venv .venv $ source .venv/bin/activate -$ pip install --requirements bin/requirements-fozen.txt +$ pip install --requirement bin/requirements-frozen.txt ``` ### Proxy diff --git a/integration/hurl/tests_error_parser/invalid_option.err b/integration/hurl/tests_error_parser/invalid_option.err index 36a93b4e3d8..7563577ab7a 100644 --- a/integration/hurl/tests_error_parser/invalid_option.err +++ b/integration/hurl/tests_error_parser/invalid_option.err @@ -2,6 +2,6 @@ error: Parsing option --> tests_error_parser/invalid_option.hurl:3:1 | 3 | foo: true - | ^ the option name is not valid. Valid values are aws-sigv4, cacert, cert, compressed, connect-to, delay, insecure, http1.0, http1.1, http2, http3, ipv4, ipv6, key, location, max-redirs, output, path-as-is, proxy, resolve, retry, retry-interval, skip, variable, verbose, very-verbose + | ^ the option name is not valid. Valid values are aws-sigv4, cacert, cert, compressed, connect-to, delay, insecure, http1.0, http1.1, http2, http3, ipv4, ipv6, key, location, max-redirs, output, path-as-is, proxy, resolve, retry, retry-interval, skip, unix-socket, variable, verbose, very-verbose | diff --git a/integration/hurl/tests_failed/unix_socket.err.pattern b/integration/hurl/tests_failed/unix_socket.err.pattern new file mode 100644 index 00000000000..cfa234fc724 --- /dev/null +++ b/integration/hurl/tests_failed/unix_socket.err.pattern @@ -0,0 +1,7 @@ +error: HTTP connection + --> tests_ok/unix_socket.hurl:1:5 + | + 1 | GET http://example/hello + | ^^^^^^^^^^^^^^^^^^^^ (7) ~~~ + | + diff --git a/integration/hurl/tests_failed/unix_socket.exit b/integration/hurl/tests_failed/unix_socket.exit new file mode 100644 index 00000000000..e440e5c8425 --- /dev/null +++ b/integration/hurl/tests_failed/unix_socket.exit @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/integration/hurl/tests_failed/unix_socket.ps1 b/integration/hurl/tests_failed/unix_socket.ps1 new file mode 100644 index 00000000000..44d27e99d7a --- /dev/null +++ b/integration/hurl/tests_failed/unix_socket.ps1 @@ -0,0 +1,8 @@ +Set-StrictMode -Version latest + +# The python test server for testing Unix Domain Sockets +# (../unix_socket/server.py) does not currently support AF_UNIX on Windows. +# See https://github.com/python/cpython/issues/77589 +# Skip for now until this can be easily tested. +$ErrorActionPreference = 'Continue' +exit 255 diff --git a/integration/hurl/tests_failed/unix_socket.sh b/integration/hurl/tests_failed/unix_socket.sh new file mode 100755 index 00000000000..f8388570586 --- /dev/null +++ b/integration/hurl/tests_failed/unix_socket.sh @@ -0,0 +1,3 @@ +#!/bin/bash +set -Eeuo pipefail +hurl --unix-socket /unknown tests_ok/unix_socket.hurl diff --git a/integration/hurl/tests_ok/help.out.pattern b/integration/hurl/tests_ok/help.out.pattern index ec63b48a52b..c8bd45170d0 100644 --- a/integration/hurl/tests_ok/help.out.pattern +++ b/integration/hurl/tests_ok/help.out.pattern @@ -97,6 +97,8 @@ Options: Activate test mode --to-entry Execute Hurl file to ENTRY_NUMBER (starting at 1) + --unix-socket + (HTTP) Connect through this Unix domain socket, instead of using the network. -A, --user-agent Specify the User-Agent string to send to the HTTP server -u, --user diff --git a/integration/hurl/tests_ok/unix_socket.curl b/integration/hurl/tests_ok/unix_socket.curl new file mode 100644 index 00000000000..63982765bb6 --- /dev/null +++ b/integration/hurl/tests_ok/unix_socket.curl @@ -0,0 +1 @@ +curl --unix-socket 'build/unix_socket.sock' 'http://example/hello' diff --git a/integration/hurl/tests_ok/unix_socket.hurl b/integration/hurl/tests_ok/unix_socket.hurl new file mode 100644 index 00000000000..c6b3f4b9b31 --- /dev/null +++ b/integration/hurl/tests_ok/unix_socket.hurl @@ -0,0 +1,4 @@ +GET http://example/hello +HTTP 200 +[Asserts] +`Hello World!` diff --git a/integration/hurl/tests_ok/unix_socket.out b/integration/hurl/tests_ok/unix_socket.out new file mode 100644 index 00000000000..c57eff55ebc --- /dev/null +++ b/integration/hurl/tests_ok/unix_socket.out @@ -0,0 +1 @@ +Hello World! \ No newline at end of file diff --git a/integration/hurl/tests_ok/unix_socket.ps1 b/integration/hurl/tests_ok/unix_socket.ps1 new file mode 100644 index 00000000000..44d27e99d7a --- /dev/null +++ b/integration/hurl/tests_ok/unix_socket.ps1 @@ -0,0 +1,8 @@ +Set-StrictMode -Version latest + +# The python test server for testing Unix Domain Sockets +# (../unix_socket/server.py) does not currently support AF_UNIX on Windows. +# See https://github.com/python/cpython/issues/77589 +# Skip for now until this can be easily tested. +$ErrorActionPreference = 'Continue' +exit 255 diff --git a/integration/hurl/tests_ok/unix_socket.sh b/integration/hurl/tests_ok/unix_socket.sh new file mode 100755 index 00000000000..d06d3b034b7 --- /dev/null +++ b/integration/hurl/tests_ok/unix_socket.sh @@ -0,0 +1,3 @@ +#!/bin/bash +set -Eeuo pipefail +hurl --unix-socket build/unix_socket.sock --verbose tests_ok/unix_socket.hurl diff --git a/integration/hurl/unix_socket/server.py b/integration/hurl/unix_socket/server.py new file mode 100644 index 00000000000..2f89c7509bf --- /dev/null +++ b/integration/hurl/unix_socket/server.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +from os import path, unlink +from socket import socket, AF_UNIX +from flask import Flask + +app = Flask("Unix Domain Sockets Server") + + +@app.route("/hello") +def hello(): + return "Hello World!" + + +def main(): + sock = socket(AF_UNIX) + socket_path = "build/unix_socket.sock" + + try: + unlink(socket_path) + except OSError: + if path.exists(socket_path): + raise + + sock.bind(socket_path) + + try: + app.run(host="unix://" + socket_path) + finally: + unlink(socket_path) + + +if __name__ == "__main__": + main() diff --git a/integration/hurlfmt/tests_export/unix_socket_option.html b/integration/hurlfmt/tests_export/unix_socket_option.html new file mode 100644 index 00000000000..e57dabaec6d --- /dev/null +++ b/integration/hurlfmt/tests_export/unix_socket_option.html @@ -0,0 +1,9 @@ +
# Go through a Unix Domain Socket
+# Requests will be sent to the Unix Domain Socket instead of over the network
+GET http://example/hello
+[Options]
+unix-socket: build/unix_socket.sock
+HTTP 200
+[Asserts]
+`Hello World!`
+
diff --git a/integration/hurlfmt/tests_export/unix_socket_option.hurl b/integration/hurlfmt/tests_export/unix_socket_option.hurl new file mode 100644 index 00000000000..d1e085cab87 --- /dev/null +++ b/integration/hurlfmt/tests_export/unix_socket_option.hurl @@ -0,0 +1,8 @@ +# Go through a Unix Domain Socket +# Requests will be sent to the Unix Domain Socket instead of over the network +GET http://example/hello +[Options] +unix-socket: build/unix_socket.sock +HTTP 200 +[Asserts] +`Hello World!` diff --git a/integration/hurlfmt/tests_export/unix_socket_option.json b/integration/hurlfmt/tests_export/unix_socket_option.json new file mode 100644 index 00000000000..de977afbb1e --- /dev/null +++ b/integration/hurlfmt/tests_export/unix_socket_option.json @@ -0,0 +1 @@ +{"entries":[{"request":{"method":"GET","url":"http://example/hello","options":[{"name":"unix-socket","value":"build/unix_socket.sock"}]},"response":{"status":200,"body":{"type":"text","value":"Hello World!"}}}]} diff --git a/packages/hurl/README.md b/packages/hurl/README.md index b08d153e28e..9fb7129f1cb 100644 --- a/packages/hurl/README.md +++ b/packages/hurl/README.md @@ -964,6 +964,7 @@ will follow a redirection only for the second entry. | --ssl-no-revoke | (Windows) This option tells Hurl to disable certificate revocation checks. WARNING: this option loosens the SSL security, and by using this flag you ask for exactly that.
| | --test | Activate test mode: with this, the HTTP response is not outputted anymore, progress is reported for each Hurl file tested, and a text summary is displayed when all files have been run.
| | --to-entry <ENTRY_NUMBER> | Execute Hurl file to ENTRY_NUMBER (starting at 1).
Ignore the remaining of the file. It is useful for debugging a session.
| +| --unix-socket <PATH> | (HTTP) Connect through this Unix domain socket, instead of using the network.
| | -u, --user <USER:PASSWORD> | Add basic Authentication header to each request.
| | -A, --user-agent <NAME> | Specify the User-Agent string to send to the HTTP server.
| | --variable <NAME=VALUE> | Define variable (name/value) to be used in Hurl templates.
| diff --git a/packages/hurl/src/cli/options/commands.rs b/packages/hurl/src/cli/options/commands.rs index 92736d7bbc7..b289f54eaf6 100644 --- a/packages/hurl/src/cli/options/commands.rs +++ b/packages/hurl/src/cli/options/commands.rs @@ -405,6 +405,14 @@ pub fn to_entry() -> clap::Arg { .num_args(1) } +pub fn unix_socket() -> clap::Arg { + clap::Arg::new("unix_socket") + .long("unix-socket") + .value_name("PATH") + .help("(HTTP) Connect through this Unix domain socket, instead of using the network.") + .num_args(1) +} + pub fn user() -> clap::Arg { clap::Arg::new("user") .long("user") diff --git a/packages/hurl/src/cli/options/matches.rs b/packages/hurl/src/cli/options/matches.rs index 4a0a7feaf01..cf69928340f 100644 --- a/packages/hurl/src/cli/options/matches.rs +++ b/packages/hurl/src/cli/options/matches.rs @@ -320,6 +320,10 @@ pub fn to_entry(arg_matches: &ArgMatches) -> Option { get::(arg_matches, "to_entry").map(|x| x as usize) } +pub fn unix_socket(arg_matches: &ArgMatches) -> Option { + get::(arg_matches, "unix_socket") +} + pub fn user(arg_matches: &ArgMatches) -> Option { get::(arg_matches, "user") } diff --git a/packages/hurl/src/cli/options/mod.rs b/packages/hurl/src/cli/options/mod.rs index ab7a31df6e0..c520a1ec57c 100644 --- a/packages/hurl/src/cli/options/mod.rs +++ b/packages/hurl/src/cli/options/mod.rs @@ -75,6 +75,7 @@ pub struct Options { pub test: bool, pub timeout: Duration, pub to_entry: Option, + pub unix_socket: Option, pub user: Option, pub user_agent: Option, pub variables: HashMap, @@ -210,6 +211,7 @@ pub fn parse() -> Result { .arg(commands::ssl_no_revoke()) .arg(commands::test()) .arg(commands::to_entry()) + .arg(commands::unix_socket()) .arg(commands::user_agent()) .arg(commands::user()) .arg(commands::variable()) @@ -275,6 +277,7 @@ fn parse_matches(arg_matches: &ArgMatches) -> Result { let test = matches::test(arg_matches); let timeout = matches::timeout(arg_matches); let to_entry = matches::to_entry(arg_matches); + let unix_socket = matches::unix_socket(arg_matches); let user = matches::user(arg_matches); let user_agent = matches::user_agent(arg_matches); let variables = matches::variables(arg_matches)?; @@ -320,6 +323,7 @@ fn parse_matches(arg_matches: &ArgMatches) -> Result { test, timeout, to_entry, + unix_socket, user, user_agent, variables, @@ -421,6 +425,7 @@ impl Options { let ssl_no_revoke = self.ssl_no_revoke; let timeout = self.timeout; let to_entry = self.to_entry; + let unix_socket = self.unix_socket.clone(); let user = self.user.clone(); let user_agent = self.user_agent.clone(); @@ -454,6 +459,7 @@ impl Options { .ssl_no_revoke(ssl_no_revoke) .timeout(timeout) .to_entry(to_entry) + .unix_socket(unix_socket) .user(user) .user_agent(user_agent) .build() diff --git a/packages/hurl/src/http/client.rs b/packages/hurl/src/http/client.rs index e06ed3227e7..c1e6ced4197 100644 --- a/packages/hurl/src/http/client.rs +++ b/packages/hurl/src/http/client.rs @@ -435,6 +435,9 @@ impl Client { if let Some(s) = options.no_proxy.clone() { self.handle.noproxy(s.as_str())?; } + if let Some(unix_socket) = &options.unix_socket { + self.handle.unix_socket(unix_socket)?; + } self.handle.timeout(options.timeout)?; self.handle.connect_timeout(options.connect_timeout)?; @@ -1116,6 +1119,7 @@ mod tests { path_as_is: true, proxy: Some("localhost:3128".to_string()), no_proxy: None, + unix_socket: Some("/var/run/example.sock".to_string()), user: Some("user:password".to_string()), user_agent: Some("my-useragent".to_string()), verbosity: Some(Verbosity::VeryVerbose), @@ -1134,6 +1138,7 @@ mod tests { --max-redirs 10 \ --path-as-is \ --proxy 'localhost:3128' \ + --unix-socket '/var/run/example.sock' \ --user 'user:password' \ --user-agent 'my-useragent' \ --output /tmp/foo.bin \ diff --git a/packages/hurl/src/http/options.rs b/packages/hurl/src/http/options.rs index 37c3da254d3..1242862c1b7 100644 --- a/packages/hurl/src/http/options.rs +++ b/packages/hurl/src/http/options.rs @@ -44,6 +44,7 @@ pub struct ClientOptions { pub retry: Retry, pub ssl_no_revoke: bool, pub timeout: Duration, + pub unix_socket: Option, pub user: Option, pub user_agent: Option, pub verbosity: Option, @@ -79,6 +80,7 @@ impl Default for ClientOptions { retry: Retry::None, ssl_no_revoke: false, timeout: Duration::from_secs(300), + unix_socket: None, user: None, user_agent: None, verbosity: None, @@ -163,6 +165,10 @@ impl ClientOptions { arguments.push("--timeout".to_string()); arguments.push(self.timeout.as_secs().to_string()); } + if let Some(ref unix_socket) = self.unix_socket { + arguments.push("--unix-socket".to_string()); + arguments.push(format!("'{unix_socket}'")) + } if let Some(ref user) = self.user { arguments.push("--user".to_string()); arguments.push(format!("'{user}'")); @@ -208,6 +214,7 @@ mod tests { retry: Retry::None, ssl_no_revoke: false, timeout: Duration::from_secs(10), + unix_socket: Some("/var/run/example.sock".to_string()), user: Some("user:password".to_string()), user_agent: Some("my-useragent".to_string()), verbosity: None, @@ -236,6 +243,8 @@ mod tests { "bar.com:443:127.0.0.1", "--timeout", "10", + "--unix-socket", + "'/var/run/example.sock'", "--user", "'user:password'", "--user-agent", diff --git a/packages/hurl/src/runner/entry.rs b/packages/hurl/src/runner/entry.rs index 80e8e58667d..b52e81aa90e 100644 --- a/packages/hurl/src/runner/entry.rs +++ b/packages/hurl/src/runner/entry.rs @@ -237,6 +237,7 @@ impl ClientOptions { retry: runner_options.retry, ssl_no_revoke: runner_options.ssl_no_revoke, timeout: runner_options.timeout, + unix_socket: runner_options.unix_socket.clone(), user: runner_options.user.clone(), user_agent: runner_options.user_agent.clone(), verbosity: match verbosity { diff --git a/packages/hurl/src/runner/hurl_file.rs b/packages/hurl/src/runner/hurl_file.rs index ebc49ed4ff2..2c0c9fd7d85 100644 --- a/packages/hurl/src/runner/hurl_file.rs +++ b/packages/hurl/src/runner/hurl_file.rs @@ -404,6 +404,12 @@ fn get_non_default_options(options: &RunnerOptions) -> Vec<(&'static str, String non_default_options.push(("retry", options.retry.to_string())); } + if options.unix_socket != default_options.unix_socket { + if let Some(unix_socket) = &options.unix_socket { + non_default_options.push(("unix socket", unix_socket.to_string())) + } + } + non_default_options } diff --git a/packages/hurl/src/runner/options.rs b/packages/hurl/src/runner/options.rs index 8f8180030f2..7939c3ac2b6 100644 --- a/packages/hurl/src/runner/options.rs +++ b/packages/hurl/src/runner/options.rs @@ -183,6 +183,10 @@ pub fn get_entry_options( let value = eval_boolean_option(value, variables)?; runner_options.skip = value } + OptionKind::UnixSocket(value) => { + let value = eval_template(value, variables)?; + runner_options.unix_socket = Some(value.to_string()) + } OptionKind::Variable(VariableDefinition { name, value, .. }) => { let value = eval_variable_value(value, variables)?; variables.insert(name.clone(), value); diff --git a/packages/hurl/src/runner/runner_options.rs b/packages/hurl/src/runner/runner_options.rs index 61c90844056..7b2e6c338ed 100644 --- a/packages/hurl/src/runner/runner_options.rs +++ b/packages/hurl/src/runner/runner_options.rs @@ -53,6 +53,7 @@ pub struct RunnerOptionsBuilder { ssl_no_revoke: bool, timeout: Duration, to_entry: Option, + unix_socket: Option, user: Option, user_agent: Option, } @@ -90,6 +91,7 @@ impl Default for RunnerOptionsBuilder { ssl_no_revoke: false, timeout: Duration::from_secs(300), to_entry: None, + unix_socket: None, user: None, user_agent: None, } @@ -312,6 +314,12 @@ impl RunnerOptionsBuilder { self } + /// Sets the specified unix domain socket to connect through, instead of using the network. + pub fn unix_socket(&mut self, unix_socket: Option) -> &mut Self { + self.unix_socket = unix_socket; + self + } + /// Adds basic Authentication header to each request. pub fn user(&mut self, user: Option) -> &mut Self { self.user = user; @@ -357,6 +365,7 @@ impl RunnerOptionsBuilder { ssl_no_revoke: self.ssl_no_revoke, timeout: self.timeout, to_entry: self.to_entry, + unix_socket: self.unix_socket.clone(), user: self.user.clone(), user_agent: self.user_agent.clone(), } @@ -395,6 +404,7 @@ pub struct RunnerOptions { pub(crate) ssl_no_revoke: bool, pub(crate) timeout: Duration, pub(crate) to_entry: Option, + pub(crate) unix_socket: Option, pub(crate) user: Option, pub(crate) user_agent: Option, } diff --git a/packages/hurl/tests/sample.rs b/packages/hurl/tests/sample.rs index 93c3d2f4df1..3834df4d427 100644 --- a/packages/hurl/tests/sample.rs +++ b/packages/hurl/tests/sample.rs @@ -114,6 +114,7 @@ fn simple_sample() { .retry_interval(Duration::from_secs(1)) .timeout(Duration::from_secs(300)) .to_entry(None) + .unix_socket(None) .user(None) .user_agent(None) .build(); diff --git a/packages/hurl_core/src/ast/core.rs b/packages/hurl_core/src/ast/core.rs index 5f5758a0bd2..b221df11e56 100644 --- a/packages/hurl_core/src/ast/core.rs +++ b/packages/hurl_core/src/ast/core.rs @@ -740,6 +740,7 @@ pub enum OptionKind { Retry(RetryOption), RetryInterval(NaturalOption), Skip(BooleanOption), + UnixSocket(Template), Variable(VariableDefinition), Verbose(BooleanOption), VeryVerbose(BooleanOption), @@ -771,6 +772,7 @@ impl OptionKind { OptionKind::Retry(_) => "retry", OptionKind::RetryInterval(_) => "retry-interval", OptionKind::Skip(_) => "skip", + OptionKind::UnixSocket(_) => "unix-socket", OptionKind::Variable(_) => "variable", OptionKind::Verbose(_) => "verbose", OptionKind::VeryVerbose(_) => "very-verbose", @@ -802,6 +804,7 @@ impl OptionKind { OptionKind::Retry(value) => value.to_string(), OptionKind::RetryInterval(value) => value.to_string(), OptionKind::Skip(value) => value.to_string(), + OptionKind::UnixSocket(value) => value.to_string(), OptionKind::Variable(VariableDefinition { name, value, .. }) => { format!("{name}={value}") } diff --git a/packages/hurl_core/src/format/html.rs b/packages/hurl_core/src/format/html.rs index 487b8374f22..b048b84dc62 100644 --- a/packages/hurl_core/src/format/html.rs +++ b/packages/hurl_core/src/format/html.rs @@ -237,6 +237,7 @@ impl HtmlFormatter { OptionKind::Retry(value) => self.fmt_retry_option(value), OptionKind::RetryInterval(value) => self.fmt_natural_option(value), OptionKind::Skip(value) => self.fmt_bool_option(value), + OptionKind::UnixSocket(value) => self.fmt_template(value), OptionKind::Variable(value) => self.fmt_variable_definition(value), OptionKind::Verbose(value) => self.fmt_bool_option(value), OptionKind::VeryVerbose(value) => self.fmt_bool_option(value), diff --git a/packages/hurl_core/src/parser/error.rs b/packages/hurl_core/src/parser/error.rs index 558d5c2ae78..a88e185aef5 100644 --- a/packages/hurl_core/src/parser/error.rs +++ b/packages/hurl_core/src/parser/error.rs @@ -168,6 +168,7 @@ impl crate::error::Error for Error { "retry", "retry-interval", "skip", + "unix-socket", "variable", "verbose", "very-verbose", diff --git a/packages/hurl_core/src/parser/option.rs b/packages/hurl_core/src/parser/option.rs index 7d748c8a32a..0610eabe8d7 100644 --- a/packages/hurl_core/src/parser/option.rs +++ b/packages/hurl_core/src/parser/option.rs @@ -62,6 +62,7 @@ pub fn parse(reader: &mut Reader) -> ParseResult { "retry" => option_retry(reader)?, "retry-interval" => option_retry_interval(reader)?, "skip" => option_skip(reader)?, + "unix-socket" => option_unix_socket(reader)?, "variable" => option_variable(reader)?, "verbose" => option_verbose(reader)?, "very-verbose" => option_very_verbose(reader)?, @@ -200,6 +201,11 @@ fn option_skip(reader: &mut Reader) -> ParseResult { Ok(OptionKind::Skip(value)) } +fn option_unix_socket(reader: &mut Reader) -> ParseResult { + let value = unquoted_template(reader)?; + Ok(OptionKind::UnixSocket(value)) +} + fn option_variable(reader: &mut Reader) -> ParseResult { let value = variable_definition(reader)?; Ok(OptionKind::Variable(value)) diff --git a/packages/hurlfmt/src/format/json.rs b/packages/hurlfmt/src/format/json.rs index 82ebe74766b..8597b38bc14 100644 --- a/packages/hurlfmt/src/format/json.rs +++ b/packages/hurlfmt/src/format/json.rs @@ -303,6 +303,7 @@ impl ToJson for EntryOption { OptionKind::Retry(value) => value.to_json(), OptionKind::RetryInterval(value) => value.to_json(), OptionKind::Skip(value) => value.to_json(), + OptionKind::UnixSocket(value) => JValue::String(value.to_string()), OptionKind::Variable(value) => { JValue::String(format!("{}={}", value.name, value.value)) } diff --git a/packages/hurlfmt/src/format/token.rs b/packages/hurlfmt/src/format/token.rs index 158614a498f..02def40656e 100644 --- a/packages/hurlfmt/src/format/token.rs +++ b/packages/hurlfmt/src/format/token.rs @@ -899,6 +899,7 @@ impl Tokenizable for OptionKind { OptionKind::Retry(value) => value.tokenize(), OptionKind::RetryInterval(value) => value.tokenize(), OptionKind::Skip(value) => value.tokenize(), + OptionKind::UnixSocket(value) => value.tokenize(), OptionKind::Variable(value) => value.tokenize(), OptionKind::Verbose(value) => value.tokenize(), OptionKind::VeryVerbose(value) => value.tokenize(),