Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 54 additions & 61 deletions vlib/net/http/h2_server.v
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ fn (mut c H2ServerConn) serve(mut handler Handler) ! {
// nearly all of its time blocked in a frame read, so per-frame mark/unmark
// only added shared-lock contention (and an O(n) list scan) on the hot
// path. On shutdown, close_idle still interrupts the reader by shutting the
// fd down; an h2 request in flight when the server stops is interrupted,
// which is acceptable at shutdown and is not relied on by any caller (the
// fd down; an h2 request in flight when the server stops is interrupted —
// including response writes, which may be truncated mid-DATA-frame. This
// is acceptable at shutdown and is not relied on by any caller (the
// graceful "wait for active request" guarantee is HTTP/1.1-only).
tracked := c.should_track_idle_read()
if tracked && !c.idle_conns.mark_idle(c.idle_handle) {
Expand Down Expand Up @@ -139,28 +140,8 @@ fn (mut c H2ServerConn) dispatch_frame(frame H2Frame, mut handler Handler) ! {
}
}
match frame {
H2SettingsFrame {
if !frame.ack {
c.apply_settings(frame.settings)
c.send_frame(H2SettingsFrame{
ack: true
})!
}
}
H2PingFrame {
if !frame.ack {
c.send_frame(H2PingFrame{
ack: true
data: frame.data
})!
}
}
H2WindowUpdateFrame {
if frame.stream_id == 0 {
c.send_window += i64(frame.window_size_increment)
} else if mut s := c.streams[frame.stream_id] {
s.send_window += i64(frame.window_size_increment)
}
H2SettingsFrame, H2PingFrame, H2WindowUpdateFrame {
c.handle_control_frame(frame)!
}
H2GoawayFrame {
c.closing = true
Expand Down Expand Up @@ -190,7 +171,41 @@ fn (mut c H2ServerConn) dispatch_frame(frame H2Frame, mut handler Handler) ! {
}
}

fn (mut c H2ServerConn) apply_settings(settings []H2Setting) {
// handle_control_frame services SETTINGS, PING, and WINDOW_UPDATE frames that
// may arrive at any point in the session, including while a response write is
// blocked in send_body waiting for flow-control credit. Both dispatch_frame and
// pump_for_window delegate to this function so the logic — and any validation
// errors — exist in exactly one place.
fn (mut c H2ServerConn) handle_control_frame(frame H2Frame) ! {
match frame {
H2SettingsFrame {
if !frame.ack {
c.apply_settings(frame.settings)!
c.send_frame(H2SettingsFrame{
ack: true
})!
}
}
H2PingFrame {
if !frame.ack {
c.send_frame(H2PingFrame{
ack: true
data: frame.data
})!
}
}
H2WindowUpdateFrame {
if frame.stream_id == 0 {
c.send_window += i64(frame.window_size_increment)
} else if mut s := c.streams[frame.stream_id] {
s.send_window += i64(frame.window_size_increment)
}
}
else {}
}
}

fn (mut c H2ServerConn) apply_settings(settings []H2Setting) ! {
for s in settings {
match s.id {
h2_settings_header_table_size {
Expand All @@ -203,8 +218,12 @@ fn (mut c H2ServerConn) apply_settings(settings []H2Setting) {
c.peer.max_concurrent_streams = s.value
}
h2_settings_initial_window_size {
// RFC 7540 Section 6.9.2: a change to the initial window size
// adjusts the send window of every active stream by the delta.
// RFC 7540 §6.5.3: values above 2^31-1 are a FLOW_CONTROL_ERROR.
if s.value > u32(0x7fff_ffff) {
return error('h2 server: SETTINGS_INITIAL_WINDOW_SIZE ${s.value} exceeds 2^31-1 (FLOW_CONTROL_ERROR)')
Comment thread
JalonSolov marked this conversation as resolved.
}
// RFC 7540 §6.9.2: a change to the initial window size adjusts
// the send window of every active stream by the delta.
delta := i64(s.value) - i64(c.peer.initial_window_size)
c.peer.initial_window_size = s.value
for _, mut st in c.streams {
Expand Down Expand Up @@ -455,44 +474,18 @@ fn (c &H2ServerConn) stream_send_window(stream_id u32) i64 {
}

// pump_for_window reads one frame while a response is blocked on flow control,
// servicing connection-level frames (SETTINGS / PING / WINDOW_UPDATE) and a
// RST_STREAM for the stream being written.
// servicing control frames (SETTINGS / PING / WINDOW_UPDATE) via
// handle_control_frame and aborting on RST_STREAM for the active stream.
fn (mut c H2ServerConn) pump_for_window(stream_id u32) ! {
frame := c.read_frame()!
match frame {
H2SettingsFrame {
if !frame.ack {
c.apply_settings(frame.settings)
c.send_frame(H2SettingsFrame{
ack: true
})!
}
}
H2PingFrame {
if !frame.ack {
c.send_frame(H2PingFrame{
ack: true
data: frame.data
})!
}
}
H2WindowUpdateFrame {
if frame.stream_id == 0 {
c.send_window += i64(frame.window_size_increment)
} else if mut s := c.streams[frame.stream_id] {
s.send_window += i64(frame.window_size_increment)
}
}
H2RstStreamFrame {
if frame.stream_id == stream_id {
return error('h2 server: stream reset by peer while writing response')
}
}
else {
// With SETTINGS_MAX_CONCURRENT_STREAMS=1 no other stream frames are
// expected mid-response; ignore anything else defensively.
c.handle_control_frame(frame)!
if frame is H2RstStreamFrame {
if frame.stream_id == stream_id {
return error('h2 server: stream reset by peer while writing response')
}
}
// With SETTINGS_MAX_CONCURRENT_STREAMS=1 no other stream frames are
// expected mid-response; ignore anything else defensively.
}

fn (mut c H2ServerConn) send_window_update(stream_id u32, inc u32) ! {
Expand Down
13 changes: 12 additions & 1 deletion vlib/net/http/server_tls_notd_use_openssl.v
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,24 @@ fn (mut w TlsHandlerWorker) process_requests() {
}

fn (mut w TlsHandlerWorker) handle_conn(mut conn mbedtls.SSLConn) {
// For H2 connections, serve_h2_conn_with_idle_tracker's serve() owns the
// mark_idle/unmark_idle lifetime (it marks idle once and unmarks in its
// own defer). Calling unmark_idle here a second time would race: after
// serve()'s defer fires the OS can recycle conn.handle for a new
// connection that has already called mark_idle, and this stale unmark
// would silently evict it, preventing close_idle from ever shutting it
// down and leaking the reader goroutine.
mut is_h2 := false
defer {
w.idle_conns.unmark_idle(conn.handle)
if !is_h2 {
w.idle_conns.unmark_idle(conn.handle)
}
conn.shutdown() or {}
}
// If the TLS handshake negotiated HTTP/2 via ALPN, switch to the HTTP/2
// driver; otherwise fall through to the existing HTTP/1.1 path unchanged.
if conn.negotiated_alpn() == 'h2' {
is_h2 = true
serve_h2_conn_with_idle_tracker(mut conn, mut w.handler, w.idle_conns, conn.handle) or {
$if debug {
eprintln('h2 server error: ${err}')
Expand Down
Loading