]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: http-fetch: Rework how HTTP message version is retrieved
authorChristopher Faulet <cfaulet@haproxy.com>
Wed, 25 Feb 2026 15:20:56 +0000 (16:20 +0100)
committerChristopher Faulet <cfaulet@haproxy.com>
Thu, 5 Mar 2026 14:34:46 +0000 (15:34 +0100)
Thanks to previous patches, we can now rely on the version stored in the
http_msg structure to get the request or the response version.

"req.ver" and "res.ver" sample fetch functions returns the string
representation of the version, without the prefix, so "<major>.<minor>", but
only if the version is valid. For the response, "res.ver" may be added from
a health-check context, in that case, the HTX message is used.

"capture.req.ver" and "capture.res.ver" does the same but the "HTTP/" prefix
is added to the result. And "capture.res.ver" cannot be called from a
health-check.

To ease the version formatting and avoid code duplication, an helper
function was added. So these samples are now relying on "get_msg_version()".

doc/configuration.txt
src/http_fetch.c

index 5245fd084e11c2d1c6430ba02d8d310de1e1e5d7..e3454608851b4f0dac410df9319ba4c3c0fbc4e8 100644 (file)
@@ -26907,9 +26907,10 @@ capture.req.uri : string
   allocated.
 
 capture.req.ver : string
-  This extracts the request's HTTP version and returns either "HTTP/1.0" or
-  "HTTP/1.1". Unlike "req.ver", it can be used in both request, response, and
-  logs because it relies on a persistent flag.
+  This extracts the request's HTTP version and returns it with the format
+  "HTTP/<major>.<minor>". It can be used in both request, response, and logs
+  because it relies on a persistent information. If the request version is not
+  valid, this sample fetch fails.
 
 capture.res.hdr(<idx>) : string
   This extracts the content of the header captured by the "capture response
@@ -26918,9 +26919,10 @@ capture.res.hdr(<idx>) : string
   See also: "capture response header"
 
 capture.res.ver : string
-  This extracts the response's HTTP version and returns either "HTTP/1.0" or
-  "HTTP/1.1". Unlike "res.ver", it can be used in logs because it relies on a
-  persistent flag.
+  This extracts the response's HTTP version and returns it with the format
+  "HTTP/<major>.<minor>". It can be used in logs because it relies on a
+  persistent information. If the response version is not valid, this sample
+  fetch fails.
 
 cookie([<name>]) : string (deprecated)
   This extracts the last occurrence of the cookie name <name> on a "Cookie"
@@ -27271,16 +27273,14 @@ req.timer.tq : integer
 
 req.ver : string
 req_ver : string (deprecated)
-  Returns the version string from the HTTP request, for example "1.1". This can
-  be useful for ACL. For logs use the "%HV" logformat alias. Some predefined
-  ACL already check for versions 1.0 and 1.1.
+  Returns the version string from the HTTP request, with the format
+  "<major>.<minor>". This can be useful for ACL. Some predefined ACL already
+  check for common versions.  It can be used in both request, response, and
+  logs because it relies on a persistent information. If the request version is
+  not valid, this sample fetch fails.
 
   Common values are "1.0", "1.1", "2.0" or "3.0".
 
-  In the case of http/2 and http/3, the value is not extracted from the HTTP
-  version in the request line but is determined by the negotiated protocol
-  version.
-
   ACL derivatives :
     req.ver : exact string match
 
@@ -27484,8 +27484,9 @@ res.timer.hdr : integer
 
 res.ver : string
 resp_ver : string (deprecated)
-  Returns the version string from the HTTP response, for example "1.1". This
-  can be useful for logs, but is mostly there for ACL.
+  Returns the version string from the HTTP response, with the format
+  "<major>.<minor>". This can be useful for logs, but is mostly there for
+  ACL. If the response version is not valid, this sample fetch fails.
 
   It may be used in tcp-check based expect rules.
 
index eae140b3b633d643ed4783bf6e0d72b0534b525a..8ce9a38e51be44a176e76ea46c6f7a71a2721f74 100644 (file)
@@ -311,19 +311,33 @@ struct htx *smp_prefetch_htx(struct sample *smp, struct channel *chn, struct che
         * that further checks can rely on HTTP tests.
         */
        if (sl && msg->msg_state < HTTP_MSG_BODY) {
+               struct ist vsn;
+
                if (!(chn->flags & CF_ISRESP)) {
+                       vsn = htx_sl_req_vsn(sl);
                        txn->meth = sl->info.req.meth;
                        if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)
                                s->flags |= SF_REDIRECTABLE;
                }
                else {
+                       vsn = htx_sl_res_vsn(sl);
                        if (txn->status == -1)
                                txn->status = sl->info.res.status;
                        if (txn->server_status == -1)
                                txn->server_status = sl->info.res.status;
                }
-               if (sl->flags & HTX_SL_F_VER_11)
-                       msg->flags |= HTTP_MSGF_VER_11;
+
+               if ((sl->flags & HTX_SL_F_NOT_HTTP) || istlen(vsn) != 8) {
+                       /* Not an HTTP message */
+                       msg->vsn = 0;
+               }
+               else {
+                       char *ptr = istptr(vsn);
+
+                       msg->vsn = ((ptr[5] - '0') << 4) + (ptr[7] - '0');
+                       if (sl->flags & HTX_SL_F_VER_11)
+                               msg->flags |= HTTP_MSGF_VER_11;
+               }
        }
 
        /* everything's OK */
