Hurl 4.2.0, the HTTP/3 Edition

Hurl loves HTTP3

The Hurl team is thrilled to announce Hurl 4.2.0 Rocket !

Hurl is a command line tool powered by curl, that runs HTTP requests defined in a simple plain text format:

GET https://example.org/api/tests/4567
HTTP 200
[Asserts]
header "x-foo" contains "bar"
certificate "Expire-Date" daysAfterNow > 15
jsonpath "$.status" == "RUNNING"    # Check the status code
jsonpath "$.tests" count == 25      # Check the number of items
jsonpath "$.id" matches /\d{4}/     # Check the format of the id

What’s New in This Release

HTTP/3 Support

Hurl HTTP engine is powered by curl, one of the most reliable HTTP libraries, available in millions of softwares, devices (TVs, cars, printers etc..), and is even used on Mars. In details, Hurl is written in Rust, offloading the HTTP layer to libcurl. We like this design a lot because Hurl benefits directly from curl’s power and capabilities “for free”, adding some nice asserts and captures possibilities to chain and test HTTP requests.

Started with Hurl 4.2.0, Hurl supports now HTTP/3! Running a Hurl file with HTTP/3 can be done on the command line, with --http3 option:

$ hurl --http3 test.hurl

Like curl, there are also --http2, --http1.1 and --http1.0 options to force a certain version of HTTP. With options sections, we can specify an HTTP version per request, in the same Hurl file:

GET https://myserver.com
[Options]
http3: true
HTTP/3 200

GET https://myserver.com
[Options]
http2: true
HTTP/2 200

To use HTTP/3, the underlying libcurl used by Hurl must expose HTTP/3 features. Simply run hurl --version to check which libcurl features are supported:

$ hurl --version
hurl 4.2.0 (x86_64-apple-darwin23.0) libcurl/8.1.2 (SecureTransport) LibreSSL/3.3.6 zlib/1.2.12 nghttp2/1.55.1
Features (libcurl):  alt-svc AsynchDNS HSTS HTTP2 HTTP3 IPv6 Largefile libz NTLM NTLM_WB SPNEGO SSL UnixSockets
Features (built-in): brotli

If installed on macOS with Homebrew, Hurl uses the system libcurl that does not support HTTP/3 yet. To use HTTP/3 on macOS, you can:

  1. compile your own version of libcurl to support HTTP/3. Cloudflare has a simple Homebrew formula to build libcurl with HTTP/3
  2. link Hurl with this updated libcurl
  3. Enjoy!

There are very few HTTP clients that support HTTP/3, so we’re very happy with this new feature and look forward for feedbacks!

Install With conda-forge

Thanks to @humphd, a recurring provider of new features, Hurl can now be installed through conda-forge, a community-led packet manager for Conda:

$ conda install -c conda-forge hurl

conda-forge Hurl installation supports macOS, Linux, Windows, on x86 and ARM 64 bits architectures so we’ve got you covered!

What’s very interesting with conda-forge is that other packet managers use it as a distribution source. For instance, pixi, a powerful and fast package management tool, uses the existing conda ecosystem, so Hurl can be installed with pixi:

$ pixi init hello-world
$ cd hello-world
$ pixi add hurl

There are, of course, a lot of other ways to install Hurl. If your preferred platform is not yet supported, drop-us an issue on GitHub!

Save Response per Request

Hurl can be used as a testing tool, but it can be also simply used to get HTTP response when you’ve to chain multiple dependant requests (download a resource behind a login for instance). In a Hurl file, you can chain multiple requests, passing data from one to another:

# Get home:
GET https://example.org
HTTP 200
[Captures]
csrf_token: xpath "string(//meta[@name='_csrf_token']/@content)"

# Do login!
POST https://example.org/login?user=toto&password=1234
X-CSRF-TOKEN: {{csrf_token}}
HTTP 302

# Get our precisous resource:
GET https://example.org/protected
HTTP 200

