v4 / vlib / net / http / h2_server.v
592 lines · 559 sloc · 18.21 KB · 89607c731290ee2c148c11541f73c40b23b00481
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module http
5
6// This file implements a minimal server-side HTTP/2 connection driver on top
7// of the existing framing (h2_frame.v) and HPACK (h2_hpack.v) layers. It is
8// intentionally serial: SETTINGS_MAX_CONCURRENT_STREAMS is advertised as 1,
9// so the peer sends one request at a time. Concurrent stream handling and
10// background writers are a planned follow-up.
11
12// h2_server_max_concurrent_streams is the value advertised in our SETTINGS;
13// the peer may not open more streams than this at once.
14const h2_server_max_concurrent_streams = u32(1)
15
16// h2_server_default_window is the default initial flow-control window
17// (RFC 7540 Section 6.9.2).
18const h2_server_default_window = u32(65535)
19
20// h2_server_max_request_body caps the in-memory request body the server will
21// accept before responding with a stream error. Bodies larger than this are
22// rejected with RST_STREAM(REFUSED_STREAM).
23const h2_server_max_request_body = 8 * 1024 * 1024
24
25// H2ServerStream holds the state of one in-flight server-side stream.
26struct H2ServerStream {
27mut:
28 id u32
29 hpack_block []u8 // assembled HEADERS + CONTINUATION fragments
30 headers []H2HeaderField
31 body []u8
32 end_headers bool
33 end_stream bool
34 headers_done bool // set once we've decoded the HPACK block
35 send_window i64
36}
37
38// H2ServerConn drives one server-side HTTP/2 connection over a transport.
39struct H2ServerConn {
40mut:
41 transport H2Transport
42 encoder H2HpackEncoder
43 decoder H2HpackDecoder
44 peer H2PeerSettings
45 rbuf []u8
46 send_window i64 = i64(h2_server_default_window)
47 streams map[u32]&H2ServerStream
48 last_stream_id u32
49 awaiting_cont u32 // non-zero when mid-CONTINUATION on this stream
50 closing bool
51 idle_conns &TlsIdleConnTracker = unsafe { nil }
52 idle_handle int
53}
54
55// serve_h2_conn drives a single HTTP/2 server-side connection until the
56// transport closes or a protocol error forces a GOAWAY. `handler` is invoked
57// once per fully-received request stream.
58fn serve_h2_conn(mut transport H2Transport, mut handler Handler) ! {
59 serve_h2_conn_with_idle_tracker(mut transport, mut handler, unsafe { nil }, 0)!
60}
61
62fn serve_h2_conn_with_idle_tracker(mut transport H2Transport, mut handler Handler, idle_conns &TlsIdleConnTracker, idle_handle int) ! {
63 mut c := &H2ServerConn{
64 transport: transport
65 idle_conns: idle_conns
66 idle_handle: idle_handle
67 }
68 c.serve(mut handler) or {
69 // Best-effort GOAWAY before bailing. Skip if one was already sent by
70 // the error path (e.g. apply_settings sends FLOW_CONTROL_ERROR).
71 if !c.closing {
72 c.send_goaway(.protocol_error, err.msg()) or {}
73 }
74 return err
75 }
76}
77
78fn (mut c H2ServerConn) serve(mut handler Handler) ! {
79 // Register the connection with the idle tracker once for its whole
80 // lifetime, instead of around every frame read. The reader thread spends
81 // nearly all of its time blocked in a frame read, so per-frame mark/unmark
82 // only added shared-lock contention (and an O(n) list scan) on the hot
83 // path. On shutdown, close_idle still interrupts the reader by shutting the
84 // fd down; an h2 request in flight when the server stops is interrupted —
85 // including response writes, which may be truncated mid-DATA-frame. This
86 // is acceptable at shutdown and is not relied on by any caller (the
87 // graceful "wait for active request" guarantee is HTTP/1.1-only).
88 tracked := c.should_track_idle_read()
89 if tracked && !c.idle_conns.mark_idle(c.idle_handle) {
90 // The server is already shutting down; do not start serving.
91 return
92 }
93 defer {
94 if tracked {
95 c.idle_conns.unmark_idle(c.idle_handle)
96 }
97 }
98 c.read_client_preface()!
99 c.send_initial_settings()!
100 for !c.closing {
101 frame := c.read_frame() or {
102 // Treat a clean transport close as end of session.
103 return
104 }
105 c.dispatch_frame(frame, mut handler)!
106 }
107}
108
109fn (mut c H2ServerConn) should_track_idle_read() bool {
110 return c.idle_handle > 0 && c.idle_conns != unsafe { nil }
111}
112
113fn (mut c H2ServerConn) read_client_preface() ! {
114 c.fill_at_least(h2_client_preface.len)!
115 got := c.rbuf[..h2_client_preface.len].bytestr()
116 if got != h2_client_preface {
117 return error('h2 server: invalid connection preface')
118 }
119 c.rbuf = c.rbuf[h2_client_preface.len..].clone()
120}
121
122fn (mut c H2ServerConn) send_initial_settings() ! {
123 c.send_frame(H2SettingsFrame{
124 settings: [
125 H2Setting{h2_settings_enable_push, 0},
126 H2Setting{h2_settings_max_concurrent_streams, h2_server_max_concurrent_streams},
127 H2Setting{h2_settings_initial_window_size, h2_server_default_window},
128 H2Setting{h2_settings_max_frame_size, h2_default_max_frame_size},
129 ]
130 })!
131}
132
133fn (mut c H2ServerConn) dispatch_frame(frame H2Frame, mut handler Handler) ! {
134 // RFC 7540 §6.10: once a HEADERS or PUSH_PROMISE without END_HEADERS is
135 // seen, the next frame must be a CONTINUATION on the same stream.
136 if c.awaiting_cont != 0 {
137 if frame is H2ContinuationFrame {
138 if frame.stream_id != c.awaiting_cont {
139 return error('h2 server: CONTINUATION on the wrong stream')
140 }
141 } else {
142 return error('h2 server: expected CONTINUATION after HEADERS without END_HEADERS')
143 }
144 }
145 match frame {
146 H2SettingsFrame, H2PingFrame, H2WindowUpdateFrame {
147 c.handle_control_frame(frame)!
148 }
149 H2GoawayFrame {
150 c.closing = true
151 }
152 H2RstStreamFrame {
153 c.streams.delete(frame.stream_id)
154 }
155 H2PriorityFrame {
156 // Priority is advisory; ignore.
157 }
158 H2HeadersFrame {
159 c.on_headers(frame, mut handler)!
160 }
161 H2ContinuationFrame {
162 c.on_continuation(frame, mut handler)!
163 }
164 H2DataFrame {
165 c.on_data(frame, mut handler)!
166 }
167 H2PushPromiseFrame {
168 // Clients should not push. Treat as a protocol error.
169 return error('h2 server: PUSH_PROMISE from client')
170 }
171 H2UnknownFrame {
172 // RFC 7540 §4.1: ignore unknown frame types.
173 }
174 }
175}
176
177// handle_control_frame services SETTINGS, PING, and WINDOW_UPDATE frames that
178// may arrive at any point in the session, including while a response write is
179// blocked in send_body waiting for flow-control credit. Both dispatch_frame and
180// pump_for_window delegate to this function so the logic — and any validation
181// errors — exist in exactly one place.
182fn (mut c H2ServerConn) handle_control_frame(frame H2Frame) ! {
183 match frame {
184 H2SettingsFrame {
185 if !frame.ack {
186 c.apply_settings(frame.settings)!
187 c.send_frame(H2SettingsFrame{
188 ack: true
189 })!
190 }
191 }
192 H2PingFrame {
193 if !frame.ack {
194 c.send_frame(H2PingFrame{
195 ack: true
196 data: frame.data
197 })!
198 }
199 }
200 H2WindowUpdateFrame {
201 if frame.stream_id == 0 {
202 c.send_window += i64(frame.window_size_increment)
203 } else if mut s := c.streams[frame.stream_id] {
204 s.send_window += i64(frame.window_size_increment)
205 }
206 }
207 else {}
208 }
209}
210
211fn (mut c H2ServerConn) apply_settings(settings []H2Setting) ! {
212 for s in settings {
213 match s.id {
214 h2_settings_header_table_size {
215 // Constrains our *encoder* (the server's response-header encoder
216 // whose output the client decodes with its advertised table size).
217 c.peer.header_table_size = s.value
218 c.encoder.dyn_table.set_max_size(int(s.value))
219 c.encoder.pending_max_table_size = int(s.value)
220 }
221 h2_settings_enable_push {
222 c.peer.enable_push = s.value != 0
223 }
224 h2_settings_max_concurrent_streams {
225 c.peer.max_concurrent_streams = s.value
226 }
227 h2_settings_initial_window_size {
228 // RFC 7540 §6.5.3: values above 2^31-1 are a FLOW_CONTROL_ERROR,
229 // not a PROTOCOL_ERROR; send the correct code before unwinding.
230 if s.value > u32(0x7fff_ffff) {
231 c.send_goaway(.flow_control_error,
232 'SETTINGS_INITIAL_WINDOW_SIZE ${s.value} exceeds 2^31-1') or {}
233 return error('h2 server: SETTINGS_INITIAL_WINDOW_SIZE ${s.value} exceeds 2^31-1 (FLOW_CONTROL_ERROR)')
234 }
235 // RFC 7540 §6.9.2: validate ALL stream windows before mutating any
236 // so that a delta overflow on stream N does not leave streams 1..N-1
237 // partially updated. Send FLOW_CONTROL_ERROR (not PROTOCOL_ERROR).
238 delta := i64(s.value) - i64(c.peer.initial_window_size)
239 for _, st in c.streams {
240 if st.send_window + delta > i64(0x7fff_ffff) {
241 c.send_goaway(.flow_control_error,
242 'SETTINGS_INITIAL_WINDOW_SIZE delta overflows stream ${st.id} send window') or {}
243 return error('h2 server: SETTINGS_INITIAL_WINDOW_SIZE delta overflows stream ${st.id} send window (RFC 7540 §6.9.2 FLOW_CONTROL_ERROR)')
244 }
245 }
246 c.peer.initial_window_size = s.value
247 for _, mut st in c.streams {
248 st.send_window += delta
249 }
250 }
251 h2_settings_max_frame_size {
252 // RFC 7540 §6.5.2: valid range is 2^14 (16384)..2^24-1 inclusive.
253 if s.value < h2_default_max_frame_size || s.value > h2_max_max_frame_size {
254 return error('h2 server: SETTINGS_MAX_FRAME_SIZE ${s.value} out of range [16384, 16777215]')
255 }
256 c.peer.max_frame_size = s.value
257 }
258 h2_settings_max_header_list_size {
259 c.peer.max_header_list_size = s.value
260 }
261 else {}
262 }
263 }
264}
265
266fn (mut c H2ServerConn) on_headers(frame H2HeadersFrame, mut handler Handler) ! {
267 // Stream ids from the client must be odd and strictly increasing.
268 if frame.stream_id & 1 == 0 || frame.stream_id <= c.last_stream_id {
269 return error('h2 server: invalid client stream id ${frame.stream_id}')
270 }
271 c.last_stream_id = frame.stream_id
272 mut s := &H2ServerStream{
273 id: frame.stream_id
274 hpack_block: frame.fragment.clone()
275 end_headers: frame.end_headers
276 end_stream: frame.end_stream
277 send_window: i64(c.peer.initial_window_size)
278 }
279 c.streams[frame.stream_id] = s
280 if !frame.end_headers {
281 c.awaiting_cont = frame.stream_id
282 return
283 }
284 c.finalize_headers(mut s, mut handler)!
285}
286
287fn (mut c H2ServerConn) on_continuation(frame H2ContinuationFrame, mut handler Handler) ! {
288 mut s := c.streams[frame.stream_id] or {
289 return error('h2 server: CONTINUATION for unknown stream ${frame.stream_id}')
290 }
291 if s.hpack_block.len + frame.fragment.len > h2_max_recv_header_block {
292 return error('h2 server: request header block exceeds ${h2_max_recv_header_block} bytes')
293 }
294 s.hpack_block << frame.fragment
295 if frame.end_headers {
296 s.end_headers = true
297 c.awaiting_cont = 0
298 c.finalize_headers(mut s, mut handler)!
299 }
300}
301
302fn (mut c H2ServerConn) finalize_headers(mut s H2ServerStream, mut handler Handler) ! {
303 s.headers = c.decoder.decode(s.hpack_block) or {
304 c.send_rst_stream(s.id, .compression_error)!
305 c.streams.delete(s.id)
306 return
307 }
308 s.headers_done = true
309 if s.end_stream {
310 c.run_request(mut s, mut handler)!
311 }
312}
313
314fn (mut c H2ServerConn) on_data(frame H2DataFrame, mut handler Handler) ! {
315 mut s := c.streams[frame.stream_id] or {
316 // DATA for an unknown stream (likely already RST'd); just drop and
317 // keep flow control consistent. Credit flow_size (full wire bytes
318 // including padding) per RFC 7540 §6.9.1.
319 if frame.flow_size > 0 {
320 c.send_window_update(0, u32(frame.flow_size))!
321 }
322 return
323 }
324 if !s.headers_done {
325 return error('h2 server: DATA before END_HEADERS')
326 }
327 if frame.data.len > 0 {
328 if s.body.len + frame.data.len > h2_server_max_request_body {
329 // Credit the connection window before resetting so the peer is
330 // not penalised for bytes it legitimately sent within its window.
331 c.send_window_update(0, u32(frame.flow_size)) or {}
332 c.send_rst_stream(s.id, .refused_stream)!
333 c.streams.delete(s.id)
334 return
335 }
336 s.body << frame.data
337 }
338 // Replenish flow control using flow_size (full wire payload including padding),
339 // per RFC 7540 §6.9.1. Credit unconditionally when flow_size>0: a padding-only
340 // DATA frame (data.len==0, flow_size>0) still consumes the peer's send window
341 // and must be credited back. (Formerly inside the data.len>0 block — padding-only
342 // frames leaked window silently.)
343 if frame.flow_size > 0 {
344 c.send_window_update(0, u32(frame.flow_size))!
345 c.send_window_update(s.id, u32(frame.flow_size))!
346 }
347 if frame.end_stream {
348 s.end_stream = true
349 c.run_request(mut s, mut handler)!
350 }
351}
352
353fn (mut c H2ServerConn) run_request(mut s H2ServerStream, mut handler Handler) ! {
354 req := c.build_request(s) or {
355 c.send_rst_stream(s.id, .protocol_error)!
356 c.streams.delete(s.id)
357 return
358 }
359 resp := handler.handle(req)
360 c.send_response(s.id, resp)!
361 c.streams.delete(s.id)
362}
363
364fn (mut c H2ServerConn) build_request(s &H2ServerStream) !Request {
365 mut req := Request{
366 version: .v2_0
367 header: new_header()
368 }
369 mut method := ''
370 mut path := ''
371 mut authority := ''
372 mut scheme := 'https'
373 for f in s.headers {
374 match f.name {
375 ':method' {
376 method = f.value
377 }
378 ':path' {
379 path = f.value
380 }
381 ':authority' {
382 authority = f.value
383 }
384 ':scheme' {
385 scheme = f.value
386 }
387 else {
388 if f.name.starts_with(':') {
389 return error('h2 server: unknown pseudo-header ${f.name}')
390 }
391 req.header.add_custom(f.name, f.value) or {}
392 }
393 }
394 }
395 if method == '' || path == '' {
396 return error('h2 server: missing :method or :path')
397 }
398 req.method = method_from_str(method)
399 if authority != '' && !req.header.contains(.host) {
400 req.header.add(.host, authority)
401 }
402 // Match the HTTP/1.1 path: req.url is the request-target (the :path
403 // pseudo-header), so handlers see the same shape on both transports.
404 _ = scheme // :scheme is parsed and discarded; handlers infer it from Host
405 req.url = path
406 req.data = s.body.bytestr()
407 req.host = authority
408 return req
409}
410
411fn (mut c H2ServerConn) send_response(stream_id u32, resp Response) ! {
412 status := if resp.status_code == 0 { 200 } else { resp.status_code }
413 mut fields := [H2HeaderField{':status', status.str()}]
414 for key in resp.header.keys() {
415 lkey := key.to_lower()
416 // Drop hop-by-hop headers; HTTP/2 forbids them (RFC 7540 §8.1.2.2).
417 if lkey in ['connection', 'keep-alive', 'transfer-encoding', 'upgrade', 'proxy-connection'] {
418 continue
419 }
420 for val in resp.header.custom_values(key) {
421 fields << H2HeaderField{lkey, val}
422 }
423 }
424 body := resp.body.bytes()
425 has_body := body.len > 0
426 block := c.encoder.encode(fields)
427 c.send_header_block(stream_id, block, !has_body)!
428 if has_body {
429 c.send_body(stream_id, body)!
430 }
431}
432
433fn (mut c H2ServerConn) send_header_block(stream_id u32, block []u8, end_stream bool) ! {
434 max := int(c.peer.max_frame_size)
435 if block.len <= max {
436 c.send_frame(H2HeadersFrame{
437 stream_id: stream_id
438 fragment: block
439 end_headers: true
440 end_stream: end_stream
441 })!
442 return
443 }
444 c.send_frame(H2HeadersFrame{
445 stream_id: stream_id
446 fragment: block[..max]
447 end_headers: false
448 end_stream: end_stream
449 })!
450 mut off := max
451 for off < block.len {
452 mut next := off + max
453 if next > block.len {
454 next = block.len
455 }
456 c.send_frame(H2ContinuationFrame{
457 stream_id: stream_id
458 fragment: block[off..next]
459 end_headers: next == block.len
460 })!
461 off = next
462 }
463}
464
465fn (mut c H2ServerConn) send_body(stream_id u32, body []u8) ! {
466 max := int(c.peer.max_frame_size)
467 mut off := 0
468 for off < body.len {
469 // Respect both the connection and per-stream send windows
470 // (RFC 7540 Section 6.9). When either is exhausted, read frames until
471 // the peer grows a window with WINDOW_UPDATE.
472 for c.send_window <= 0 || c.stream_send_window(stream_id) <= 0 {
473 c.pump_for_window(stream_id)!
474 }
475 avail := if c.send_window < c.stream_send_window(stream_id) {
476 c.send_window
477 } else {
478 c.stream_send_window(stream_id)
479 }
480 mut chunk := body.len - off
481 if chunk > max {
482 chunk = max
483 }
484 if i64(chunk) > avail {
485 chunk = int(avail)
486 }
487 next := off + chunk
488 c.send_frame(H2DataFrame{
489 stream_id: stream_id
490 data: body[off..next]
491 end_stream: next == body.len
492 })!
493 c.send_window -= i64(chunk)
494 if mut s := c.streams[stream_id] {
495 s.send_window -= i64(chunk)
496 }
497 off = next
498 }
499}
500
501// stream_send_window returns the current per-stream send window, or 0 if the
502// stream is gone.
503fn (c &H2ServerConn) stream_send_window(stream_id u32) i64 {
504 if s := c.streams[stream_id] {
505 return s.send_window
506 }
507 return 0
508}
509
510// pump_for_window reads one frame while a response is blocked on flow control,
511// servicing control frames (SETTINGS / PING / WINDOW_UPDATE) via
512// handle_control_frame and aborting on RST_STREAM for the active stream.
513fn (mut c H2ServerConn) pump_for_window(stream_id u32) ! {
514 frame := c.read_frame()!
515 c.handle_control_frame(frame)!
516 if frame is H2RstStreamFrame {
517 if frame.stream_id == stream_id {
518 return error('h2 server: stream reset by peer while writing response')
519 }
520 }
521 // With SETTINGS_MAX_CONCURRENT_STREAMS=1 no other stream frames are
522 // expected mid-response; ignore anything else defensively.
523}
524
525fn (mut c H2ServerConn) send_window_update(stream_id u32, inc u32) ! {
526 if inc == 0 {
527 return
528 }
529 c.send_frame(H2WindowUpdateFrame{
530 stream_id: stream_id
531 window_size_increment: inc
532 })!
533}
534
535fn (mut c H2ServerConn) send_rst_stream(stream_id u32, code H2ErrorCode) ! {
536 c.send_frame(H2RstStreamFrame{
537 stream_id: stream_id
538 error_code: u32(code)
539 })!
540}
541
542fn (mut c H2ServerConn) send_goaway(code H2ErrorCode, msg string) ! {
543 c.send_frame(H2GoawayFrame{
544 last_stream_id: c.last_stream_id
545 error_code: u32(code)
546 debug_data: msg.bytes()
547 })!
548 c.closing = true
549}
550
551fn (mut c H2ServerConn) read_frame() !H2Frame {
552 c.fill_at_least(h2_frame_header_len)!
553 header := h2_parse_frame_header(c.rbuf)!
554 // Check against our own advertised receive limit (sent in send_initial_settings).
555 // H2ServerConn always advertises h2_default_max_frame_size and never renegotiates
556 // it, so the constant is correct here. c.peer.max_frame_size is the client's
557 // receive limit (our outbound cap) and must not be used for inbound checking.
558 if header.length > h2_default_max_frame_size {
559 return error('h2 server: frame larger than SETTINGS_MAX_FRAME_SIZE (${header.length})')
560 }
561 total := h2_frame_header_len + int(header.length)
562 c.fill_at_least(total)!
563 frame := h2_parse_frame(header, c.rbuf[h2_frame_header_len..total])!
564 c.rbuf = c.rbuf[total..].clone()
565 return frame
566}
567
568fn (mut c H2ServerConn) fill_at_least(n int) ! {
569 for c.rbuf.len < n {
570 mut tmp := []u8{len: h2_conn_read_chunk}
571 got := c.transport.read(mut tmp)!
572 if got <= 0 {
573 return error('h2 server: connection closed by peer')
574 }
575 c.rbuf << tmp[..got]
576 }
577}
578
579fn (mut c H2ServerConn) send_frame(f H2Frame) ! {
580 c.write_all(f.encode())!
581}
582
583fn (mut c H2ServerConn) write_all(data []u8) ! {
584 mut sent := 0
585 for sent < data.len {
586 n := c.transport.write(data[sent..])!
587 if n <= 0 {
588 return error('h2 server: transport write returned ${n}')
589 }
590 sent += n
591 }
592}
593