]> git.kaiwu.me - nginx.git/commitdiff
HTTP/2: parse Priority request header
authorAndrew Clayton <a.clayton@nginx.com>
Tue, 22 Sep 2026 00:52:41 +0000 (01:52 +0100)
committerAndrew Clayton <a.clayton@nginx.com>
Mon, 28 Sep 2026 21:01:20 +0000 (22:01 +0100)
Parse the RFC 9218 Priority request header, an RFC 8941 Structured
Fields Dictionary such as "u=3, i".

The header lines are collected during header block processing and, once
the block is complete, combined into a single value and parsed once in
ngx_http_v2_run_request(), as RFC 8941, Section 4.2, requires all field
lines of the same field to be combined before parsing.  This mirrors how
cookies are handled.  The header is still passed through to the request
so it stays visible to variables and proxying.

A malformed field is ignored, leaving the defaults.  A PRIORITY_UPDATE
frame overrides the header (RFC 9218, Section 7): if a frame has already
been applied to the stream the header is not consulted.

Assisted-by: opencode:claude-opus-4.8
src/http/v2/ngx_http_v2.c
src/http/v2/ngx_http_v2.h

index 4342ba2622b12e22cec19e65c2f97799619229f5..093e6c54837f215a25fc277acdc08c1113c86cfb 100644 (file)
@@ -160,6 +160,9 @@ static ngx_int_t ngx_http_v2_construct_request_line(ngx_http_request_t *r);
 static ngx_int_t ngx_http_v2_cookie(ngx_http_request_t *r,
     ngx_http_v2_header_t *header);
 static ngx_int_t ngx_http_v2_construct_cookie_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_v2_priority(ngx_http_request_t *r,
+    ngx_http_v2_header_t *header);
+static ngx_int_t ngx_http_v2_process_priority(ngx_http_request_t *r);
 static ngx_int_t ngx_http_v2_construct_host_header(ngx_http_request_t *r);
 static void ngx_http_v2_run_request(ngx_http_request_t *r);
 static ngx_int_t ngx_http_v2_process_request_body(ngx_http_request_t *r,
@@ -1354,6 +1357,7 @@ ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos,
     if (node->priority_set) {
         stream->priority.client = node->priority;
         node->priority_set = 0;
+        stream->priority_update = 1;
 
         ngx_http_priority_state_update(&stream->priority);
 
@@ -1741,6 +1745,7 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos,
     ngx_http_core_main_conf_t  *cmcf;
 
     static ngx_str_t cookie = ngx_string("cookie");
+    static ngx_str_t priority = ngx_string("priority");
 
     header = &h2c->state.header;
 
@@ -1839,6 +1844,15 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos,
         }
     }
 
+    if (header->name.len == priority.len
+        && ngx_memcmp(header->name.data, priority.data, priority.len) == 0)
+    {
+        if (ngx_http_v2_priority(r, header) != NGX_OK) {
+            return ngx_http_v2_connection_error(h2c,
+                                                NGX_HTTP_V2_INTERNAL_ERROR);
+        }
+    }
+
     if (header->name.len == cookie.len
         && ngx_memcmp(header->name.data, cookie.data, cookie.len) == 0)
     {
@@ -2201,6 +2215,7 @@ ngx_http_v2_state_priority_update(ngx_http_v2_connection_t *h2c, u_char *pos,
     } else if (node && node->stream) {
         stream = node->stream;
         stream->priority.client = priority;
+        stream->priority_update = 1;
         ngx_http_priority_state_update(&stream->priority);
     }
 
@@ -3904,6 +3919,111 @@ ngx_http_v2_construct_cookie_header(ngx_http_request_t *r)
 }
 
 
+static ngx_int_t
+ngx_http_v2_priority(ngx_http_request_t *r, ngx_http_v2_header_t *header)
+{
+    ngx_str_t    *val;
+    ngx_array_t  *priorities;
+
+    priorities = r->stream->priorities;
+
+    if (priorities == NULL) {
+        priorities = ngx_array_create(r->pool, 1, sizeof(ngx_str_t));
+        if (priorities == NULL) {
+            return NGX_ERROR;
+        }
+
+        r->stream->priorities = priorities;
+    }
+
+    val = ngx_array_push(priorities);
+    if (val == NULL) {
+        return NGX_ERROR;
+    }
+
+    val->len = header->value.len;
+    val->data = header->value.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_process_priority(ngx_http_request_t *r)
+{
+    u_char               *p;
+    size_t                len;
+    ngx_str_t             value, *vals;
+    ngx_uint_t            i;
+    ngx_array_t          *priorities;
+    ngx_http_priority_t   priority;
+
+    priorities = r->stream->priorities;
+
+    if (priorities == NULL) {
+        return NGX_OK;
+    }
+
+    /*
+     * RFC 9218, Section 7: a PRIORITY_UPDATE frame overrides the Priority
+     * header field, so ignore the header once a frame has been applied.
+     */
+
+    if (r->stream->priority_update) {
+        return NGX_OK;
+    }
+
+    vals = priorities->elts;
+
+    if (priorities->nelts == 1) {
+        value = vals[0];
+
+    } else {
+
+        /* combine the field lines into a single value, "v1, v2, ..." */
+
+        len = 0;
+
+        for (i = 0; i < priorities->nelts; i++) {
+            len += vals[i].len + 2;
+        }
+
+        len -= 2;
+
+        p = ngx_pnalloc(r->pool, len);
+        if (p == NULL) {
+            ngx_http_v2_close_stream(r->stream,
+                                     NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return NGX_ERROR;
+        }
+
+        value.data = p;
+        value.len = len;
+
+        for (i = 0; /* void */ ; i++) {
+            p = ngx_cpymem(p, vals[i].data, vals[i].len);
+
+            if (i == priorities->nelts - 1) {
+                break;
+            }
+
+            *p++ = ','; *p++ = ' ';
+        }
+    }
+
+    if (ngx_http_priority_parse(&value, &priority) != NGX_OK) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http2 ignoring malformed priority header");
+        return NGX_OK;
+    }
+
+    r->stream->priority.client = priority;
+    ngx_http_priority_state_update(&r->stream->priority);
+
+    return NGX_OK;
+}
+
+
 static ngx_int_t
 ngx_http_v2_construct_host_header(ngx_http_request_t *r)
 {
@@ -3980,6 +4100,10 @@ ngx_http_v2_run_request(ngx_http_request_t *r)
         goto failed;
     }
 
+    if (ngx_http_v2_process_priority(r) != NGX_OK) {
+        goto failed;
+    }
+
     r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
 
     if (r->headers_in.connection) {
index e35c23c3e88d84e6e00fc152971986316869a4dd..4106ce2969ae33b3de82b57b374606f2e2f7ea9e 100644 (file)
@@ -214,6 +214,7 @@ struct ngx_http_v2_stream_s {
     ngx_queue_t                      queue;
 
     ngx_array_t                     *cookies;
+    ngx_array_t                     *priorities;
 
     ngx_http_priority_state_t        priority;
 
@@ -228,6 +229,7 @@ struct ngx_http_v2_stream_s {
     unsigned                         rst_sent:1;
     unsigned                         no_flow_control:1;
     unsigned                         skip_data:1;
+    unsigned                         priority_update:1;
 };