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..4c08f38f3d7
--- /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:3:5
+ |
+ 3 | GET http://localhost:8000/foo
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ 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..7b9652c7459
--- /dev/null
+++ b/integration/tests_failed/http_version_not_supported.html
@@ -0,0 +1,5 @@
+
+
+GET http://localhost:8000/foo
+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..f7b80d53f26
--- /dev/null
+++ b/integration/tests_failed/http_version_not_supported.hurl
@@ -0,0 +1,4 @@
+# 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 http://localhost:8000/foo
+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..3d348dd0ba6
--- /dev/null
+++ b/integration/tests_failed/http_version_not_supported.json
@@ -0,0 +1 @@
+{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/foo"},"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..40bdc18a952 100644
--- a/packages/hurl/src/http/client.rs
+++ b/packages/hurl/src/http/client.rs
@@ -21,8 +21,8 @@ use std::str::FromStr;
use base64::engine::general_purpose;
use base64::Engine;
use chrono::Utc;
-use curl::easy;
use curl::easy::{List, SslOpt};
+use curl::{easy, Version};
use encoding::all::ISO_8859_1;
use encoding::{DecoderTrap, Encoding};
use url::Url;
@@ -48,6 +48,9 @@ pub struct Client {
handle: Box,
/// Current State
state: ClientState,
+ /// HTTP version support
+ http2: bool,
+ http3: bool,
}
/// Represents the state of the HTTP client.
@@ -78,10 +81,13 @@ impl ClientState {
impl Client {
/// Creates HTTP Hurl client.
pub fn new() -> Client {
- let h = easy::Easy::new();
+ let handle = easy::Easy::new();
+ let version = Version::get();
Client {
- handle: Box::new(h),
+ handle: Box::new(handle),
state: ClientState::default(),
+ http2: version.feature_http2(),
+ http3: version.feature_http3(),
}
}
@@ -157,6 +163,30 @@ impl Client {
// way to get access to the outgoing headers.
self.handle.verbose(true)?;
+ // We checks libcurl HTTP version support.
+ let http_version = options.http_version;
+ if (http_version == RequestedHttpVersion::Http2 && !self.http2)
+ || (http_version == RequestedHttpVersion::Http3 && !self.http3)
+ {
+ return Err(HttpError::UnsupportedHttpVersion(http_version));
+ }
+
+ // 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(http_version);
+ if self.state.has_changed() {
+ logger.debug("Force refreshing connections because requested HTTP version change");
+ self.handle.fresh_connect(true)?;
+ }
+
// Activates the access of certificates info chain after a transfer has been executed.
self.handle.certinfo(true)?;
@@ -192,23 +222,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),
}