@@ -331,6 +345,37 @@ struct htx *smp_prefetch_htx(struct sample *smp, struct channel *chn, struct che
        return htx;
 }
 
+/* Get the HTTP version from <msg> or <htx> and append it into the chunk <chk>
+ * with the format "<major>.<minor>".
+ * It returns 0 if <msg> and <htx> are both NULL or if the version
+ * is not a valid HTTP version. Otherwise, it returns 1 (success).
+ *
+ * The version is retrieved from <msg>, if not NULL. Otherwise, it is retrieved
+ * from <htx>.
+ */
+static int get_msg_version(const struct http_msg *msg, const struct htx *htx, struct buffer *chk)
+{
+       if (msg) {
+               if (msg->vsn) {
+                       chunk_appendf(chk, "%d.%d", (msg->vsn & 0xf0) >> 4, msg->vsn & 0xf);
+                       return 1;
+               }
+       }
+       else if (htx) {
+               struct htx_sl *sl = http_get_stline(htx);
+               struct ist vsn = htx_sl_vsn(sl);
+
+               if (!(sl->flags & HTX_SL_F_NOT_HTTP) && istlen(vsn) == 8) {
+                       chunk_appendf(chk, "%d.%d", istptr(vsn)[5] - '0',  istptr(vsn)[7] - '0');
+                       return 1;
+               }
+       }
+
+       /* <msg> and <htx> are both NULL or not a valid HTTP version */
+       return 0;
+}
+
+
 /* This function fetches the method of current HTTP request and stores
  * it in the global pattern struct as a chunk. There are two possibilities :
  *   - if the method is known (not HTTP_METH_OTHER), its identifier is stored
@@ -374,56 +419,34 @@ static int smp_fetch_meth(const struct arg *args, struct sample *smp, const char
 
 static int smp_fetch_rqver(const struct arg *args, struct sample *smp, const char *kw, void *private)
 {
+       struct stream *s = smp->strm;
        struct channel *chn = SMP_REQ_CHN(smp);
        struct htx *htx = smp_prefetch_htx(smp, chn, NULL, 1);
-       struct htx_sl *sl;
-       char *ptr;
-       int len;
-
-       if (!htx)
-               return 0;
-
-       sl = http_get_stline(htx);
-       len = HTX_SL_REQ_VLEN(sl);
-       ptr = HTX_SL_REQ_VPTR(sl);
+       struct buffer *vsn = get_trash_chunk();
 
-       while ((len-- > 0) && (*ptr++ != '/'));
-       if (len <= 0)
+       if (!get_msg_version((s && s->txn) ? &s->txn->req : NULL, htx, vsn))
                return 0;
 
        smp->data.type = SMP_T_STR;
-       smp->data.u.str.area = ptr;
-       smp->data.u.str.data = len;
-
-       smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
+       smp->data.u.str = *vsn;
        return 1;
 }
 
 static int smp_fetch_stver(const struct arg *args, struct sample *smp, const char *kw, void *private)
 {
+       struct stream *s = smp->strm;
        struct channel *chn = SMP_RES_CHN(smp);
        struct check *check = objt_check(smp->sess->origin);
        struct htx *htx = smp_prefetch_htx(smp, chn, check, 1);
-       struct htx_sl *sl;
-       char *ptr;
-       int len;
-
-       if (!htx)
-               return 0;
-
-       sl = http_get_stline(htx);
-       len = HTX_SL_RES_VLEN(sl);
-       ptr = HTX_SL_RES_VPTR(sl);
+       struct buffer *vsn = get_trash_chunk();
 
-       while ((len-- > 0) && (*ptr++ != '/'));
-       if (len <= 0)
+       if (!get_msg_version((s && s->txn) ? &s->txn->rsp : NULL, htx, vsn))
                return 0;
 
        smp->data.type = SMP_T_STR;
-       smp->data.u.str.area = ptr;
-       smp->data.u.str.data = len;
+       smp->data.u.str = *vsn;
+       return 1;
 
-       smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
        return 1;
 }
 
@@ -1638,25 +1661,17 @@ static int smp_fetch_capture_req_uri(const struct arg *args, struct sample *smp,
  */
 static int smp_fetch_capture_req_ver(const struct arg *args, struct sample *smp, const char *kw, void *private)
 {
-       struct http_txn *txn;
-
-       if (!smp->strm)
-               return 0;
+       struct stream *s = smp->strm;
+       struct buffer *vsn;
 
-       txn = smp->strm->txn;
-       if (!txn || txn->req.msg_state < HTTP_MSG_BODY)
+       vsn = get_trash_chunk();
+       chunk_memcat(vsn, "HTTP/", 5);
+       if (!get_msg_version((s && s->txn) ? &s->txn->req : NULL, NULL, vsn))
                return 0;
 
-       if (txn->req.flags & HTTP_MSGF_VER_11)
-               smp->data.u.str.area = "HTTP/1.1";
-       else
-               smp->data.u.str.area = "HTTP/1.0";
-
-       smp->data.u.str.data = 8;
-       smp->data.type  = SMP_T_STR;
-       smp->flags = SMP_F_CONST;
+       smp->data.type = SMP_T_STR;
+       smp->data.u.str = *vsn;
        return 1;
-
 }
 
 /* Retrieves the HTTP version from the response (either 1.0 or 1.1) and emits it
@@ -1664,23 +1679,16 @@ static int smp_fetch_capture_req_ver(const struct arg *args, struct sample *smp,
  */
 static int smp_fetch_capture_res_ver(const struct arg *args, struct sample *smp, const char *kw, void *private)
 {
-       struct http_txn *txn;
-
-       if (!smp->strm)
-               return 0;
+       struct stream *s = smp->strm;
+       struct buffer *vsn;
 
-       txn = smp->strm->txn;
-       if (!txn || txn->rsp.msg_state < HTTP_MSG_BODY)
+       vsn = get_trash_chunk();
+       chunk_memcat(vsn, "HTTP/", 5);
+       if (!get_msg_version((s && s->txn) ? &s->txn->rsp : NULL, NULL, vsn))
                return 0;
 
-       if (txn->rsp.flags & HTTP_MSGF_VER_11)
-               smp->data.u.str.area = "HTTP/1.1";
-       else
-               smp->data.u.str.area = "HTTP/1.0";
-
-       smp->data.u.str.data = 8;
-       smp->data.type  = SMP_T_STR;
-       smp->flags = SMP_F_CONST;
+       smp->data.type = SMP_T_STR;
+       smp->data.u.str = *vsn;
        return 1;
 
 }
