vxx2 / vlib / net / http / h2_server.v
820 lines · 780 sloc · 26.3 KB · 02015a7ce111e4d0cfd5b5d78711ad0db7586936
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// The connection-specific header fields RFC 9113 §8.2.2 forbids in HTTP/2 (a
26// request carrying one is malformed; the server must never echo one in a
27// response) are the module-level `h2_conn_specific_headers` const defined in
28// h2_mux_conn.v — shared with the client path rather than redefined here.
29
30// h2_all_digits reports whether s is a non-empty run of ASCII digits. A value
31// that parses with `.int()` is NOT proof of a valid number — `'12x'.int()` is 12
32// — so wire-derived numerics (content-length) must be charset-checked first.
33fn h2_all_digits(s string) bool {
34 if s.len == 0 {
35 return false
36 }
37 for ch in s {
38 if ch < `0` || ch > `9` {
39 return false
40 }
41 }
42 return true
43}
44
45// h2_field_value_has_forbidden_octet reports whether a field value contains an
46// octet RFC 9113 §8.2.1 forbids: NUL (0x00), LF (0x0a), or CR (0x0d). Such a
47// value makes the request malformed and, if the request were forwarded to an
48// HTTP/1.x peer, would be a header-injection / request-smuggling vector.
49fn h2_field_value_has_forbidden_octet(value string) bool {
50 for ch in value {
51 if ch == 0 || ch == 0x0a || ch == 0x0d {
52 return true
53 }
54 }
55 return false
56}
57
58// h2_request_field_error returns a non-empty reason when a regular (non-pseudo)
59// request header field is malformed per RFC 9113 §8.2: names must be lowercase
60// and non-empty, values must not contain NUL/CR/LF, connection-specific fields
61// are forbidden, and TE may only carry the value "trailers". An empty return
62// means the field is valid.
63fn h2_request_field_error(name string, value string) string {
64 if name.len == 0 {
65 return 'empty header field name'
66 }
67 if name != name.to_lower() {
68 return 'uppercase header field name "${name}"'
69 }
70 if h2_field_value_has_forbidden_octet(value) {
71 return 'forbidden NUL/CR/LF octet in value of "${name}"'
72 }
73 if name in h2_conn_specific_headers {
74 return 'connection-specific header field "${name}"'
75 }
76 if name == 'te' && value.trim_space().to_lower() != 'trailers' {
77 return 'TE header field with a value other than "trailers"'
78 }
79 return ''
80}
81
82// h2_validate_request_pseudo enforces the RFC 9113 §8.1.2/§8.3 rules a request
83// header list must satisfy: only the request pseudo-headers, each at most once,
84// all appearing before any regular field, with :method, :path and :scheme
85// present; every regular field must pass h2_request_field_error. A non-ok return
86// means the request is malformed (a stream error at the call site).
87fn h2_validate_request_pseudo(headers []H2HeaderField) ! {
88 mut seen_regular := false
89 mut has_method := false
90 mut has_path := false
91 mut has_scheme := false
92 mut has_authority := false
93 for f in headers {
94 if f.name.starts_with(':') {
95 if seen_regular {
96 return error('pseudo-header "${f.name}" after a regular field')
97 }
98 if h2_field_value_has_forbidden_octet(f.value) {
99 return error('forbidden NUL/CR/LF octet in pseudo-header "${f.name}"')
100 }
101 match f.name {
102 ':method' {
103 if has_method {
104 return error('duplicate :method pseudo-header')
105 }
106 if f.value == '' {
107 return error('empty :method pseudo-header')
108 }
109 has_method = true
110 }
111 ':path' {
112 if has_path {
113 return error('duplicate :path pseudo-header')
114 }
115 // RFC 9113 §8.3.1: :path must not be empty for http/https.
116 if f.value == '' {
117 return error('empty :path pseudo-header')
118 }
119 has_path = true
120 }
121 ':scheme' {
122 if has_scheme {
123 return error('duplicate :scheme pseudo-header')
124 }
125 if f.value == '' {
126 return error('empty :scheme pseudo-header')
127 }
128 has_scheme = true
129 }
130 ':authority' {
131 if has_authority {
132 return error('duplicate :authority pseudo-header')
133 }
134 has_authority = true
135 }
136 else {
137 return error('unknown request pseudo-header "${f.name}"')
138 }
139 }
140 } else {
141 seen_regular = true
142 reason := h2_request_field_error(f.name, f.value)
143 if reason != '' {
144 return error(reason)
145 }
146 }
147 }
148 if !has_method || !has_path || !has_scheme {
149 return error('request omits a mandatory pseudo-header (:method/:path/:scheme)')
150 }
151}
152
153// H2ServerStream holds the state of one in-flight server-side stream.
154struct H2ServerStream {
155mut:
156 id u32
157 hpack_block []u8 // assembled HEADERS + CONTINUATION fragments
158 headers []H2HeaderField
159 body []u8
160 end_headers bool
161 end_stream bool
162 headers_done bool // set once we've decoded the HPACK block
163 send_window i64
164 trailer_block []u8 // assembled trailer HEADERS + CONTINUATION fragments
165 in_trailers bool // set once a trailer section (a 2nd HEADERS block) begins
166}
167
168// H2ServerConn drives one server-side HTTP/2 connection over a transport.
169struct H2ServerConn {
170mut:
171 transport H2Transport
172 encoder H2HpackEncoder
173 decoder H2HpackDecoder
174 peer H2PeerSettings
175 rbuf []u8
176 send_window i64 = i64(h2_server_default_window)
177 streams map[u32]&H2ServerStream
178 last_stream_id u32
179 awaiting_cont u32 // non-zero when mid-CONTINUATION on this stream
180 closing bool
181 idle_conns &TlsIdleConnTracker = unsafe { nil }
182 idle_handle int
183}
184
185// serve_h2_conn drives a single HTTP/2 server-side connection until the
186// transport closes or a protocol error forces a GOAWAY. `handler` is invoked
187// once per fully-received request stream.
188fn serve_h2_conn(mut transport H2Transport, mut handler Handler) ! {
189 serve_h2_conn_with_idle_tracker(mut transport, mut handler, unsafe { nil }, 0)!
190}
191
192fn serve_h2_conn_with_idle_tracker(mut transport H2Transport, mut handler Handler, idle_conns &TlsIdleConnTracker, idle_handle int) ! {
193 mut c := &H2ServerConn{
194 transport: transport
195 idle_conns: idle_conns
196 idle_handle: idle_handle
197 }
198 c.serve(mut handler) or {
199 // Best-effort GOAWAY before bailing. Skip if one was already sent by
200 // the error path (e.g. apply_settings sends FLOW_CONTROL_ERROR).
201 if !c.closing {
202 c.send_goaway(.protocol_error, err.msg()) or {}
203 }
204 return err
205 }
206}
207
208fn (mut c H2ServerConn) serve(mut handler Handler) ! {
209 // Register the connection with the idle tracker once for its whole
210 // lifetime, instead of around every frame read. The reader thread spends
211 // nearly all of its time blocked in a frame read, so per-frame mark/unmark
212 // only added shared-lock contention (and an O(n) list scan) on the hot
213 // path. On shutdown, close_idle still interrupts the reader by shutting the
214 // fd down; an h2 request in flight when the server stops is interrupted —
215 // including response writes, which may be truncated mid-DATA-frame. This
216 // is acceptable at shutdown and is not relied on by any caller (the
217 // graceful "wait for active request" guarantee is HTTP/1.1-only).
218 tracked := c.should_track_idle_read()
219 if tracked && !c.idle_conns.mark_idle(c.idle_handle) {
220 // The server is already shutting down; do not start serving.
221 return
222 }
223 defer {
224 if tracked {
225 c.idle_conns.unmark_idle(c.idle_handle)
226 }
227 }
228 c.read_client_preface()!
229 c.send_initial_settings()!
230 for !c.closing {
231 frame := c.read_frame() or {
232 // Treat a clean transport close as end of session.
233 return
234 }
235 c.dispatch_frame(frame, mut handler)!
236 }
237}
238
239fn (mut c H2ServerConn) should_track_idle_read() bool {
240 return c.idle_handle > 0 && c.idle_conns != unsafe { nil }
241}
242
243fn (mut c H2ServerConn) read_client_preface() ! {
244 c.fill_at_least(h2_client_preface.len)!
245 got := c.rbuf[..h2_client_preface.len].bytestr()
246 if got != h2_client_preface {
247 return error('h2 server: invalid connection preface')
248 }
249 c.rbuf = c.rbuf[h2_client_preface.len..].clone()
250}
251
252fn (mut c H2ServerConn) send_initial_settings() ! {
253 c.send_frame(H2SettingsFrame{
254 settings: [
255 H2Setting{h2_settings_enable_push, 0},
256 H2Setting{h2_settings_max_concurrent_streams, h2_server_max_concurrent_streams},
257 H2Setting{h2_settings_initial_window_size, h2_server_default_window},
258 H2Setting{h2_settings_max_frame_size, h2_default_max_frame_size},
259 ]
260 })!
261}
262
263fn (mut c H2ServerConn) dispatch_frame(frame H2Frame, mut handler Handler) ! {
264 // RFC 7540 §6.10: once a HEADERS or PUSH_PROMISE without END_HEADERS is
265 // seen, the next frame must be a CONTINUATION on the same stream.
266 if c.awaiting_cont != 0 {
267 if frame is H2ContinuationFrame {
268 if frame.stream_id != c.awaiting_cont {
269 return error('h2 server: CONTINUATION on the wrong stream')
270 }
271 } else {
272 return error('h2 server: expected CONTINUATION after HEADERS without END_HEADERS')
273 }
274 }
275 match frame {
276 H2SettingsFrame, H2PingFrame, H2WindowUpdateFrame {
277 c.handle_control_frame(frame)!
278 }
279 H2GoawayFrame {
280 c.closing = true
281 }
282 H2RstStreamFrame {
283 c.streams.delete(frame.stream_id)
284 }
285 H2PriorityFrame {
286 // Priority is advisory; ignore.
287 }
288 H2HeadersFrame {
289 c.on_headers(frame, mut handler)!
290 }
291 H2ContinuationFrame {
292 c.on_continuation(frame, mut handler)!
293 }
294 H2DataFrame {
295 c.on_data(frame, mut handler)!
296 }
297 H2PushPromiseFrame {
298 // Clients should not push. Treat as a protocol error.
299 return error('h2 server: PUSH_PROMISE from client')
300 }
301 H2UnknownFrame {
302 // RFC 7540 §4.1: ignore unknown frame types.
303 }
304 }
305}
306
307// handle_control_frame services SETTINGS, PING, and WINDOW_UPDATE frames that
308// may arrive at any point in the session, including while a response write is
309// blocked in send_body waiting for flow-control credit. Both dispatch_frame and
310// pump_for_window delegate to this function so the logic — and any validation
311// errors — exist in exactly one place.
312fn (mut c H2ServerConn) handle_control_frame(frame H2Frame) ! {
313 match frame {
314 H2SettingsFrame {
315 if !frame.ack {
316 c.apply_settings(frame.settings)!
317 c.send_frame(H2SettingsFrame{
318 ack: true
319 })!
320 }
321 }
322 H2PingFrame {
323 if !frame.ack {
324 c.send_frame(H2PingFrame{
325 ack: true
326 data: frame.data
327 })!
328 }
329 }
330 H2WindowUpdateFrame {
331 if frame.stream_id == 0 {
332 c.send_window += i64(frame.window_size_increment)
333 } else if mut s := c.streams[frame.stream_id] {
334 s.send_window += i64(frame.window_size_increment)
335 }
336 }
337 else {}
338 }
339}
340
341fn (mut c H2ServerConn) apply_settings(settings []H2Setting) ! {
342 for s in settings {
343 match s.id {
344 h2_settings_header_table_size {
345 // Constrains our *encoder* (the server's response-header encoder
346 // whose output the client decodes with its advertised table size).
347 c.peer.header_table_size = s.value
348 c.encoder.dyn_table.set_max_size(int(s.value))
349 c.encoder.pending_max_table_size = int(s.value)
350 }
351 h2_settings_enable_push {
352 c.peer.enable_push = s.value != 0
353 }
354 h2_settings_max_concurrent_streams {
355 c.peer.max_concurrent_streams = s.value
356 }
357 h2_settings_initial_window_size {
358 // RFC 7540 §6.5.3: values above 2^31-1 are a FLOW_CONTROL_ERROR,
359 // not a PROTOCOL_ERROR; send the correct code before unwinding.
360 if s.value > u32(0x7fff_ffff) {
361 c.send_goaway(.flow_control_error,
362 'SETTINGS_INITIAL_WINDOW_SIZE ${s.value} exceeds 2^31-1') or {}
363 return error('h2 server: SETTINGS_INITIAL_WINDOW_SIZE ${s.value} exceeds 2^31-1 (FLOW_CONTROL_ERROR)')
364 }
365 // RFC 7540 §6.9.2: validate ALL stream windows before mutating any
366 // so that a delta overflow on stream N does not leave streams 1..N-1
367 // partially updated. Send FLOW_CONTROL_ERROR (not PROTOCOL_ERROR).
368 delta := i64(s.value) - i64(c.peer.initial_window_size)
369 for _, st in c.streams {
370 if st.send_window + delta > i64(0x7fff_ffff) {
371 c.send_goaway(.flow_control_error,
372 'SETTINGS_INITIAL_WINDOW_SIZE delta overflows stream ${st.id} send window') or {}
373 return error('h2 server: SETTINGS_INITIAL_WINDOW_SIZE delta overflows stream ${st.id} send window (RFC 7540 §6.9.2 FLOW_CONTROL_ERROR)')
374 }
375 }
376 c.peer.initial_window_size = s.value
377 for _, mut st in c.streams {
378 st.send_window += delta
379 }
380 }
381 h2_settings_max_frame_size {
382 // RFC 7540 §6.5.2: valid range is 2^14 (16384)..2^24-1 inclusive.
383 if s.value < h2_default_max_frame_size || s.value > h2_max_max_frame_size {
384 return error('h2 server: SETTINGS_MAX_FRAME_SIZE ${s.value} out of range [16384, 16777215]')
385 }
386 c.peer.max_frame_size = s.value
387 }
388 h2_settings_max_header_list_size {
389 c.peer.max_header_list_size = s.value
390 }
391 else {}
392 }
393 }
394}
395
396fn (mut c H2ServerConn) on_headers(frame H2HeadersFrame, mut handler Handler) ! {
397 // A HEADERS block for a stream that is already open is a trailer section
398 // (RFC 9113 §8.1), not a new request.
399 if mut existing := c.streams[frame.stream_id] {
400 c.on_trailers(mut existing, frame, mut handler)!
401 return
402 }
403 // Stream ids from the client must be odd and strictly increasing.
404 if frame.stream_id & 1 == 0 || frame.stream_id <= c.last_stream_id {
405 return error('h2 server: invalid client stream id ${frame.stream_id}')
406 }
407 c.last_stream_id = frame.stream_id
408 mut s := &H2ServerStream{
409 id: frame.stream_id
410 hpack_block: frame.fragment.clone()
411 end_headers: frame.end_headers
412 end_stream: frame.end_stream
413 send_window: i64(c.peer.initial_window_size)
414 }
415 c.streams[frame.stream_id] = s
416 if !frame.end_headers {
417 c.awaiting_cont = frame.stream_id
418 return
419 }
420 c.finalize_headers(mut s, mut handler)!
421}
422
423fn (mut c H2ServerConn) on_continuation(frame H2ContinuationFrame, mut handler Handler) ! {
424 mut s := c.streams[frame.stream_id] or {
425 return error('h2 server: CONTINUATION for unknown stream ${frame.stream_id}')
426 }
427 if s.in_trailers {
428 if s.trailer_block.len + frame.fragment.len > h2_max_recv_header_block {
429 return error('h2 server: trailer block exceeds ${h2_max_recv_header_block} bytes')
430 }
431 s.trailer_block << frame.fragment
432 if frame.end_headers {
433 c.awaiting_cont = 0
434 c.finalize_trailers(mut s, mut handler)!
435 }
436 return
437 }
438 if s.hpack_block.len + frame.fragment.len > h2_max_recv_header_block {
439 return error('h2 server: request header block exceeds ${h2_max_recv_header_block} bytes')
440 }
441 s.hpack_block << frame.fragment
442 if frame.end_headers {
443 s.end_headers = true
444 c.awaiting_cont = 0
445 c.finalize_headers(mut s, mut handler)!
446 }
447}
448
449fn (mut c H2ServerConn) finalize_headers(mut s H2ServerStream, mut handler Handler) ! {
450 // An HPACK decoding failure is a CONNECTION error (RFC 9113 §4.3): the decoder
451 // mutates the dynamic table as it runs, so a failure leaves our table out of
452 // sync with the peer and no later header block on this connection can be decoded
453 // reliably. Close the connection with COMPRESSION_ERROR rather than RST-ing one
454 // stream. (send_goaway sets c.closing, so serve()'s catch won't re-GOAWAY.)
455 s.headers = c.decoder.decode(s.hpack_block) or {
456 c.send_goaway(.compression_error, 'HPACK decode error') or {}
457 return error('h2 server: HPACK decode error (COMPRESSION_ERROR)')
458 }
459 s.headers_done = true
460 // A well-decoded but malformed request is a STREAM error (RFC 9113 §8.1.1) —
461 // reset just this stream and keep the connection alive.
462 h2_validate_request_pseudo(s.headers) or {
463 c.send_rst_stream(s.id, .protocol_error)!
464 c.streams.delete(s.id)
465 return
466 }
467 if s.end_stream {
468 c.run_request(mut s, mut handler)!
469 }
470}
471
472// on_trailers handles a second HEADERS block on an already-open stream: an
473// HTTP/2 trailer section (RFC 9113 §8.1). The block is always assembled and
474// decoded; whether it is well-formed — it must carry END_STREAM and no
475// pseudo-headers — is decided in finalize_trailers. A well-decoded but malformed
476// trailer section is reset as a stream error; an HPACK decode failure is a
477// connection error (§4.3) since it desyncs the decoder.
478fn (mut c H2ServerConn) on_trailers(mut s H2ServerStream, frame H2HeadersFrame, mut handler Handler) ! {
479 s.in_trailers = true
480 s.end_stream = frame.end_stream
481 s.trailer_block << frame.fragment
482 if !frame.end_headers {
483 c.awaiting_cont = frame.stream_id
484 return
485 }
486 c.finalize_trailers(mut s, mut handler)!
487}
488
489fn (mut c H2ServerConn) finalize_trailers(mut s H2ServerStream, mut handler Handler) ! {
490 // An HPACK decoding failure is a CONNECTION error (RFC 9113 §4.3): the decoder's
491 // dynamic table is now out of sync with the peer. Close the connection rather
492 // than RST one stream.
493 trailers := c.decoder.decode(s.trailer_block) or {
494 c.send_goaway(.compression_error, 'HPACK decode error in trailers') or {}
495 return error('h2 server: HPACK decode error in trailers (COMPRESSION_ERROR)')
496 }
497 // A well-decoded but malformed trailer section is a STREAM error (§8.1.1).
498 mut reason := if !s.end_stream {
499 // A trailer section MUST terminate the stream (RFC 9113 §8.1).
500 'trailing HEADERS without END_STREAM'
501 } else {
502 ''
503 }
504 if reason == '' {
505 for f in trailers {
506 // Trailers carry no pseudo-headers and follow the §8.2 field rules.
507 reason = if f.name.starts_with(':') {
508 'pseudo-header "${f.name}" in trailers'
509 } else {
510 h2_request_field_error(f.name, f.value)
511 }
512 if reason != '' {
513 break
514 }
515 }
516 }
517 if reason != '' {
518 c.send_rst_stream(s.id, .protocol_error)!
519 c.streams.delete(s.id)
520 return
521 }
522 c.run_request(mut s, mut handler)!
523}
524
525fn (mut c H2ServerConn) on_data(frame H2DataFrame, mut handler Handler) ! {
526 mut s := c.streams[frame.stream_id] or {
527 // DATA for an unknown stream (likely already RST'd); just drop and
528 // keep flow control consistent. Credit flow_size (full wire bytes
529 // including padding) per RFC 7540 §6.9.1.
530 if frame.flow_size > 0 {
531 c.send_window_update(0, u32(frame.flow_size))!
532 }
533 return
534 }
535 if !s.headers_done {
536 return error('h2 server: DATA before END_HEADERS')
537 }
538 if frame.data.len > 0 {
539 if s.body.len + frame.data.len > h2_server_max_request_body {
540 // Credit the connection window before resetting so the peer is
541 // not penalised for bytes it legitimately sent within its window.
542 c.send_window_update(0, u32(frame.flow_size)) or {}
543 c.send_rst_stream(s.id, .refused_stream)!
544 c.streams.delete(s.id)
545 return
546 }
547 s.body << frame.data
548 }
549 // Replenish flow control using flow_size (full wire payload including padding),
550 // per RFC 7540 §6.9.1. Credit unconditionally when flow_size>0: a padding-only
551 // DATA frame (data.len==0, flow_size>0) still consumes the peer's send window
552 // and must be credited back. (Formerly inside the data.len>0 block — padding-only
553 // frames leaked window silently.)
554 if frame.flow_size > 0 {
555 c.send_window_update(0, u32(frame.flow_size))!
556 c.send_window_update(s.id, u32(frame.flow_size))!
557 }
558 if frame.end_stream {
559 s.end_stream = true
560 c.run_request(mut s, mut handler)!
561 }
562}
563
564fn (mut c H2ServerConn) run_request(mut s H2ServerStream, mut handler Handler) ! {
565 req := c.build_request(s) or {
566 c.send_rst_stream(s.id, .protocol_error)!
567 c.streams.delete(s.id)
568 return
569 }
570 resp := handler.handle(req)
571 c.send_response(s.id, resp)!
572 c.streams.delete(s.id)
573}
574
575fn (mut c H2ServerConn) build_request(s &H2ServerStream) !Request {
576 mut req := Request{
577 version: .v2_0
578 header: new_header()
579 }
580 // Pseudo-headers were already validated in h2_validate_request_pseudo
581 // (presence, no duplicates, ordering, allowed set); here we only extract.
582 mut method := ''
583 mut path := ''
584 mut authority := ''
585 mut content_length := -1
586 for f in s.headers {
587 match f.name {
588 ':method' {
589 method = f.value
590 }
591 ':path' {
592 path = f.value
593 }
594 ':authority' {
595 authority = f.value
596 }
597 ':scheme' {
598 // Validated already; handlers infer the scheme from Host, matching
599 // the HTTP/1.1 path.
600 }
601 else {
602 if f.name.starts_with(':') {
603 continue
604 }
605 if f.name == 'content-length' {
606 if !h2_all_digits(f.value) {
607 return error('h2 server: malformed content-length "${f.value}"')
608 }
609 cl := f.value.int()
610 // Multiple content-length fields with differing values are a
611 // malformed request (RFC 9110 §8.6); validate every occurrence,
612 // not just the last.
613 if content_length >= 0 && content_length != cl {
614 return error('h2 server: conflicting content-length values ${content_length} and ${cl}')
615 }
616 content_length = cl
617 }
618 req.header.add_custom(f.name, f.value) or {}
619 }
620 }
621 }
622 // RFC 9113 §8.1.2.6: a declared content-length MUST equal the sum of the DATA
623 // payload lengths; a mismatch is a malformed request (stream error).
624 if content_length >= 0 && content_length != s.body.len {
625 return error('h2 server: content-length ${content_length} != DATA length ${s.body.len}')
626 }
627 req.method = method_from_str(method)
628 if authority != '' && !req.header.contains(.host) {
629 req.header.add(.host, authority)
630 }
631 // Match the HTTP/1.1 path: req.url is the request-target (the :path
632 // pseudo-header), so handlers see the same shape on both transports.
633 req.url = path
634 req.data = s.body.bytestr()
635 req.host = authority
636 return req
637}
638
639fn (mut c H2ServerConn) send_response(stream_id u32, resp Response) ! {
640 status := if resp.status_code == 0 { 200 } else { resp.status_code }
641 mut fields := [H2HeaderField{':status', status.str()}]
642 for key in resp.header.keys() {
643 lkey := key.to_lower()
644 // Drop hop-by-hop headers; HTTP/2 forbids them (RFC 9113 §8.2.2).
645 if lkey in h2_conn_specific_headers {
646 continue
647 }
648 for val in resp.header.custom_values(key) {
649 fields << H2HeaderField{lkey, val}
650 }
651 }
652 body := resp.body.bytes()
653 has_body := body.len > 0
654 block := c.encoder.encode(fields)
655 c.send_header_block(stream_id, block, !has_body)!
656 if has_body {
657 c.send_body(stream_id, body)!
658 }
659}
660
661fn (mut c H2ServerConn) send_header_block(stream_id u32, block []u8, end_stream bool) ! {
662 max := int(c.peer.max_frame_size)
663 if block.len <= max {
664 c.send_frame(H2HeadersFrame{
665 stream_id: stream_id
666 fragment: block
667 end_headers: true
668 end_stream: end_stream
669 })!
670 return
671 }
672 c.send_frame(H2HeadersFrame{
673 stream_id: stream_id
674 fragment: block[..max]
675 end_headers: false
676 end_stream: end_stream
677 })!
678 mut off := max
679 for off < block.len {
680 mut next := off + max
681 if next > block.len {
682 next = block.len
683 }
684 c.send_frame(H2ContinuationFrame{
685 stream_id: stream_id
686 fragment: block[off..next]
687 end_headers: next == block.len
688 })!
689 off = next
690 }
691}
692
693fn (mut c H2ServerConn) send_body(stream_id u32, body []u8) ! {
694 max := int(c.peer.max_frame_size)
695 mut off := 0
696 for off < body.len {
697 // Respect both the connection and per-stream send windows
698 // (RFC 7540 Section 6.9). When either is exhausted, read frames until
699 // the peer grows a window with WINDOW_UPDATE.
700 for c.send_window <= 0 || c.stream_send_window(stream_id) <= 0 {
701 c.pump_for_window(stream_id)!
702 }
703 avail := if c.send_window < c.stream_send_window(stream_id) {
704 c.send_window
705 } else {
706 c.stream_send_window(stream_id)
707 }
708 mut chunk := body.len - off
709 if chunk > max {
710 chunk = max
711 }
712 if i64(chunk) > avail {
713 chunk = int(avail)
714 }
715 next := off + chunk
716 c.send_frame(H2DataFrame{
717 stream_id: stream_id
718 data: body[off..next]
719 end_stream: next == body.len
720 })!
721 c.send_window -= i64(chunk)
722 if mut s := c.streams[stream_id] {
723 s.send_window -= i64(chunk)
724 }
725 off = next
726 }
727}
728
729// stream_send_window returns the current per-stream send window, or 0 if the
730// stream is gone.
731fn (c &H2ServerConn) stream_send_window(stream_id u32) i64 {
732 if s := c.streams[stream_id] {
733 return s.send_window
734 }
735 return 0
736}
737
738// pump_for_window reads one frame while a response is blocked on flow control,
739// servicing control frames (SETTINGS / PING / WINDOW_UPDATE) via
740// handle_control_frame and aborting on RST_STREAM for the active stream.
741fn (mut c H2ServerConn) pump_for_window(stream_id u32) ! {
742 frame := c.read_frame()!
743 c.handle_control_frame(frame)!
744 if frame is H2RstStreamFrame {
745 if frame.stream_id == stream_id {
746 return error('h2 server: stream reset by peer while writing response')
747 }
748 }
749 // With SETTINGS_MAX_CONCURRENT_STREAMS=1 no other stream frames are
750 // expected mid-response; ignore anything else defensively.
751}
752
753fn (mut c H2ServerConn) send_window_update(stream_id u32, inc u32) ! {
754 if inc == 0 {
755 return
756 }
757 c.send_frame(H2WindowUpdateFrame{
758 stream_id: stream_id
759 window_size_increment: inc
760 })!
761}
762
763fn (mut c H2ServerConn) send_rst_stream(stream_id u32, code H2ErrorCode) ! {
764 c.send_frame(H2RstStreamFrame{
765 stream_id: stream_id
766 error_code: u32(code)
767 })!
768}
769
770fn (mut c H2ServerConn) send_goaway(code H2ErrorCode, msg string) ! {
771 c.send_frame(H2GoawayFrame{
772 last_stream_id: c.last_stream_id
773 error_code: u32(code)
774 debug_data: msg.bytes()
775 })!
776 c.closing = true
777}
778
779fn (mut c H2ServerConn) read_frame() !H2Frame {
780 c.fill_at_least(h2_frame_header_len)!
781 header := h2_parse_frame_header(c.rbuf)!
782 // Check against our own advertised receive limit (sent in send_initial_settings).
783 // H2ServerConn always advertises h2_default_max_frame_size and never renegotiates
784 // it, so the constant is correct here. c.peer.max_frame_size is the client's
785 // receive limit (our outbound cap) and must not be used for inbound checking.
786 if header.length > h2_default_max_frame_size {
787 return error('h2 server: frame larger than SETTINGS_MAX_FRAME_SIZE (${header.length})')
788 }
789 total := h2_frame_header_len + int(header.length)
790 c.fill_at_least(total)!
791 frame := h2_parse_frame(header, c.rbuf[h2_frame_header_len..total])!
792 c.rbuf = c.rbuf[total..].clone()
793 return frame
794}
795
796fn (mut c H2ServerConn) fill_at_least(n int) ! {
797 for c.rbuf.len < n {
798 mut tmp := []u8{len: h2_conn_read_chunk}
799 got := c.transport.read(mut tmp)!
800 if got <= 0 {
801 return error('h2 server: connection closed by peer')
802 }
803 c.rbuf << tmp[..got]
804 }
805}
806
807fn (mut c H2ServerConn) send_frame(f H2Frame) ! {
808 c.write_all(f.encode())!
809}
810
811fn (mut c H2ServerConn) write_all(data []u8) ! {
812 mut sent := 0
813 for sent < data.len {
814 n := c.transport.write(data[sent..])!
815 if n <= 0 {
816 return error('h2 server: transport write returned ${n}')
817 }
818 sent += n
819 }
820}
821