From e6cad39a9aa932f2930bf1e266e95a4ef885f88c Mon Sep 17 00:00:00 2001 From: Jean-Christophe Amiel Date: Fri, 25 Oct 2024 22:34:33 +0200 Subject: [PATCH] Implement --limit-rate from curl --- README.md | 3 +- completions/_hurl | 3 +- completions/_hurl.ps1 | 3 +- completions/hurl.bash | 2 +- completions/hurl.fish | 3 +- docs/manual.md | 3 +- docs/manual/hurl.1 | 11 +++++-- docs/manual/hurl.md | 9 +++++- docs/manual/hurlfmt.1 | 2 +- docs/spec/options/hurl/limit_rate.option | 10 +++++++ docs/spec/options/hurl/max_filesize.option | 4 +-- integration/hurl/tests_ok/get_large.py | 1 + integration/hurl/tests_ok/help.out.pattern | 4 ++- integration/hurl/tests_ok/limit_rate.curl | 1 + integration/hurl/tests_ok/limit_rate.hurl | 9 ++++++ integration/hurl/tests_ok/limit_rate.ps1 | 4 +++ integration/hurl/tests_ok/limit_rate.py | 14 +++++++++ integration/hurl/tests_ok/limit_rate.sh | 4 +++ packages/hurl/README.md | 3 +- packages/hurl/src/cli/options/commands.rs | 12 +++++++- packages/hurl/src/cli/options/matches.rs | 14 +++++---- packages/hurl/src/cli/options/mod.rs | 12 +++++++- packages/hurl/src/http/client.rs | 6 ++++ packages/hurl/src/http/options.rs | 16 +++++++++- packages/hurl/src/runner/entry.rs | 2 ++ packages/hurl/src/runner/runner_options.rs | 34 +++++++++++++++++----- packages/hurl_core/src/typing.rs | 10 +++++++ 27 files changed, 170 insertions(+), 29 deletions(-) create mode 100644 docs/spec/options/hurl/limit_rate.option create mode 100644 integration/hurl/tests_ok/limit_rate.curl create mode 100644 integration/hurl/tests_ok/limit_rate.hurl create mode 100644 integration/hurl/tests_ok/limit_rate.ps1 create mode 100644 integration/hurl/tests_ok/limit_rate.py create mode 100755 integration/hurl/tests_ok/limit_rate.sh diff --git a/README.md b/README.md index b72cf0a16c5..9c6b76bb5ab 100644 --- a/README.md +++ b/README.md @@ -1247,9 +1247,10 @@ will follow a redirection only for the second entry. | --jobs <NUM> | Maximum number of parallel jobs in parallel mode. Default value corresponds (in most cases) to the
current amount of CPUs.

