Hurl 4.2.0, the HTTP/3 Edition
The Hurl team is thrilled to announce Hurl 4.2.0 !
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
- Install With conda-forge
- Save Response per Request
- jsonpath Filter
- More curl options: IPv6/IPv4, unix-socket etc...
- Others
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:
- compile your own version of libcurl to support HTTP/3. Cloudflare has a simple Homebrew formula to build libcurl with HTTP/3
- link Hurl with this updated libcurl
- 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:
- Get the JSON response data for our first cat
- Extract the image’s URL of this cat
- Save it to disk with
--output
- Get the JSON response data for our second cat
- Extract the image’s URL of this cat
- Save it to disk with
--output
- 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:
- First, with a
regex
query, we extract this string
regex /var s = '(.*)';/
- 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!