When run, Hurl outputs the last response body on the standard output (in this case the response of GET https://example.org/protected). Like curl, this response can be saved to a file with --output option:

$ hurl --output /tmp/response.json api.hurl

With options sections, we can now save to file each individual response of any request.

Let’s imagine we have a cat API that returns a JSON response for a given cat. If we want to save multiple cat images on disk, we can write this Hurl file:

  1. Get the JSON response data for our first cat
  2. Extract the image’s URL of this cat
  3. Save it to disk with --output
  4. Get the JSON response data for our second cat
  5. Extract the image’s URL of this cat
  6. Save it to disk with --output
  7. Repeat...

Our Hurl file will look like this:

# Get our first cat resource and capture its image
GET https://catapi.com/cats/1
HTTP 200
[Captures]
img: jsonpath "$.url"

# Download its image and save it to disk
GET {{img}}
[Options]
output: cat1.jpg
HTTP 200

# Do the same with the second cat:
GET https://catapi.com/cats/2
HTTP 200
[Captures]
img: jsonpath "$.url"

# Download its image and save it to disk
GET {{img}}
[Options]
output: cat2.jpg
HTTP 200

jsonpath Filter

Data can be captured and/or tested from HTTP responses using JSONPath, XPath, regex etc... Filters allow us to even refine the extracted data. With the new jsonpath filter, we’re able to chain XPath or regex queries with a JSONPath filter:

For instance, given this HTML file:

<!DOCTYPE html>
<html>
<body>

<p id="user"></p>

<script>
var s = '{"first_name" : "Sammy", "last_name" : "Shark", "location" : "Ocean"}';

var obj = JSON.parse(s);

document.getElementById("user").innerHTML =
"Name: " + obj.first_name + " " + obj.last_name + "<br>" +
"Location: " + obj.location;
</script>

</body>
</html>

We want to extract the string {"first_name" : "Sammy", "last_name" : "Shark", "location" : "Ocean"} and analyse it as a JSON:

  1. First, with a regex query, we extract this string
    regex /var s = '(.*)';/
  2. Then, with a jsonpath filter, we test the data
    jsonpath "$.first_name" == "Sammy"

So our Hurl file will be:

GET https://example.com/test.html
HTTP 200
[Asserts]
regex /var s = '(.*)';/ jsonpath "$.first_name" == "Sammy"
regex /var s = '(.*)';/ jsonpath "$.last_name" == "Shark"
regex /var s = '(.*)';/ jsonpath "$.location" == "Ocean"

Or with an intermediate capture:

GET https://example.com/test.html
HTTP 200
[Captures]
s: regex /var s = '(.*)';/
[Asserts]
variable "s" jsonpath "$.first_name" == "Sammy"
variable "s" jsonpath "$.last_name" == "Shark"
variable "s" jsonpath "$.location" == "Ocean"

Check out all the available filters to get the data you want from the HTTP responses.

More curl options: IPv6/IPv4, unix-socket etc...

Finally, in Hurl 4.2.0, more curl options have been implemented --ipv4 / --ipv6, --unix-socket and --location-trusted.

--ipv4 / --ipv6

In a shell:

$ hurl --ipv6 foo.hurl

Or for a specific request in a Hurl file:

GET https://foo.com
[Options]
ipv6: true
HTTP 200

GET https://foo.com
[Options]
ipv4: true
HTTP 200

--unix-socket

In a shell:

$ hurl --unix-socket pid bar.hurl

Or for a specific request in a Hurl file:

GET https://bar.com
[Options]
unix-socket: "pid"
HTTP 200

--location-trusted

In a shell:

$ hurl --location-trusted baz.hurl

Or for a specific request in a Hurl file:

GET https://baz.com
[Options]
location-trusted: true
HTTP 200

All the options can, of course, be combined on a specific request:

GET https://baz.com
[Options]
ipv6: true
location-trusted: true
skip: false
HTTP 200

Others

There are other improvements with Hurl 4.2.0 (dark mode for HTML report, skip option request, etc...) and also a lot of bug fixes, you can check the complete list of enhancements and bug fixes in our release note.

If you like Hurl, don’t hesitate to give us a star on GitHub or share it on Twitter!

We’ll be happy to hear from you, either for enhancement requests or for sharing your success story using Hurl!