No problem! I’d over thought your previous comment and inferred you were taking about difficult to prove page load performance gains (which can be true because of all the variables at play). When we are talking about more straightforward “does it work tests” then curl is realy good as long as it uses HTTP/2 or HTTP/3.
Alternatively, you can also try the nghttp2 client tool nghttp
(nghttp(1) — nghttp2 1.52.0-DEV documentation). This gives a bit more low-level information about what it happening on the wire. In this example, I purposefully bust the cache to emulate the “server think” time where early hints shines. You can see that the 103 is returned at time 0.091
and then then 200 is returned at 0.333
. In my local adhoc tests from residential ISP, I see a consistent delta in the range of 100-250 ms.
$ nghttp https://lucaspardue.com/?cache=bust -nv -H "user-agent: curl/7.68.0"
[ 0.045] Connected
The negotiated protocol: h2
[ 0.068] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 0.068] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
(dep_stream_id=0, weight=201, exclusive=0)
[ 0.068] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
(dep_stream_id=0, weight=101, exclusive=0)
[ 0.068] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
(dep_stream_id=0, weight=1, exclusive=0)
[ 0.068] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
(dep_stream_id=7, weight=1, exclusive=0)
[ 0.068] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
(dep_stream_id=3, weight=1, exclusive=0)
[ 0.068] send HEADERS frame <length=47, flags=0x25, stream_id=13>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=11, weight=16, exclusive=0)
; Open new stream
:method: GET
:path: /?cache=bust
:scheme: https
:authority: lucaspardue.com
accept: */*
accept-encoding: gzip, deflate
user-agent: curl/7.68.0
[ 0.085] recv SETTINGS frame <length=18, flags=0x00, stream_id=0>
(niv=3)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):256]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65536]
[SETTINGS_MAX_FRAME_SIZE(0x05):16777215]
[ 0.086] recv WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=2147418112)
[ 0.086] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.086] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.091] recv (stream_id=13) :status: 103
[ 0.091] recv (stream_id=13) link: </wp-content/themes/twentynineteen/style.slim.css>; as=style; rel=preload
[ 0.091] recv HEADERS frame <length=60, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
; First response header
[ 0.333] recv (stream_id=13) :status: 200
[ 0.333] recv (stream_id=13) date: Fri, 07 Jan 2022 02:11:55 GMT
[ 0.333] recv (stream_id=13) content-type: text/html; charset=UTF-8
[ 0.333] recv (stream_id=13) link: </wp-content/themes/twentynineteen/style.slim.css>; rel=preload; as=style
[ 0.333] recv (stream_id=13) vary: Accept-Encoding
[ 0.333] recv (stream_id=13) cf-cache-status: DYNAMIC
[ 0.333] recv (stream_id=13) expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
[ 0.333] recv (stream_id=13) strict-transport-security: max-age=15552000; preload
[ 0.333] recv (stream_id=13) server: cloudflare
[ 0.333] recv (stream_id=13) cf-ray: 6c99a87d99fe7480-LHR
[ 0.333] recv (stream_id=13) content-encoding: gzip
[ 0.333] recv (stream_id=13) alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400, h3-28=":443"; ma=86400, h3-27=":443"; ma=86400
[ 0.333] recv HEADERS frame <length=344, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
[ 0.334] recv DATA frame <length=7460, flags=0x00, stream_id=13>
[ 0.334] recv DATA frame <length=8192, flags=0x00, stream_id=13>
[ 0.335] recv DATA frame <length=5888, flags=0x00, stream_id=13>
[ 0.335] recv DATA frame <length=0, flags=0x01, stream_id=13>
; END_STREAM
[ 0.335] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
(last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])