]> git.kaiwu.me - nginx.git/commitdiff
HTTP/2: handle PRIORITY_UPDATE frames
authorAndrew Clayton <a.clayton@nginx.com>
Wed, 23 Sep 2026 20:17:33 +0000 (21:17 +0100)
committerAndrew Clayton <a.clayton@nginx.com>
Mon, 28 Sep 2026 21:01:20 +0000 (22:01 +0100)
Implement the RFC 9218 PRIORITY_UPDATE frame (type 0x10).  The frame is
sent on stream 0 and carries a prioritized stream id and a Priority
Field Value; it is validated for framing errors and dispatched from
ngx_http_v2_state_head().

A PRIORITY_UPDATE conveys a complete set of parameters (RFC 9218,
Section 7): omitted parameters reset to their defaults, so the value
replaces any earlier signal.  A frame for a stream that already exists
is applied at once.  A frame for a not-yet-created stream is buffered on
its node until the HEADERS frame arrives; the node is parked in
h2c->closed and, while the RFC 7540 tree is still present, attached to
the root as is done for a PRIORITY frame with a zero dependency.  The
number of buffered signals is bounded by the node reuse budget.

A malformed value is logged and ignored, leaving the current priority
untouched.  A value that does not fit the state buffer while the frame
is fragmented is skipped.

The signals are only collected here; the scheduler still uses the
RFC 7540 dependency tree until the switch to RFC 9218.

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

index ebf724d8eee5d1cb6410dc54e215a21edadec8b9..4342ba2622b12e22cec19e65c2f97799619229f5 100644 (file)
@@ -85,6 +85,8 @@ static u_char *ngx_http_v2_handle_continuation(ngx_http_v2_connection_t *h2c,
     u_char *pos, u_char *end, ngx_http_v2_handler_pt handler);
 static u_char *ngx_http_v2_state_priority(ngx_http_v2_connection_t *h2c,
     u_char *pos, u_char *end);
+static u_char *ngx_http_v2_state_priority_update(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end);
 static u_char *ngx_http_v2_state_rst_stream(ngx_http_v2_connection_t *h2c,
     u_char *pos, u_char *end);
 static u_char *ngx_http_v2_state_settings(ngx_http_v2_connection_t *h2c,
@@ -907,6 +909,11 @@ ngx_http_v2_state_head(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end)
                    type, h2c->state.flags, h2c->state.length, h2c->state.sid);
 
     if (type >= NGX_HTTP_V2_FRAME_STATES) {
+
+        if (type == NGX_HTTP_V2_PRIORITY_UPDATE_FRAME) {
+            return ngx_http_v2_state_priority_update(h2c, pos, end);
+        }
+
         ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
                       "client sent frame with unknown type %ui", type);
         return ngx_http_v2_state_skip(h2c, pos, end);
@@ -1342,6 +1349,20 @@ ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos,
         ngx_http_v2_set_dependency(h2c, node, depend, excl);
     }
 
+    /* a PRIORITY_UPDATE may have arrived before HEADERS */
+
+    if (node->priority_set) {
+        stream->priority.client = node->priority;
+        node->priority_set = 0;
+
+        ngx_http_priority_state_update(&stream->priority);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                       "http2 buffered priority: u=%ud i=%ud",
+                       stream->priority.effective.urgency,
+                       stream->priority.effective.incremental);
+    }
+
     clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx,
                                         ngx_http_core_module);
 
@@ -2066,6 +2087,127 @@ ngx_http_v2_state_priority(ngx_http_v2_connection_t *h2c, u_char *pos,
 }
 
 