@@ -2333,8 +2341,8 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
        { "req_proto_http",     smp_fetch_proto_http,         0,                NULL,    SMP_T_BOOL, SMP_USE_HRQHP },
 
        /* HTTP version on the request path */
-       { "req.ver",            smp_fetch_rqver,              0,                NULL,    SMP_T_STR,  SMP_USE_HRQHV },
-       { "req_ver",            smp_fetch_rqver,              0,                NULL,    SMP_T_STR,  SMP_USE_HRQHV },
+       { "req.ver",            smp_fetch_rqver,              0,                NULL,    SMP_T_STR,  SMP_USE_HRQHP },
+       { "req_ver",            smp_fetch_rqver,              0,                NULL,    SMP_T_STR,  SMP_USE_HRQHP },
 
        { "req.body",           smp_fetch_body,               0,                NULL,    SMP_T_BIN,  SMP_USE_HRQHV },
        { "req.body_len",       smp_fetch_body_len,           0,                NULL,    SMP_T_SINT, SMP_USE_HRQHV },
@@ -2345,8 +2353,8 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
        { "req.hdrs_bin",       smp_fetch_hdrs_bin,           0,                NULL,    SMP_T_BIN,  SMP_USE_HRQHV },
 
        /* HTTP version on the response path */
-       { "res.ver",            smp_fetch_stver,              0,                NULL,    SMP_T_STR,  SMP_USE_HRSHV },
-       { "resp_ver",           smp_fetch_stver,              0,                NULL,    SMP_T_STR,  SMP_USE_HRSHV },
+       { "res.ver",            smp_fetch_stver,              0,                NULL,    SMP_T_STR,  SMP_USE_HRSHP },
+       { "resp_ver",           smp_fetch_stver,              0,                NULL,    SMP_T_STR,  SMP_USE_HRSHP },
 
        { "res.body",           smp_fetch_body,               0,                NULL,    SMP_T_BIN,  SMP_USE_HRSHV },
        { "res.body_len",       smp_fetch_body_len,           0,                NULL,    SMP_T_SINT, SMP_USE_HRSHV },