Skip to content

Commit

Permalink
Specialize libcurl error message for unsupported HTTP version error m…
Browse files Browse the repository at this point in the history
…essage.
  • Loading branch information
jcamiel committed Oct 14, 2023
1 parent 897da08 commit 9f234ad
Show file tree
Hide file tree
Showing 12 changed files with 95 additions and 18 deletions.
7 changes: 7 additions & 0 deletions integration/tests_failed/http_version_not_supported.err
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
error: Unsupported HTTP version
--> tests_failed/http_version_not_supported.hurl:4:5
|
4 | GET https://localhost:8000/foo
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ HTTP/3 is not supported, check --version
|

1 change: 1 addition & 0 deletions integration/tests_failed/http_version_not_supported.exit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3
6 changes: 6 additions & 0 deletions integration/tests_failed/http_version_not_supported.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<pre><code class="language-hurl"><span class="hurl-entry"><span class="request"><span class="line"></span><span class="comment"># This test is run when the libcurl used by Hurl</span>
<span class="line"></span><span class="comment"># doesn't support HTTP/3 so it should failed with</span>
<span class="line"></span><span class="comment"># an appropriate error message.</span>
<span class="line"><span class="method">GET</span> <span class="url">https://localhost:8000/foo</span></span>
</span><span class="response"><span class="line"><span class="version">HTTP</span> <span class="number">200</span></span>
</span></span></code></pre>
5 changes: 5 additions & 0 deletions integration/tests_failed/http_version_not_supported.hurl
Original file line number Diff line number Diff line change
@@ -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://localhost:8000/foo
HTTP 200
1 change: 1 addition & 0 deletions integration/tests_failed/http_version_not_supported.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"entries":[{"request":{"method":"GET","url":"https://localhost:8000/foo"},"response":{"status":200}}]}
11 changes: 11 additions & 0 deletions integration/tests_failed/http_version_not_supported.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Set-StrictMode -Version latest
$ErrorActionPreference = 'Stop'

$ErrorActionPreference = 'Continue'
curl --version | grep Features | grep -q HTTP3
if ($LASTEXITCODE -eq 0) {
exit 0
}
$ErrorActionPreference = 'Stop'

hurl --http3 tests_failed/http_version_not_supported.hurl
10 changes: 10 additions & 0 deletions integration/tests_failed/http_version_not_supported.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash
set -Eeuo pipefail

set +eo pipefail
if (curl --version | grep Features | grep -q HTTP3); then
exit
fi
set -Eeuo pipefail

hurl --http3 tests_failed/http_version_not_supported.hurl
44 changes: 27 additions & 17 deletions packages/hurl/src/http/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://curl.se/libcurl/c/CURLOPT_HTTP_VERSION.html>)
// 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 <https://curl.se/libcurl/c/CURLOPT_FRESH_CONNECT.html>)
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)?;

Expand Down Expand Up @@ -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 <https://curl.se/libcurl/c/CURLOPT_HTTP_VERSION.html>)
// 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 <https://curl.se/libcurl/c/CURLOPT_FRESH_CONNECT.html>)
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);
Expand Down
3 changes: 3 additions & 0 deletions packages/hurl/src/http/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*
*/

use crate::http::RequestedHttpVersion;

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HttpError {
CouldNotParseResponse,
Expand Down Expand Up @@ -45,6 +47,7 @@ pub enum HttpError {
UnsupportedContentEncoding {
description: String,
},
UnsupportedHttpVersion(RequestedHttpVersion),
InvalidUrl(String),
InvalidUrlPrefix(String),
}
Expand Down
14 changes: 14 additions & 0 deletions packages/hurl/src/http/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* limitations under the License.
*
*/
use std::fmt;
use url::Url;

use crate::http::core::*;
Expand All @@ -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<Param> {
Expand Down
3 changes: 2 additions & 1 deletion packages/hurl/src/runner/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -132,6 +132,7 @@ pub enum RunnerError {
SslCertificate(String),

UnsupportedContentEncoding(String),
UnsupportedHttpVersion(RequestedHttpVersion),
CouldNotUncompressResponse(String),

FileReadAccess {
Expand Down
8 changes: 8 additions & 0 deletions packages/hurl/src/runner/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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}")
}
Expand Down Expand Up @@ -206,6 +211,9 @@ impl From<HttpError> 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),
}
Expand Down

0 comments on commit 9f234ad

Please sign in to comment.