Skip to content

Commit f70d902

Browse files
committed
feat(quic,http): implement QUIC FIN signaling, HTTP/1 body limit, and review fixes
QUIC FIN layer: - Add NGTCP2_WRITE_STREAM_FLAG_FIN/MORE constants and flags parameter to conn_writev_stream - Register recv_stream_data and stream_close ngtcp2 callbacks with FIN detection and overflow-safe event buffering - Add send_fin(), send_with_fin(), send_with_flags() methods - Add drain_stream_events() with error propagation on overflow - Add ensure_stream(), stream_has_fin(), stream_exists() abstraction API - Auto-create stream entries for FIN events on unknown streams HTTP/3 FIN integration: - Replace non-standard empty DATA frame end-marker with proper QUIC FIN signaling per RFC 9114 §4.1 - Client sends FIN after last frame via send_frame_with_fin() - Server detects request completion via check_fin_completions() sweep after frame processing, handling separate-packet FIN and empty-body POST/PUT/PATCH - Server coalesces response FIN with last data write - Per-connection packet_mu mutex serializing QUIC state mutations - Split process_packet_frames into ingest/decode/dispatch helpers HTTP/1 hardening: - Add max_request_body_size (10MB default) to Server struct matching HTTP/2 and HTTP/3 defaults - Add parse_request_with_limit() checking Content-Length before allocation - Strict Content-Length validation rejecting negative, non-numeric, and overflow values via validate_and_parse_content_length() - Detect truncated request bodies (unexpected EOF) - Backward-compatible Handler interface with ServerHandler adapter
1 parent ede3439 commit f70d902

56 files changed

Lines changed: 3873 additions & 2331 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,7 @@ autofuzz.log
169169
CHANGELOG.md
170170
v
171171
vnew
172-
vnew.*
172+
vnew.*
173+
174+
docs/
175+
*.md

examples/binary_upload_server.v

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Binary Upload/Download Server Example
2+
// Demonstrates []u8 body handling for file uploads and binary responses.
3+
module main
4+
5+
import net.http
6+
import os
7+
8+
struct BinaryHandler {}
9+
10+
fn (h BinaryHandler) handle(req http.ServerRequest) http.ServerResponse {
11+
match req.path {
12+
'/upload' {
13+
if req.method != .post {
14+
return http.ServerResponse{
15+
status_code: 405
16+
body: 'Method Not Allowed'.bytes()
17+
}
18+
}
19+
content_type := req.header.get(.content_type) or { 'application/octet-stream' }
20+
println('[upload] received ${req.body.len} bytes (${content_type})')
21+
22+
os.write_file_array('/tmp/uploaded_file', req.body) or {
23+
return http.ServerResponse{
24+
status_code: 500
25+
body: 'Failed to save file: ${err}'.bytes()
26+
}
27+
}
28+
29+
mut header := http.new_header()
30+
header.add(.content_type, 'application/json')
31+
return http.ServerResponse{
32+
status_code: 200
33+
header: header
34+
body: '{"status":"ok","size":${req.body.len}}'.bytes()
35+
}
36+
}
37+
'/download' {
38+
data := os.read_bytes('/tmp/uploaded_file') or {
39+
return http.ServerResponse{
40+
status_code: 404
41+
body: 'No file uploaded yet'.bytes()
42+
}
43+
}
44+
mut header := http.new_header()
45+
header.add(.content_type, 'application/octet-stream')
46+
header.add(.content_disposition, 'attachment; filename="downloaded_file"')
47+
return http.ServerResponse{
48+
status_code: 200
49+
header: header
50+
body: data
51+
}
52+
}
53+
'/generate' {
54+
size := 1024 * 1024 // 1 MB of binary data
55+
mut data := []u8{len: size}
56+
for i in 0 .. size {
57+
data[i] = u8(i % 256)
58+
}
59+
mut header := http.new_header()
60+
header.add(.content_type, 'application/octet-stream')
61+
return http.ServerResponse{
62+
status_code: 200
63+
header: header
64+
body: data
65+
}
66+
}
67+
'/' {
68+
return http.ServerResponse{
69+
status_code: 200
70+
body: 'Binary Server\n\nEndpoints:\n POST /upload - upload binary file\n GET /download - download last uploaded file\n GET /generate - download 1MB generated binary\n'.bytes()
71+
}
72+
}
73+
else {
74+
return http.ServerResponse{
75+
status_code: 404
76+
body: 'Not found'.bytes()
77+
}
78+
}
79+
}
80+
}
81+
82+
fn main() {
83+
mut server := http.Server{
84+
addr: ':8080'
85+
handler: BinaryHandler{}
86+
}
87+
server.listen_and_serve()
88+
}

