From 5b8ada87c5747fe030270771ff42e6b46a6adaf3 Mon Sep 17 00:00:00 2001 From: jcamiel Date: Fri, 13 Oct 2023 07:44:19 +0200 Subject: [PATCH] Specialize libcurl error message for unsupported HTTP version error message. --- .../http_version_not_supported.err | 7 +++ .../http_version_not_supported.exit | 1 + .../http_version_not_supported.html | 6 +++ .../http_version_not_supported.hurl | 5 +++ .../http_version_not_supported.json | 1 + .../http_version_not_supported.ps1 | 11 +++++ .../http_version_not_supported.sh | 10 +++++ packages/hurl/src/http/client.rs | 44 ++++++++++++------- packages/hurl/src/http/error.rs | 3 ++ packages/hurl/src/http/request.rs | 14 ++++++ packages/hurl/src/runner/core.rs | 3 +- packages/hurl/src/runner/error.rs | 8 ++++ 12 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 integration/tests_failed/http_version_not_supported.err create mode 100644 integration/tests_failed/http_version_not_supported.exit create mode 100644 integration/tests_failed/http_version_not_supported.html create mode 100644 integration/tests_failed/http_version_not_supported.hurl create mode 100644 integration/tests_failed/http_version_not_supported.json create mode 100644 integration/tests_failed/http_version_not_supported.ps1 create mode 100755 integration/tests_failed/http_version_not_supported.sh diff --git a/integration/tests_failed/http_version_not_supported.err b/integration/tests_failed/http_version_not_supported.err new file mode 100644 index 00000000000..feca6c2092c --- /dev/null +++ b/integration/tests_failed/http_version_not_supported.err @@ -0,0 +1,7 @@ +error: Unsupported HTTP version + --> tests_failed/http_version_not_supported.hurl:4:5 + | + 4 | GET https://google.com + | ^^^^^^^^^^^^^^^^^^ HTTP/3 is not supported, check --version + | + diff --git a/integration/tests_failed/http_version_not_supported.exit b/integration/tests_failed/http_version_not_supported.exit new file mode 100644 index 00000000000..00750edc07d --- /dev/null +++ b/integration/tests_failed/http_version_not_supported.exit @@ -0,0 +1 @@ +3 diff --git a/integration/tests_failed/http_version_not_supported.html b/integration/tests_failed/http_version_not_supported.html new file mode 100644 index 00000000000..188f8e6ea6b --- /dev/null +++ b/integration/tests_failed/http_version_not_supported.html @@ -0,0 +1,6 @@ +
# This test is run when the libcurl used by Hurl
+# doesn't support HTTP/3 so it should failed with
+# an appropriate error message.
+GET https://google.com
+HTTP 200
+
diff --git a/integration/tests_failed/http_version_not_supported.hurl b/integration/tests_failed/http_version_not_supported.hurl new file mode 100644 index 00000000000..dfe17f9510c --- /dev/null +++ b/integration/tests_failed/http_version_not_supported.hurl @@ -0,0 +1,5 @@ +# This test is run when the libcurl used by Hurl +# doesn't support HTTP/3 so it should failed with +# an appropriate error message. +GET https://google.com +HTTP 200 diff --git a/integration/tests_failed/http_version_not_supported.json b/integration/tests_failed/http_version_not_supported.json new file mode 100644 index 00000000000..108d51e0c22 --- /dev/null +++ b/integration/tests_failed/http_version_not_supported.json @@ -0,0 +1 @@ +{"entries":[{"request":{"method":"GET","url":"https://google.com"},"response":{"status":200}}]} diff --git a/integration/tests_failed/http_version_not_supported.ps1 b/integration/tests_failed/http_version_not_supported.ps1 new file mode 100644 index 00000000000..0aa09a25fd9 --- /dev/null +++ b/integration/tests_failed/http_version_not_supported.ps1 @@ -0,0 +1,11 @@ +Set-StrictMode -Version latest +$ErrorActionPreference = 'Stop' + +$ErrorActionPreference = 'Continue' +curl --version | grep Features | grep -q HTTP3 +if ($LASTEXITCODE -eq 0) { + exit 255 +} +$ErrorActionPreference = 'Stop' + +hurl --http3 tests_failed/http_version_not_supported.hurl diff --git a/integration/tests_failed/http_version_not_supported.sh b/integration/tests_failed/http_version_not_supported.sh new file mode 100755 index 00000000000..36c210a07df --- /dev/null +++ b/integration/tests_failed/http_version_not_supported.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -Eeuo pipefail + +set +eo pipefail +if (curl --version | grep Features | grep -q HTTP3); then + exit 255 +fi +set -Eeuo pipefail + +hurl --http3 tests_failed/http_version_not_supported.hurl diff --git a/packages/hurl/src/http/client.rs b/packages/hurl/src/http/client.rs index de9a5d3046c..da64ea0b4d1 100644 --- a/packages/hurl/src/http/client.rs +++ b/packages/hurl/src/http/client.rs @@ -157,6 +157,33 @@ impl Client { // way to get access to the outgoing headers. self.handle.verbose(true)?; + // libcurl tries to reuse connections as much as possible (see ) + // That's why an `handle` initiated with a HTTP 2 version may keep using HTTP 2 protocol + // even if we ask to switch to HTTP 3 in the same session (using `[Options]` section for + // instance). + // > Note that the HTTP version is just a request. libcurl still prioritizes to reuse + // > existing connections so it might then reuse a connection using a HTTP version you + // > have not asked for. + // + // So, if we detect a change of requested HTTP version, we force libcurl to refresh its + // connections (see ) + let http_version = options.http_version; + self.state.set_requested_http_version(http_version); + if self.state.has_changed() { + logger.debug("Force refreshing connections because requested HTTP version change"); + self.handle.fresh_connect(true)?; + } + // We set the HTTP requested version and specialize libcurl error message to report + // the unsupported HTTP version if there is an error. + if let Err(e) = self.handle.http_version(http_version.into()) { + return match e.code() { + curl_sys::CURLE_UNSUPPORTED_PROTOCOL => { + Err(HttpError::UnsupportedHttpVersion(http_version)) + } + _ => Err(e.into()), + }; + } + // Activates the access of certificates info chain after a transfer has been executed. self.handle.certinfo(true)?; @@ -192,23 +219,6 @@ impl Client { self.handle.timeout(options.timeout)?; self.handle.connect_timeout(options.connect_timeout)?; - // libcurl tries to reuse connections as much as possible (see ) - // That's why an `handle` initiated with a HTTP 2 version may keep using HTTP 2 protocol - // even if we ask to switch to HTTP 3 in the same session (using `[Options]` section for - // instance). - // > Note that the HTTP version is just a request. libcurl still prioritizes to reuse - // > existing connections so it might then reuse a connection using a HTTP version you - // > have not asked for. - // - // So, if we detect a change of requested HTTP version, we force libcurl to refresh its - // connections (see ) - self.state.set_requested_http_version(options.http_version); - if self.state.has_changed() { - logger.debug("Force refreshing connections because requested HTTP version change"); - self.handle.fresh_connect(true)?; - } - self.handle.http_version(options.http_version.into())?; - self.set_ssl_options(options.ssl_no_revoke)?; let url = self.generate_url(&request_spec.url, &request_spec.querystring); diff --git a/packages/hurl/src/http/error.rs b/packages/hurl/src/http/error.rs index ce358ac4974..d421ea081e3 100644 --- a/packages/hurl/src/http/error.rs +++ b/packages/hurl/src/http/error.rs @@ -16,6 +16,8 @@ * */ +use crate::http::RequestedHttpVersion; + #[derive(Clone, Debug, PartialEq, Eq)] pub enum HttpError { CouldNotParseResponse, @@ -45,6 +47,7 @@ pub enum HttpError { UnsupportedContentEncoding { description: String, }, + UnsupportedHttpVersion(RequestedHttpVersion), InvalidUrl(String), InvalidUrlPrefix(String), } diff --git a/packages/hurl/src/http/request.rs b/packages/hurl/src/http/request.rs index 67667b6f32f..e30927aee49 100644 --- a/packages/hurl/src/http/request.rs +++ b/packages/hurl/src/http/request.rs @@ -15,6 +15,7 @@ * limitations under the License. * */ +use std::fmt; use url::Url; use crate::http::core::*; @@ -38,6 +39,19 @@ pub enum RequestedHttpVersion { Http3, } +impl fmt::Display for RequestedHttpVersion { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let value = match self { + RequestedHttpVersion::Default => "HTTP (default)", + RequestedHttpVersion::Http10 => "HTTP/1.0", + RequestedHttpVersion::Http11 => "HTTP/1.1", + RequestedHttpVersion::Http2 => "HTTP/2", + RequestedHttpVersion::Http3 => "HTTP/3", + }; + write!(f, "{value}") + } +} + impl Request { /// Extracts query string params from the url of the request. pub fn query_string_params(&self) -> Vec { diff --git a/packages/hurl/src/runner/core.rs b/packages/hurl/src/runner/core.rs index 391506f737f..f374d0cfe0e 100644 --- a/packages/hurl/src/runner/core.rs +++ b/packages/hurl/src/runner/core.rs @@ -19,7 +19,7 @@ use std::path::PathBuf; use hurl_core::ast::SourceInfo; -use crate::http::{Call, Cookie}; +use crate::http::{Call, Cookie, RequestedHttpVersion}; use crate::runner::value::Value; #[derive(Clone, Debug, PartialEq, Eq)] @@ -132,6 +132,7 @@ pub enum RunnerError { SslCertificate(String), UnsupportedContentEncoding(String), + UnsupportedHttpVersion(RequestedHttpVersion), CouldNotUncompressResponse(String), FileReadAccess { diff --git a/packages/hurl/src/runner/error.rs b/packages/hurl/src/runner/error.rs index a72b9ab95f5..3d01580a3bf 100644 --- a/packages/hurl/src/runner/error.rs +++ b/packages/hurl/src/runner/error.rs @@ -61,6 +61,7 @@ impl Error for runner::Error { RunnerError::UnrenderableVariable { .. } => "Unrenderable variable".to_string(), RunnerError::NoQueryResult => "No query result".to_string(), RunnerError::UnsupportedContentEncoding(..) => "Decompression error".to_string(), + RunnerError::UnsupportedHttpVersion(..) => "Unsupported HTTP version".to_string(), RunnerError::CouldNotUncompressResponse(..) => "Decompression error".to_string(), RunnerError::InvalidJson { .. } => "Invalid JSON".to_string(), RunnerError::UnauthorizedFileAccess { .. } => "Unauthorized file access".to_string(), @@ -143,6 +144,10 @@ impl Error for runner::Error { RunnerError::UnsupportedContentEncoding(algorithm) => { format!("compression {algorithm} is not supported") } + RunnerError::UnsupportedHttpVersion(version) => { + format!("{version} is not supported, check --version") + } + RunnerError::CouldNotUncompressResponse(algorithm) => { format!("could not uncompress response with {algorithm}") } @@ -206,6 +211,9 @@ impl From for RunnerError { HttpError::UnsupportedContentEncoding { description } => { RunnerError::UnsupportedContentEncoding(description) } + HttpError::UnsupportedHttpVersion(version) => { + RunnerError::UnsupportedHttpVersion(version) + } HttpError::InvalidUrl(url) => RunnerError::InvalidUrl(url), HttpError::InvalidUrlPrefix(url) => RunnerError::InvalidUrlPrefix(url), }