From: Willy Tarreau Date: Mon, 21 Sep 2026 20:19:59 +0000 (+0000) Subject: REGTESTS: sample_fetches: add a test for the HTTP request fetches X-Git-Tag: v3.5-dev8~67 X-Git-Url: https://git.kaiwu.me/$%7BGITURL%7D$%7Bcid%7D?a=commitdiff_plain;h=ba5d9512bf4a01ea6609618c5faefe7656a9bb4b;p=haproxy.git REGTESTS: sample_fetches: add a test for the HTTP request fetches The request side header, URI and body fetches were largely uncovered. This test checks req.hdr_val, req.hdr_ip, req.hdr_cnt with an occurrence index, req.hdr_names with and without a custom delimiter, req.fhdr and req.fhdr_cnt against req.hdr on a comma-delimited list, req.hdrs, req.hdrs_bin, base, baseq, base32, base32+src, url32, url32+src, url_ip, url_port, query, urlp_val, req.body_len, req.body_size, req.body_param with and without the case-insensitive flag, req.timer.hdr, req.timer.idle, req.ver, req.proto_http, the deprecated req_ver and req_proto_http aliases, and the capture.req.method, capture.req.ver, capture.req.uri and capture.req.hdr fetches. The fetches are collected into variables from the http-request rule set since most of them may not be used from the http-response one, and they are reported in the response headers so that a single exchange suffices. An absolute URI is used so that base, baseq, url_ip and url_port have something to extract, which requires the Host header to match the authority. A second request uses an address as the authority since url_ip and url_port do not resolve host names. --- diff --git a/reg-tests/sample_fetches/http_request.vtc b/reg-tests/sample_fetches/http_request.vtc new file mode 100644 index 000000000..4f2b2dce1 --- /dev/null +++ b/reg-tests/sample_fetches/http_request.vtc @@ -0,0 +1,198 @@ +varnishtest "Test the HTTP request header, URI and body sample fetches" + +# A large part of the request side fetches had no coverage: the header +# list and value families, the URI decomposition fetches, the base32 and +# url32 hashes, the request captures and the body fetches. They are all +# collected here into variables from the http-request rule set, where +# they are available, and reported in the response headers, so that a +# single exchange is enough to check them all. + +feature ignore_unknown_macro + +server s1 { + rxreq + expect req.bodylen == 21 + txresp +} -repeat 2 -start + +haproxy h1 -conf { + global + .if feature(THREAD) + thread-groups 1 + .endif + chroot / + + defaults + mode http + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + frontend fe + bind "fd@${fe}" + capture request header x-num len 16 + + http-request wait-for-body time "${HAPROXY_TEST_TIMEOUT-5s}" + + # the request side fetches are not usable from the http-response rule + # set, they are collected into variables while the request is being + # processed + http-request set-var(txn.x_method) method + http-request set-var(txn.x_ver) req.ver + http-request set-var(txn.x_reqver) req_ver + http-request set-var(txn.x_proto) req.proto_http + http-request set-var(txn.x_sproto) req_proto_http + http-request set-var(txn.x_capmethod) capture.req.method + http-request set-var(txn.x_capver) capture.req.ver + http-request set-var(txn.x_capuri) capture.req.uri + http-request set-var(txn.x_caphdr) capture.req.hdr(0) + http-request set-var(txn.x_hdr) req.hdr(x-num) + http-request set-var(txn.x_hdrval) req.hdr_val(x-num),add(1) + http-request set-var(txn.x_hdrip) req.hdr_ip(x-ip) + http-request set-var(txn.x_hdrcnt) req.hdr_cnt(x-list) + http-request set-var(txn.x_hdrfirst) req.hdr(x-list,1) + http-request set-var(txn.x_hdrlast) req.hdr(x-list,-1) + http-request set-var(txn.x_fhdrcnt) req.fhdr_cnt(x-csv) + http-request set-var(txn.x_fhdr) req.fhdr(x-csv) + http-request set-var(txn.x_names) req.hdr_names + http-request set-var(txn.x_names2) req.hdr_names(|) + http-request set-var(txn.x_hdrs) str(yes) if { req.hdrs -m sub "x-num: 42" } + http-request set-var(txn.x_hdrsbin) req.hdrs_bin,hex,lower + http-request set-var(txn.x_base) base + http-request set-var(txn.x_baseq) baseq + http-request set-var(txn.x_urlip) url_ip + http-request set-var(txn.x_urlport) url_port + http-request set-var(txn.x_query) query + http-request set-var(txn.x_urlpval) urlp_val(n),add(1) + # base32 and url32 are integer hashes, their "+src" variants append + # the source address to the hash and return a binary block + http-request set-var(txn.x_base32) base32 + http-request set-var(txn.x_base32src) base32+src,hex,lower + http-request set-var(txn.x_url32) url32 + http-request set-var(txn.x_url32src) url32+src,hex,lower + http-request set-var(txn.x_bodylen) req.body_len + http-request set-var(txn.x_bodysize) req.body_size + http-request set-var(txn.x_bodyparam) req.body_param(who) + http-request set-var(txn.x_bodyparami) req.body_param(WHO,i) + http-request set-var(txn.x_timerhdr) req.timer.hdr + http-request set-var(txn.x_timeridle) req.timer.idle + + http-response set-header x-method "%[var(txn.x_method)]" + http-response set-header x-ver "%[var(txn.x_ver)]" + http-response set-header x-reqver "%[var(txn.x_reqver)]" + http-response set-header x-proto "%[var(txn.x_proto)]" + http-response set-header x-sproto "%[var(txn.x_sproto)]" + http-response set-header x-capmethod "%[var(txn.x_capmethod)]" + http-response set-header x-capver "%[var(txn.x_capver)]" + http-response set-header x-capuri "%[var(txn.x_capuri)]" + http-response set-header x-caphdr "%[var(txn.x_caphdr)]" + http-response set-header x-hdr "%[var(txn.x_hdr)]" + http-response set-header x-hdrval "%[var(txn.x_hdrval)]" + http-response set-header x-hdrip "%[var(txn.x_hdrip)]" + http-response set-header x-hdrcnt "%[var(txn.x_hdrcnt)]" + http-response set-header x-hdrfirst "%[var(txn.x_hdrfirst)]" + http-response set-header x-hdrlast "%[var(txn.x_hdrlast)]" + http-response set-header x-fhdrcnt "%[var(txn.x_fhdrcnt)]" + http-response set-header x-fhdr "%[var(txn.x_fhdr)]" + http-response set-header x-names "%[var(txn.x_names)]" + http-response set-header x-names2 "%[var(txn.x_names2)]" + http-response set-header x-hdrs "%[var(txn.x_hdrs)]" + http-response set-header x-hdrsbin "%[var(txn.x_hdrsbin)]" + http-response set-header x-base "%[var(txn.x_base)]" + http-response set-header x-baseq "%[var(txn.x_baseq)]" + http-response set-header x-urlip "%[var(txn.x_urlip)]" + http-response set-header x-urlport "%[var(txn.x_urlport)]" + http-response set-header x-query "%[var(txn.x_query)]" + http-response set-header x-urlpval "%[var(txn.x_urlpval)]" + http-response set-header x-base32 "%[var(txn.x_base32)]" + http-response set-header x-base32src "%[var(txn.x_base32src)]" + http-response set-header x-url32 "%[var(txn.x_url32)]" + http-response set-header x-url32src "%[var(txn.x_url32src)]" + http-response set-header x-bodylen "%[var(txn.x_bodylen)]" + http-response set-header x-bodysize "%[var(txn.x_bodysize)]" + http-response set-header x-bodyparam "%[var(txn.x_bodyparam)]" + http-response set-header x-bodyparami "%[var(txn.x_bodyparami)]" + http-response set-header x-timerhdr "%[var(txn.x_timerhdr)]" + http-response set-header x-timeridle "%[var(txn.x_timeridle)]" + + default_backend be + + backend be + server s1 ${s1_addr}:${s1_port} +} -start + +client c1 -connect ${h1_fe_sock} { + # the Host header must match the authority of the absolute URI, + # otherwise the request is rejected + txreq -req POST -url "http://www.example.com:8080/path/to/res?n=41&x=y" \ + -hdr "Host: www.example.com:8080" \ + -hdr "x-num: 42" \ + -hdr "x-list: a" \ + -hdr "x-list: b" \ + -hdr "x-csv: 1, 2, 3" \ + -hdr "x-ip: 10.0.0.7" \ + -hdr "content-type: application/x-www-form-urlencoded" \ + -body "who=world&other=thing" + rxresp + expect resp.status == 200 + expect resp.http.x-method == "POST" + expect resp.http.x-ver == "1.1" + expect resp.http.x-reqver == "1.1" + expect resp.http.x-proto == "1" + expect resp.http.x-sproto == "1" + + expect resp.http.x-capmethod == "POST" + expect resp.http.x-capver == "HTTP/1.1" + expect resp.http.x-capuri == "/path/to/res?n=41&x=y" + expect resp.http.x-caphdr == "42" + + expect resp.http.x-hdr == "42" + expect resp.http.x-hdrval == "43" + expect resp.http.x-hdrip == "10.0.0.7" + expect resp.http.x-hdrcnt == "2" + expect resp.http.x-hdrfirst == "a" + expect resp.http.x-hdrlast == "b" + expect resp.http.x-fhdrcnt == "1" + expect resp.http.x-fhdr == "1, 2, 3" + expect resp.http.x-names ~ "x-num" + expect resp.http.x-names2 ~ "x-num[|]x-list" + expect resp.http.x-hdrs == "yes" + expect resp.http.x-hdrsbin ~ "782d6e756d023432" + + # an absolute URI carries the authority, which "base" prepends to the + # path while "baseq" also keeps the query string + expect resp.http.x-base == "www.example.com:8080/path/to/res" + expect resp.http.x-baseq == "www.example.com:8080/path/to/res?n=41&x=y" + # url_ip and url_port only work on an authority given as an address + expect resp.http.x-urlip == "" + expect resp.http.x-urlport == "" + expect resp.http.x-query == "n=41&x=y" + expect resp.http.x-urlpval == "42" + + expect resp.http.x-base32 ~ "^[0-9]+$" + expect resp.http.x-base32src ~ "^[0-9a-f]{16}$" + expect resp.http.x-url32 ~ "^[0-9]+$" + expect resp.http.x-url32src ~ "^[0-9a-f]{16}$" + + expect resp.http.x-bodylen == "21" + expect resp.http.x-bodysize == "21" + expect resp.http.x-bodyparam == "world" + expect resp.http.x-bodyparami == "world" + + expect resp.http.x-timerhdr ~ "^-?[0-9]+$" + expect resp.http.x-timeridle ~ "^-?[0-9]+$" +} -run + +client c2 -connect ${h1_fe_sock} { + # a host given as an address feeds url_ip and url_port + txreq -req POST -url "http://10.1.2.3:81/other?n=41&x=y" \ + -hdr "Host: 10.1.2.3:81" \ + -hdr "x-num: 42" \ + -hdr "content-type: application/x-www-form-urlencoded" \ + -body "who=world&other=thing" + rxresp + expect resp.status == 200 + expect resp.http.x-urlip == "10.1.2.3" + expect resp.http.x-urlport == "81" + expect resp.http.x-base == "10.1.2.3:81/other" +} -run