examples/http2/01_simple_server.v

Lines changed: 49 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,75 @@
1-
// Simple HTTP/2 Server Example
2-
// Demonstrates basic HTTP/2 server usage
3-
import net.http.v2
1+
// HTTP/2 Server Example
2+
// Demonstrates HTTP/2 server using the unified net.http API.
3+
// HTTP/2 is enabled automatically over TLS with ALPN h2 negotiation.
4+
//
5+
// To generate test certificates:
6+
// openssl req -x509 -newkey rsa:2048 -nodes \
7+
// -keyout cert.pem -out cert.pem -days 365 \
8+
// -subj "/CN=localhost"
9+
//
10+
// Test with: curl -k --http2 https://localhost:8080/
11+
module main
412

5-
fn main() {
6-
// Create server configuration
7-
config := v2.ServerConfig{
8-
addr: '0.0.0.0:8080'
9-
max_concurrent_streams: 100
10-
initial_window_size: 65535
11-
max_frame_size: 16384
12-
}
13-
14-
// Create server with handler
15-
mut server := v2.new_server(config, handle_request) or {
16-
eprintln('Failed to create server: ${err}')
17-
return
18-
}
13+
import net.http
1914

20-
println('Starting HTTP/2 server on ${config.addr}')
21-
println('Test with: curl --http2-prior-knowledge http://localhost:8080/')
22-
println('Press Ctrl+C to stop')
23-
24-
// Start server (blocks)
25-
server.listen_and_serve() or { eprintln('Server error: ${err}') }
26-
}
15+
struct AppHandler {}
2716

28-
// handle_request processes HTTP/2 requests
29-
fn handle_request(req v2.ServerRequest) v2.ServerResponse {
30-
println('Received: ${req.method} ${req.path}')
17+
fn (h AppHandler) handle(req http.ServerRequest) http.ServerResponse {
18+
println('Received: ${req.method} ${req.path} (${req.version})')
3119

32-
// Route requests
3320
match req.path {
3421
'/' {
35-
return v2.ServerResponse{
22+
return http.ServerResponse{
3623
status_code: 200
37-
headers: {
38-
'content-type': 'text/html; charset=utf-8'
39-
}
24+
header: http.new_header_from_map({
25+
.content_type: 'text/html; charset=utf-8'
26+
})
4027
body: '<h1>Hello from HTTP/2!</h1><p>This is a V HTTP/2 server.</p>'.bytes()
4128
}
4229
}
4330
'/json' {
44-
return v2.ServerResponse{
31+
return http.ServerResponse{
4532
status_code: 200
46-
headers: {
47-
'content-type': 'application/json'
48-
}
33+
header: http.new_header_from_map({
34+
.content_type: 'application/json'
35+
})
4936
body: '{"message":"Hello from HTTP/2","protocol":"h2"}'.bytes()
5037
}
5138
}
5239
'/echo' {
53-
return v2.ServerResponse{
40+
return http.ServerResponse{
5441
status_code: 200
55-
headers: {
56-
'content-type': 'text/plain'
57-
}
58-
body: 'Method: ${req.method}\nPath: ${req.path}\n'.bytes()
42+
header: http.new_header_from_map({
43+
.content_type: 'text/plain'
44+
})
45+
body: 'Method: ${req.method}\nPath: ${req.path}\nVersion: ${req.version}\n'.bytes()
5946
}
6047
}
6148
else {
62-
return v2.ServerResponse{
49+
return http.ServerResponse{
6350
status_code: 404
64-
headers: {
65-
'content-type': 'text/plain'
66-
}
51+
header: http.new_header_from_map({
52+
.content_type: 'text/plain'
53+
})
6754
body: 'Not Found'.bytes()
6855
}
6956
}
7057
}
7158
}
59+
60+
fn main() {
61+
mut server := http.Server{
62+
addr: '0.0.0.0:8080'
63+
handler: AppHandler{}
64+
cert_file: 'cert.pem'
65+
key_file: 'key.pem'
66+
}
67+
68+
println('Starting HTTP/2 server on ${server.addr}')
69+
println('HTTP/2 is enabled automatically over TLS (ALPN h2)')
70+
println('Test with: curl -k --http2 https://localhost:8080/')
71+
println('Press Ctrl+C to stop')
72+
73+
// listen_and_serve_tls() starts HTTP/2 over TLS automatically
74+
server.listen_and_serve_tls() or { eprintln('Server error: ${err}') }
75+
}

0 commit comments

Comments
 (0)