+static u_char *
+ngx_http_v2_state_priority_update(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end)
+{
+    size_t                 len;
+    ngx_str_t              value;
+    ngx_uint_t             sid;
+    ngx_http_v2_node_t    *node;
+    ngx_http_priority_t    priority;
+    ngx_http_v2_stream_t  *stream;
+
+    if (h2c->state.sid != 0) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent PRIORITY_UPDATE frame with incorrect "
+                      "identifier");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
+    }
+
+    if (h2c->state.length < 4) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent PRIORITY_UPDATE frame with incorrect "
+                      "length %uz", h2c->state.length);
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    len = h2c->state.length;
+
+    if ((size_t) (end - pos) < len) {
+
+        if (len > NGX_HTTP_V2_STATE_BUFFER_SIZE) {
+            return ngx_http_v2_state_skip(h2c, pos, end);
+        }
+
+        return ngx_http_v2_state_save(h2c, pos, end,
+                                      ngx_http_v2_state_priority_update);
+    }
+
+    sid = ngx_http_v2_parse_sid(pos);
+
+    if (sid % 2 == 0) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent PRIORITY_UPDATE frame with incorrect "
+                      "prioritized stream %ui", sid);
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
+    }
+
+    value.data = pos + 4;
+    value.len = len - 4;
+
+    pos += len;
+
+    if (ngx_http_priority_parse(&value, &priority) != NGX_OK) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent PRIORITY_UPDATE frame with malformed value "
+                      "for stream %ui", sid);
+
+        return ngx_http_v2_state_complete(h2c, pos, end);
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                   "http2 PRIORITY_UPDATE frame sid:%ui u=%ud i=%ud",
+                   sid, priority.urgency, priority.incremental);
+
+    /*
+     * A PRIORITY_UPDATE conveys a complete set of parameters (RFC 9218,
+     * Section 7): omitted parameters reset to their defaults, so the result
+     * replaces any earlier signal rather than merging with it.
+     */
+
+    node = ngx_http_v2_get_node_by_id(h2c, sid, sid > h2c->last_sid);
+
+    if (sid > h2c->last_sid) {
+        /*
+         * The stream is not created yet; park the node in h2c->closed holding
+         * the buffered priority.  While the RFC 7540 tree is still present a
+         * parked node has to be part of it, so attach it to the root as is
+         * done for a PRIORITY frame with a zero dependency.
+         *
+         * RFC 9218, Section 7.1, bounds idle prioritized streams together with
+         * active ones and requires a connection error on overflow; here idle
+         * signals are bounded on their own by the node reuse budget and the
+         * oldest is dropped instead, which is deliberately more lenient.
+         */
+
+        if (node == NULL) {
+            return ngx_http_v2_connection_error(h2c,
+                                                NGX_HTTP_V2_INTERNAL_ERROR);
+        }
+
+        if (node->parent == NULL) {
+            h2c->closed_nodes++;
+
+        } else {
+            ngx_queue_remove(&node->reuse);
+        }
+
+        ngx_queue_insert_tail(&h2c->closed, &node->reuse);
+
+        if (node->parent == NULL) {
+            /* a node in the closed queue is expected to be in the tree */
+
+            node->weight = NGX_HTTP_V2_DEFAULT_WEIGHT;
+            ngx_http_v2_set_dependency(h2c, node, 0, 0);
+        }
+
+        node->priority = priority;
+        node->priority_set = 1;
+
+    } else if (node && node->stream) {
+        stream = node->stream;
+        stream->priority.client = priority;
+        ngx_http_priority_state_update(&stream->priority);
+    }
+
+    return ngx_http_v2_state_complete(h2c, pos, end);
+}
+
+
 static u_char *
 ngx_http_v2_state_rst_stream(ngx_http_v2_connection_t *h2c, u_char *pos,
     u_char *end)
@@ -3106,6 +3248,8 @@ ngx_http_v2_create_stream(ngx_http_v2_connection_t *h2c)
     stream->request = r;
     stream->connection = h2c;
 
+    ngx_http_priority_state_init(&stream->priority);
+
     h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module);
 
     stream->send_window = h2c->init_window;
index beb77a5c1db9eed659fe8719a0fe93e68ca144ad..e35c23c3e88d84e6e00fc152971986316869a4dd 100644 (file)
 #define NGX_HTTP_V2_FRAME_HEADER_SIZE    9
 
 /* frame types */
-#define NGX_HTTP_V2_DATA_FRAME           0x0
-#define NGX_HTTP_V2_HEADERS_FRAME        0x1
-#define NGX_HTTP_V2_PRIORITY_FRAME       0x2
-#define NGX_HTTP_V2_RST_STREAM_FRAME     0x3
-#define NGX_HTTP_V2_SETTINGS_FRAME       0x4
-#define NGX_HTTP_V2_PUSH_PROMISE_FRAME   0x5
-#define NGX_HTTP_V2_PING_FRAME           0x6
-#define NGX_HTTP_V2_GOAWAY_FRAME         0x7
-#define NGX_HTTP_V2_WINDOW_UPDATE_FRAME  0x8
-#define NGX_HTTP_V2_CONTINUATION_FRAME   0x9
+#define NGX_HTTP_V2_DATA_FRAME             0x00
+#define NGX_HTTP_V2_HEADERS_FRAME          0x01
+#define NGX_HTTP_V2_PRIORITY_FRAME         0x02
+#define NGX_HTTP_V2_RST_STREAM_FRAME       0x03
+#define NGX_HTTP_V2_SETTINGS_FRAME         0x04
+#define NGX_HTTP_V2_PUSH_PROMISE_FRAME     0x05
+#define NGX_HTTP_V2_PING_FRAME             0x06
+#define NGX_HTTP_V2_GOAWAY_FRAME           0x07
+#define NGX_HTTP_V2_WINDOW_UPDATE_FRAME    0x08
+#define NGX_HTTP_V2_CONTINUATION_FRAME     0x09
+#define NGX_HTTP_V2_PRIORITY_UPDATE_FRAME  0x10
 
 /* frame flags */
 #define NGX_HTTP_V2_NO_FLAG              0x00
@@ -183,6 +184,8 @@ struct ngx_http_v2_node_s {
     ngx_uint_t                       weight;
     double                           rel_weight;
     ngx_http_v2_stream_t            *stream;
+    ngx_http_priority_t              priority;
+    ngx_uint_t                       priority_set; /* unsigned priority_set:1 */
 };
 
 
@@ -212,6 +215,8 @@ struct ngx_http_v2_stream_s {
 
     ngx_array_t                     *cookies;
 
+    ngx_http_priority_state_t        priority;
+
     ngx_pool_t                      *pool;
 
     unsigned                         initialized:1;