See also [`--parallel`](#parallel).

This is a cli-only option.
| | --json | Output each Hurl file result to JSON. The format is very closed to HAR format.

This is a cli-only option.
| | --key <KEY> | Private key file name.
| +| --limit-rate <SPEED> | Specify the maximum transfer rate you want Hurl to use, for both downloads and uploads. This feature is useful if you have a limited pipe and you would like your transfer not to use your entire bandwidth. To make it slower than it otherwise would be.
The given speed is measured in bytes/second.

This is a cli-only option.
| | -L, --location | Follow redirect. To limit the amount of redirects to follow use the [`--max-redirs`](#max-redirs) option
| | --location-trusted | Like [`-L, --location`](#location), but allows sending the name + password to all hosts that the site may redirect to.
This may or may not introduce a security breach if the site redirects you to a site to which you send your authentication info (which is plaintext in the case of HTTP Basic authentication).
| -| --max-filesize <BYTES> | Specify the maximum size (in bytes) of a file to download. If the file requested is larger than this value, the transfer does not start.

This is a cli-only option.
| +| --max-filesize <BYTES> | Specify the maximum size in bytes of a file to download. If the file requested is larger than this value, the transfer does not start.

This is a cli-only option.
| | --max-redirs <NUM> | Set maximum number of redirection-followings allowed

By default, the limit is set to 50 redirections. Set this option to -1 to make it unlimited.
| | -m, --max-time <SECONDS> | Maximum time in seconds that you allow a request/response to take. This is the standard timeout.

You can specify time units in the maximum time expression. Set Hurl to use a maximum time of 20 seconds with `--max-time 20s` or set it to 35,000 milliseconds with `--max-time 35000ms`. No spaces allowed.

See also [`--connect-timeout`](#connect-timeout).

This is a cli-only option.
| | -n, --netrc | Scan the .netrc file in the user's home directory for the username and password.

See also [`--netrc-file`](#netrc-file) and [`--netrc-optional`](#netrc-optional).
| diff --git a/completions/_hurl b/completions/_hurl index 616ca1d932e..93fd19a33fa 100644 --- a/completions/_hurl +++ b/completions/_hurl @@ -46,7 +46,8 @@ _hurl() { '(-6 --ipv6)'{-6,--ipv6}'[Tell Hurl to use IPv6 addresses only when resolving host names, and not for example try IPv4]' \ '--jobs[Maximum number of parallel jobs]: :' \ '--json[Output each Hurl file result to JSON]' \ - '--max-filesize[Specify the maximum size (in bytes) of a file to download]: :' \ + '--limit-rate[Specify the maximum transfer rate in bytes/second, for both downloads and uploads]: :' \ + '--max-filesize[Specify the maximum size in bytes of a file to download]: :' \ '--max-redirs[Maximum number of redirects allowed, -1 for unlimited redirects]: :' \ '(-m --max-time)'{-m,--max-time}'[Maximum time allowed for the transfer]: :' \ '(-n --netrc)'{-n,--netrc}'[Must read .netrc for username and password]' \ diff --git a/completions/_hurl.ps1 b/completions/_hurl.ps1 index 3c5fef66643..1fc5a5598c1 100644 --- a/completions/_hurl.ps1 +++ b/completions/_hurl.ps1 @@ -51,7 +51,8 @@ Register-ArgumentCompleter -Native -CommandName 'hurl' -ScriptBlock { [CompletionResult]::new('--ipv6', 'ipv6', [CompletionResultType]::ParameterName, 'Tell Hurl to use IPv6 addresses only when resolving host names, and not for example try IPv4') [CompletionResult]::new('--jobs', 'jobs', [CompletionResultType]::ParameterName, 'Maximum number of parallel jobs') [CompletionResult]::new('--json', 'json', [CompletionResultType]::ParameterName, 'Output each Hurl file result to JSON') - [CompletionResult]::new('--max-filesize', 'max-filesize', [CompletionResultType]::ParameterName, 'Specify the maximum size (in bytes) of a file to download') + [CompletionResult]::new('--limit-rate', 'limit-rate', [CompletionResultType]::ParameterName, 'Specify the maximum transfer rate in bytes/second, for both downloads and uploads') + [CompletionResult]::new('--max-filesize', 'max-filesize', [CompletionResultType]::ParameterName, 'Specify the maximum size in bytes of a file to download') [CompletionResult]::new('--max-redirs', 'max-redirs', [CompletionResultType]::ParameterName, 'Maximum number of redirects allowed, -1 for unlimited redirects') [CompletionResult]::new('--max-time', 'max-time', [CompletionResultType]::ParameterName, 'Maximum time allowed for the transfer') [CompletionResult]::new('--netrc', 'netrc', [CompletionResultType]::ParameterName, 'Must read .netrc for username and password') diff --git a/completions/hurl.bash b/completions/hurl.bash index abfc099f843..1624e2de481 100644 --- a/completions/hurl.bash +++ b/completions/hurl.bash @@ -5,7 +5,7 @@ _hurl() _init_completion || return if [[ $cur == -* ]]; then - COMPREPLY=($(compgen -W '--aws-sigv4 --cacert --cert --key --color --compressed --connect-timeout --connect-to --continue-on-error --cookie --cookie-jar --delay --error-format --fail-at-end --file-root --location --location-trusted --from-entry --glob --http1.0 --http1.1 --http2 --http3 --ignore-asserts --include --insecure --interactive --ipv4 --ipv6 --jobs --json --max-filesize --max-redirs --max-time --netrc --netrc-file --netrc-optional --no-color --no-output --noproxy --output --parallel --path-as-is --proxy --repeat --report-html --report-json --report-junit --report-tap --resolve --retry --retry-interval --ssl-no-revoke --test --to-entry --unix-socket --user --user-agent --variable --variables-file --verbose --very-verbose --help --version' -- "$cur")) + COMPREPLY=($(compgen -W '--aws-sigv4 --cacert --cert --key --color --compressed --connect-timeout --connect-to --continue-on-error --cookie --cookie-jar --delay --error-format --fail-at-end --file-root --location --location-trusted --from-entry --glob --http1.0 --http1.1 --http2 --http3 --ignore-asserts --include --insecure --interactive --ipv4 --ipv6 --jobs --json --limit-rate --max-filesize --max-redirs --max-time --netrc --netrc-file --netrc-optional --no-color --no-output --noproxy --output --parallel --path-as-is --proxy --repeat --report-html --report-json --report-junit --report-tap --resolve --retry --retry-interval --ssl-no-revoke --test --to-entry --unix-socket --user --user-agent --variable --variables-file --verbose --very-verbose --help --version' -- "$cur")) return fi diff --git a/completions/hurl.fish b/completions/hurl.fish index 5371d529acd..b01087832c5 100644 --- a/completions/hurl.fish +++ b/completions/hurl.fish @@ -29,7 +29,8 @@ complete -c hurl -l ipv4 -d 'Tell Hurl to use IPv4 addresses only when resolving complete -c hurl -l ipv6 -d 'Tell Hurl to use IPv6 addresses only when resolving host names, and not for example try IPv4' complete -c hurl -l jobs -d 'Maximum number of parallel jobs' complete -c hurl -l json -d 'Output each Hurl file result to JSON' -complete -c hurl -l max-filesize -d 'Specify the maximum size (in bytes) of a file to download' +complete -c hurl -l limit-rate -d 'Specify the maximum transfer rate in bytes/second, for both downloads and uploads' +complete -c hurl -l max-filesize -d 'Specify the maximum size in bytes of a file to download' complete -c hurl -l max-redirs -d 'Maximum number of redirects allowed, -1 for unlimited redirects' complete -c hurl -l max-time -d 'Maximum time allowed for the transfer' complete -c hurl -l netrc -d 'Must read .netrc for username and password' diff --git a/docs/manual.md b/docs/manual.md index 9d49b8a500a..ae5b96eeb2b 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -177,9 +177,10 @@ will follow a redirection only for the second entry. | --jobs <NUM> | Maximum number of parallel jobs in parallel mode. Default value corresponds (in most cases) to the
current amount of CPUs.

See also [`--parallel`](#parallel).

This is a cli-only option.
| | --json | Output each Hurl file result to JSON. The format is very closed to HAR format.

This is a cli-only option.
| | --key <KEY> | Private key file name.
| +| --limit-rate <SPEED> | Specify the maximum transfer rate you want Hurl to use, for both downloads and uploads. This feature is useful if you have a limited pipe and you would like your transfer not to use your entire bandwidth. To make it slower than it otherwise would be.
The given speed is measured in bytes/second.

This is a cli-only option.
| | -L, --location | Follow redirect. To limit the amount of redirects to follow use the [`--max-redirs`](#max-redirs) option
| | --location-trusted | Like [`-L, --location`](#location), but allows sending the name + password to all hosts that the site may redirect to.
This may or may not introduce a security breach if the site redirects you to a site to which you send your authentication info (which is plaintext in the case of HTTP Basic authentication).
| -| --max-filesize <BYTES> | Specify the maximum size (in bytes) of a file to download. If the file requested is larger than this value, the transfer does not start.

This is a cli-only option.
| +| --max-filesize <BYTES> | Specify the maximum size in bytes of a file to download. If the file requested is larger than this value, the transfer does not start.

This is a cli-only option.
| | --max-redirs <NUM> | Set maximum number of redirection-followings allowed

By default, the limit is set to 50 redirections. Set this option to -1 to make it unlimited.
| | -m, --max-time <SECONDS> | Maximum time in seconds that you allow a request/response to take. This is the standard timeout.

You can specify time units in the maximum time expression. Set Hurl to use a maximum time of 20 seconds with `--max-time 20s` or set it to 35,000 milliseconds with `--max-time 35000ms`. No spaces allowed.

See also [`--connect-timeout`](#connect-timeout).

This is a cli-only option.
| | -n, --netrc | Scan the .netrc file in the user's home directory for the username and password.

See also [`--netrc-file`](#netrc-file) and [`--netrc-optional`](#netrc-optional).
| diff --git a/docs/manual/hurl.1 b/docs/manual/hurl.1 index 39cdfdac114..ef2a22b3541 100644 --- a/docs/manual/hurl.1 +++ b/docs/manual/hurl.1 @@ -1,4 +1,4 @@ -.TH hurl 1 "24 Oct 2024" "hurl 6.0.0-SNAPSHOT" " Hurl Manual" +.TH hurl 1 "25 Oct 2024" "hurl 6.0.0-SNAPSHOT" " Hurl Manual" .SH NAME hurl - run and test HTTP requests. @@ -300,6 +300,13 @@ This is a cli-only option. Private key file name. +.IP "--limit-rate " + +Specify the maximum transfer rate you want Hurl to use, for both downloads and uploads. This feature is useful if you have a limited pipe and you would like your transfer not to use your entire bandwidth. To make it slower than it otherwise would be. +The given speed is measured in bytes/second. + +This is a cli-only option. + .IP "-L, --location " Follow redirect. To limit the amount of redirects to follow use the \fI--max-redirs\fP option @@ -311,7 +318,7 @@ This may or may not introduce a security breach if the site redirects you to a s .IP "--max-filesize " -Specify the maximum size (in bytes) of a file to download. If the file requested is larger than this value, the transfer does not start. +Specify the maximum size in bytes of a file to download. If the file requested is larger than this value, the transfer does not start. This is a cli-only option. diff --git a/docs/manual/hurl.md b/docs/manual/hurl.md index 6b9b6b6c7bc..f2f1934592f 100644 --- a/docs/manual/hurl.md +++ b/docs/manual/hurl.md @@ -319,6 +319,13 @@ This is a cli-only option. Private key file name. +### --limit-rate {#limit-rate} + +Specify the maximum transfer rate you want Hurl to use, for both downloads and uploads. This feature is useful if you have a limited pipe and you would like your transfer not to use your entire bandwidth. To make it slower than it otherwise would be. +The given speed is measured in bytes/second. + +This is a cli-only option. + ### -L, --location {#location} Follow redirect. To limit the amount of redirects to follow use the [`--max-redirs`](#max-redirs) option @@ -330,7 +337,7 @@ This may or may not introduce a security breach if the site redirects you to a s ### --max-filesize {#max-filesize} -Specify the maximum size (in bytes) of a file to download. If the file requested is larger than this value, the transfer does not start. +Specify the maximum size in bytes of a file to download. If the file requested is larger than this value, the transfer does not start. This is a cli-only option. diff --git a/docs/manual/hurlfmt.1 b/docs/manual/hurlfmt.1 index bd9943c692a..b3cdfeb732a 100644 --- a/docs/manual/hurlfmt.1 +++ b/docs/manual/hurlfmt.1 @@ -1,4 +1,4 @@ -.TH hurl 1 "24 Oct 2024" "hurl 6.0.0-SNAPSHOT" " Hurl Manual" +.TH hurl 1 "25 Oct 2024" "hurl 6.0.0-SNAPSHOT" " Hurl Manual" .SH NAME hurlfmt - format Hurl files diff --git a/docs/spec/options/hurl/limit_rate.option b/docs/spec/options/hurl/limit_rate.option new file mode 100644 index 00000000000..cb34dcc3277 --- /dev/null +++ b/docs/spec/options/hurl/limit_rate.option @@ -0,0 +1,10 @@ +name: limit_rate +long: limit-rate +value: SPEED +value_parser: clap::value_parser!(u64) +help: Specify the maximum transfer rate in bytes/second, for both downloads and uploads +help_heading: HTTP options +cli_only: true +--- +Specify the maximum transfer rate you want Hurl to use, for both downloads and uploads. This feature is useful if you have a limited pipe and you would like your transfer not to use your entire bandwidth. To make it slower than it otherwise would be. +The given speed is measured in bytes/second. diff --git a/docs/spec/options/hurl/max_filesize.option b/docs/spec/options/hurl/max_filesize.option index 0bc542f41b3..09f8b3fa18a 100644 --- a/docs/spec/options/hurl/max_filesize.option +++ b/docs/spec/options/hurl/max_filesize.option @@ -2,8 +2,8 @@ name: max_filesize long: max-filesize value: BYTES value_parser: clap::value_parser!(u64) -help: Specify the maximum size (in bytes) of a file to download +help: Specify the maximum size in bytes of a file to download help_heading: HTTP options cli_only: true --- -Specify the maximum size (in bytes) of a file to download. If the file requested is larger than this value, the transfer does not start. +Specify the maximum size in bytes of a file to download. If the file requested is larger than this value, the transfer does not start. diff --git a/integration/hurl/tests_ok/get_large.py b/integration/hurl/tests_ok/get_large.py index 739b829afa1..80be986e57b 100644 --- a/integration/hurl/tests_ok/get_large.py +++ b/integration/hurl/tests_ok/get_large.py @@ -6,6 +6,7 @@ @app.route("/get_large") def get_large(): result = BytesIO() + # Returns ~536M for _ in range(1024 * 1024 * 32): result.write(b"0123456789abcdef") data = result.getvalue() diff --git a/integration/hurl/tests_ok/help.out.pattern b/integration/hurl/tests_ok/help.out.pattern index 7ed2bd5eb8a..bf8b43545cc 100644 --- a/integration/hurl/tests_ok/help.out.pattern +++ b/integration/hurl/tests_ok/help.out.pattern @@ -45,8 +45,10 @@ HTTP options: -6, --ipv6 Tell Hurl to use IPv6 addresses only when resolving host names, and not for example try IPv4 + --limit-rate + Specify the maximum transfer rate in bytes/second, for both downloads and uploads --max-filesize - Specify the maximum size (in bytes) of a file to download + Specify the maximum size in bytes of a file to download --max-redirs Maximum number of redirects allowed, -1 for unlimited redirects [default: 50] -m, --max-time diff --git a/integration/hurl/tests_ok/limit_rate.curl b/integration/hurl/tests_ok/limit_rate.curl new file mode 100644 index 00000000000..2e2019c729a --- /dev/null +++ b/integration/hurl/tests_ok/limit_rate.curl @@ -0,0 +1 @@ +curl --limit-rate 100000 'http://localhost:8000/3_000_000_bytes' diff --git a/integration/hurl/tests_ok/limit_rate.hurl b/integration/hurl/tests_ok/limit_rate.hurl new file mode 100644 index 00000000000..cffb5e54b0d --- /dev/null +++ b/integration/hurl/tests_ok/limit_rate.hurl @@ -0,0 +1,9 @@ +GET http://localhost:8000/3_000_000_bytes +# A ~2.8M live file for test +# GET https://upload.wikimedia.org/wikipedia/commons/4/4d/Cat_November_2010-1a.jpg +HTTP 200 +[Asserts] +# With a limit rate of 100 000 bytes/s we should expect +# a duration 3 000 000 / 100 000 ~30s, so between 25s-35s, with a good margin for error. +duration >= 25000 +duration <= 35000 diff --git a/integration/hurl/tests_ok/limit_rate.ps1 b/integration/hurl/tests_ok/limit_rate.ps1 new file mode 100644 index 00000000000..9702f654747 --- /dev/null +++ b/integration/hurl/tests_ok/limit_rate.ps1 @@ -0,0 +1,4 @@ +Set-StrictMode -Version latest +$ErrorActionPreference = 'Stop' + +hurl --no-output --limit-rate 100000 tests_ok/limit_rate.hurl diff --git a/integration/hurl/tests_ok/limit_rate.py b/integration/hurl/tests_ok/limit_rate.py new file mode 100644 index 00000000000..188abd381fc --- /dev/null +++ b/integration/hurl/tests_ok/limit_rate.py @@ -0,0 +1,14 @@ +from app import app +from flask import make_response +from io import BytesIO + + +@app.route("/3_000_000_bytes") +def get_300_000_bytes(): + result = BytesIO() + for _ in range(3_000_000): + result.write(b"a") + data = result.getvalue() + resp = make_response(data) + resp.content_type = "application/octet-stream" + return resp diff --git a/integration/hurl/tests_ok/limit_rate.sh b/integration/hurl/tests_ok/limit_rate.sh new file mode 100755 index 00000000000..a7c00fb0553 --- /dev/null +++ b/integration/hurl/tests_ok/limit_rate.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -Eeuo pipefail + +hurl --no-output --limit-rate 100000 tests_ok/limit_rate.hurl diff --git a/packages/hurl/README.md b/packages/hurl/README.md index 258511b81a1..d4c5b171e4a 100644 --- a/packages/hurl/README.md +++ b/packages/hurl/README.md @@ -1247,9 +1247,10 @@ will follow a redirection only for the second entry. | --jobs <NUM> | Maximum number of parallel jobs in parallel mode. Default value corresponds (in most cases) to the
current amount of CPUs.

See also [`--parallel`](#parallel).

This is a cli-only option.
| | --json | Output each Hurl file result to JSON. The format is very closed to HAR format.

This is a cli-only option.
| | --key <KEY> | Private key file name.
| +| --limit-rate <SPEED> | Specify the maximum transfer rate you want Hurl to use, for both downloads and uploads. This feature is useful if you have a limited pipe and you would like your transfer not to use your entire bandwidth. To make it slower than it otherwise would be.
The given speed is measured in bytes/second.

This is a cli-only option.
| | -L, --location | Follow redirect. To limit the amount of redirects to follow use the [`--max-redirs`](#max-redirs) option
| | --location-trusted | Like [`-L, --location`](#location), but allows sending the name + password to all hosts that the site may redirect to.
This may or may not introduce a security breach if the site redirects you to a site to which you send your authentication info (which is plaintext in the case of HTTP Basic authentication).
| -| --max-filesize <BYTES> | Specify the maximum size (in bytes) of a file to download. If the file requested is larger than this value, the transfer does not start.

This is a cli-only option.
| +| --max-filesize <BYTES> | Specify the maximum size in bytes of a file to download. If the file requested is larger than this value, the transfer does not start.

This is a cli-only option.
| | --max-redirs <NUM> | Set maximum number of redirection-followings allowed

By default, the limit is set to 50 redirections. Set this option to -1 to make it unlimited.
| | -m, --max-time <SECONDS> | Maximum time in seconds that you allow a request/response to take. This is the standard timeout.

You can specify time units in the maximum time expression. Set Hurl to use a maximum time of 20 seconds with `--max-time 20s` or set it to 35,000 milliseconds with `--max-time 35000ms`. No spaces allowed.

See also [`--connect-timeout`](#connect-timeout).

This is a cli-only option.
| | -n, --netrc | Scan the .netrc file in the user's home directory for the username and password.

See also [`--netrc-file`](#netrc-file) and [`--netrc-optional`](#netrc-optional).
| diff --git a/packages/hurl/src/cli/options/commands.rs b/packages/hurl/src/cli/options/commands.rs index 44e0d09421a..c5c563333c4 100644 --- a/packages/hurl/src/cli/options/commands.rs +++ b/packages/hurl/src/cli/options/commands.rs @@ -310,12 +310,22 @@ pub fn json() -> clap::Arg { .action(clap::ArgAction::SetTrue) } +pub fn limit_rate() -> clap::Arg { + clap::Arg::new("limit_rate") + .long("limit-rate") + .value_name("SPEED") + .value_parser(clap::value_parser!(u64)) + .help("Specify the maximum transfer rate in bytes/second, for both downloads and uploads") + .help_heading("HTTP options") + .num_args(1) +} + pub fn max_filesize() -> clap::Arg { clap::Arg::new("max_filesize") .long("max-filesize") .value_name("BYTES") .value_parser(clap::value_parser!(u64)) - .help("Specify the maximum size (in bytes) of a file to download") + .help("Specify the maximum size in bytes of a file to download") .help_heading("HTTP options") .num_args(1) } diff --git a/packages/hurl/src/cli/options/matches.rs b/packages/hurl/src/cli/options/matches.rs index 698976de336..6af2d48b33e 100644 --- a/packages/hurl/src/cli/options/matches.rs +++ b/packages/hurl/src/cli/options/matches.rs @@ -25,7 +25,7 @@ use std::{env, fs, io}; use clap::ArgMatches; use hurl::runner::Value; use hurl_core::input::Input; -use hurl_core::typing::{Count, DurationUnit}; +use hurl_core::typing::{BytesPerSec, Count, DurationUnit}; use crate::cli::options::variables::{parse as parse_variable, parse_value}; use crate::cli::options::{duration, CliOptionsError}; @@ -276,6 +276,14 @@ pub fn junit_file(arg_matches: &ArgMatches) -> Option { get::(arg_matches, "report_junit").map(PathBuf::from) } +pub fn limit_rate(arg_matches: &ArgMatches) -> Option { + get::(arg_matches, "limit_rate").map(BytesPerSec) +} + +pub fn max_filesize(arg_matches: &ArgMatches) -> Option { + get::(arg_matches, "max_filesize") +} + pub fn max_redirect(arg_matches: &ArgMatches) -> Count { match get::(arg_matches, "max_redirects").unwrap() { -1 => Count::Infinite, @@ -488,10 +496,6 @@ pub fn very_verbose(arg_matches: &ArgMatches) -> bool { has_flag(arg_matches, "very_verbose") } -pub fn max_filesize(arg_matches: &ArgMatches) -> Option { - get::(arg_matches, "max_filesize") -} - /// Returns a list of path names from the command line options `matches`. fn glob_files(matches: &ArgMatches) -> Result, CliOptionsError> { let mut all_files = vec![]; diff --git a/packages/hurl/src/cli/options/mod.rs b/packages/hurl/src/cli/options/mod.rs index 3bf47657804..ebed9dce3b8 100644 --- a/packages/hurl/src/cli/options/mod.rs +++ b/packages/hurl/src/cli/options/mod.rs @@ -35,7 +35,7 @@ use hurl::util::logger::{LoggerOptions, LoggerOptionsBuilder, Verbosity}; use hurl::util::path::ContextDir; use hurl_core::ast::Entry; use hurl_core::input::Input; -use hurl_core::typing::Count; +use hurl_core::typing::{BytesPerSec, Count}; use crate::cli; use crate::runner::{RunnerOptions, RunnerOptionsBuilder, Value}; @@ -71,6 +71,7 @@ pub struct CliOptions { pub jobs: Option, pub json_report_dir: Option, pub junit_file: Option, + pub limit_rate: Option, pub max_filesize: Option, pub max_redirect: Count, pub netrc: bool, @@ -200,6 +201,7 @@ pub fn parse() -> Result { .arg(commands::ipv6()) .arg(commands::jobs()) .arg(commands::json()) + .arg(commands::limit_rate()) .arg(commands::max_filesize()) .arg(commands::max_redirects()) .arg(commands::max_time()) @@ -282,6 +284,7 @@ fn parse_matches(arg_matches: &ArgMatches) -> Result Result http::IpResolve::default(), }; let max_filesize = self.max_filesize; + // Like curl, we don't differentiate upload and download limit rate, we have + // only one option. + let max_recv_speed = self.limit_rate; + let max_send_speed = self.limit_rate; let max_redirect = self.max_redirect; let netrc = self.netrc; let netrc_file = self.netrc_file.clone(); @@ -461,7 +469,9 @@ impl CliOptions { .insecure(insecure) .ip_resolve(ip_resolve) .max_filesize(max_filesize) + .max_recv_speed(max_recv_speed) .max_redirect(max_redirect) + .max_send_speed(max_send_speed) .netrc(netrc) .netrc_file(netrc_file) .netrc_optional(netrc_optional) diff --git a/packages/hurl/src/http/client.rs b/packages/hurl/src/http/client.rs index afaac7c45f8..7d7bf9588b0 100644 --- a/packages/hurl/src/http/client.rs +++ b/packages/hurl/src/http/client.rs @@ -464,6 +464,12 @@ impl Client { if let Some(max_filesize) = options.max_filesize { self.handle.max_filesize(max_filesize)?; } + if let Some(max_recv_speed) = options.max_recv_speed { + self.handle.max_recv_speed(max_recv_speed.0)?; + } + if let Some(max_send_speed) = options.max_send_speed { + self.handle.max_send_speed(max_send_speed.0)?; + } self.set_ssl_options(options.ssl_no_revoke)?; diff --git a/packages/hurl/src/http/options.rs b/packages/hurl/src/http/options.rs index 8b5ac02cfa8..ba881912547 100644 --- a/packages/hurl/src/http/options.rs +++ b/packages/hurl/src/http/options.rs @@ -15,7 +15,7 @@ * limitations under the License. * */ -use hurl_core::typing::Count; +use hurl_core::typing::{BytesPerSec, Count}; use std::time::Duration; use crate::http::request::RequestedHttpVersion; @@ -37,7 +37,9 @@ pub struct ClientOptions { pub insecure: bool, pub ip_resolve: IpResolve, pub max_filesize: Option, + pub max_recv_speed: Option, pub max_redirect: Count, + pub max_send_speed: Option, pub netrc: bool, pub netrc_file: Option, pub netrc_optional: bool, @@ -77,7 +79,9 @@ impl Default for ClientOptions { insecure: false, ip_resolve: IpResolve::default(), max_filesize: None, + max_recv_speed: None, max_redirect: Count::Finite(50), + max_send_speed: None, netrc: false, netrc_file: None, netrc_optional: false, @@ -155,6 +159,12 @@ impl ClientOptions { arguments.push("--max-filesize".to_string()); arguments.push(max_filesize.to_string()); } + if let Some(max_speed) = self.max_recv_speed { + arguments.push("--limit-rate".to_string()); + arguments.push(max_speed.to_string()); + } + // We don't implement --limit-rate for self.max_send_speed as curl limit-rate seems + // to limit both upload and download speed. There is no distinct option.. if self.max_redirect != ClientOptions::default().max_redirect { let max_redirect = match self.max_redirect { Count::Finite(n) => n as i32, @@ -228,7 +238,9 @@ mod tests { insecure: true, ip_resolve: IpResolve::IpV6, max_filesize: None, + max_recv_speed: Some(BytesPerSec(8000)), max_redirect: Count::Finite(10), + max_send_speed: Some(BytesPerSec(8000)), netrc: false, netrc_file: Some("/var/run/netrc".to_string()), netrc_optional: true, @@ -259,6 +271,8 @@ mod tests { "--insecure", "--ipv6", "--location", + "--limit-rate", + "8000", "--max-redirs", "10", "--netrc-file", diff --git a/packages/hurl/src/runner/entry.rs b/packages/hurl/src/runner/entry.rs index d802c1ad911..2ba99555cd8 100644 --- a/packages/hurl/src/runner/entry.rs +++ b/packages/hurl/src/runner/entry.rs @@ -220,7 +220,9 @@ impl ClientOptions { http_version: runner_options.http_version, ip_resolve: runner_options.ip_resolve, max_filesize: runner_options.max_filesize, + max_recv_speed: runner_options.max_recv_speed, max_redirect: runner_options.max_redirect, + max_send_speed: runner_options.max_send_speed, netrc: runner_options.netrc, netrc_file: runner_options.netrc_file.clone(), netrc_optional: runner_options.netrc_optional, diff --git a/packages/hurl/src/runner/runner_options.rs b/packages/hurl/src/runner/runner_options.rs index 1a8ef096318..0d8db1319bb 100644 --- a/packages/hurl/src/runner/runner_options.rs +++ b/packages/hurl/src/runner/runner_options.rs @@ -18,7 +18,7 @@ use std::time::Duration; use hurl_core::ast::Entry; -use hurl_core::typing::Count; +use hurl_core::typing::{BytesPerSec, Count}; use crate::http::{IpResolve, RequestedHttpVersion}; use crate::runner::Output; @@ -44,7 +44,9 @@ pub struct RunnerOptionsBuilder { insecure: bool, ip_resolve: IpResolve, max_filesize: Option, + max_recv_speed: Option, max_redirect: Count, + max_send_speed: Option, netrc: bool, netrc_file: Option, netrc_optional: bool, @@ -89,7 +91,9 @@ impl Default for RunnerOptionsBuilder { insecure: false, ip_resolve: IpResolve::default(), max_filesize: None, + max_recv_speed: None, max_redirect: Count::Finite(50), + max_send_speed: None, netrc: false, netrc_file: None, netrc_optional: false, @@ -255,6 +259,12 @@ impl RunnerOptionsBuilder { self } + /// Set the file size limit + pub fn max_filesize(&mut self, max_filesize: Option) -> &mut Self { + self.max_filesize = max_filesize; + self + } + /// Set maximum number of redirection-followings allowed /// /// By default, the limit is set to 50 redirections @@ -263,6 +273,18 @@ impl RunnerOptionsBuilder { self } + /// Set the maximum upload speed. + pub fn max_send_speed(&mut self, max_send_speed: Option) -> &mut Self { + self.max_send_speed = max_send_speed; + self + } + + /// Set the maximum download speed. + pub fn max_recv_speed(&mut self, max_recv_speed: Option) -> &mut Self { + self.max_recv_speed = max_recv_speed; + self + } + /// Sets the path-as-is flag. pub fn path_as_is(&mut self, path_as_is: bool) -> &mut Self { self.path_as_is = path_as_is; @@ -386,12 +408,6 @@ impl RunnerOptionsBuilder { self } - /// Set the file size limit - pub fn max_filesize(&mut self, max_filesize: Option) -> &mut Self { - self.max_filesize = max_filesize; - self - } - /// Create an instance of [`RunnerOptions`]. pub fn build(&self) -> RunnerOptions { RunnerOptions { @@ -414,7 +430,9 @@ impl RunnerOptionsBuilder { insecure: self.insecure, ip_resolve: self.ip_resolve, max_filesize: self.max_filesize, + max_recv_speed: self.max_recv_speed, max_redirect: self.max_redirect, + max_send_speed: self.max_send_speed, netrc: self.netrc, netrc_file: self.netrc_file.clone(), netrc_optional: self.netrc_optional, @@ -460,7 +478,9 @@ pub struct RunnerOptions { pub(crate) ip_resolve: IpResolve, pub(crate) insecure: bool, pub(crate) max_filesize: Option, + pub(crate) max_recv_speed: Option, pub(crate) max_redirect: Count, + pub(crate) max_send_speed: Option, pub(crate) netrc: bool, pub(crate) netrc_file: Option, pub(crate) netrc_optional: bool, diff --git a/packages/hurl_core/src/typing.rs b/packages/hurl_core/src/typing.rs index 5c5b636fca3..e07e6edcd5e 100644 --- a/packages/hurl_core/src/typing.rs +++ b/packages/hurl_core/src/typing.rs @@ -93,3 +93,13 @@ impl FromStr for DurationUnit { } } } + +/// Represents bit rate. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct BytesPerSec(pub u64); + +impl fmt::Display for BytesPerSec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +}