1#[cfg(feature = "http2")]
10#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
11use crate::error::ServerError;
12#[cfg(feature = "http2")]
13#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
14use crate::request::Request;
15#[cfg(feature = "http2")]
16#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
17use crate::server::{Server, build_response_for_request_with_metrics};
18
19#[cfg(feature = "http2")]
25#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
26pub async fn start_http2(server: Server) -> Result<(), ServerError> {
47 let listener = tokio::net::TcpListener::bind(server.address())
48 .await
49 .map_err(ServerError::from)?;
50
51 loop {
52 let (stream, _) =
53 listener.accept().await.map_err(ServerError::from)?;
54 let server_clone = server.clone();
55 drop(tokio::spawn(async move {
56 if let Err(error) =
57 handle_h2_connection(stream, server_clone).await
58 {
59 eprintln!("HTTP/2 connection error: {}", error);
60 }
61 }));
62 }
63}
64
65#[cfg(feature = "http2")]
66fn h2_handshake_err(e: h2::Error) -> ServerError {
67 ServerError::Custom(format!("h2 handshake: {e}"))
68}
69
70#[cfg(feature = "http2")]
71fn h2_accept_err(e: h2::Error) -> ServerError {
72 ServerError::Custom(format!("h2 accept: {e}"))
73}
74
75#[cfg(feature = "http2")]
76fn h2_send_headers_err(e: h2::Error) -> ServerError {
77 ServerError::Custom(format!(
78 "failed to send h2 response headers: {e}"
79 ))
80}
81
82#[cfg(feature = "http2")]
83fn h2_send_body_err(e: h2::Error) -> ServerError {
84 ServerError::Custom(format!("failed to send h2 response body: {e}"))
85}
86
87#[cfg(feature = "http2")]
88fn h2_build_head_err(e: http::Error) -> ServerError {
89 ServerError::Custom(format!(
90 "failed to build h2 response headers: {e}"
91 ))
92}
93
94#[cfg(feature = "http2")]
95#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
96async fn handle_h2_connection(
97 stream: tokio::net::TcpStream,
98 server: Server,
99) -> Result<(), ServerError> {
100 let _ = stream.set_nodelay(true);
102 let mut connection = h2::server::handshake(stream)
103 .await
104 .map_err(h2_handshake_err)?;
105
106 while let Some(next) = connection.accept().await {
107 let (request, respond) = next.map_err(h2_accept_err)?;
108 let parsed_request = map_h2_request(&request);
109 let response = build_response_for_request_with_metrics(
110 &server,
111 &parsed_request,
112 );
113 send_h2_response(respond, response)?;
114 }
115
116 Ok(())
117}
118
119#[cfg(feature = "http2")]
120#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
121fn map_h2_request<B>(request: &http::Request<B>) -> Request {
122 let headers = request
123 .headers()
124 .iter()
125 .filter_map(|(name, value)| {
126 value.to_str().ok().map(|value| {
127 (name.as_str().to_ascii_lowercase(), value.to_string())
128 })
129 })
130 .collect();
131
132 let version = match request.version() {
133 http::Version::HTTP_2 => "HTTP/2.0",
134 _ => "HTTP/1.1",
135 };
136
137 Request {
138 method: request.method().as_str().to_string(),
139 path: request.uri().path().to_string(),
140 version: version.to_string(),
141 headers,
142 }
143}
144
145#[cfg(feature = "http2")]
146#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
147fn send_h2_response(
148 mut respond: h2::server::SendResponse<bytes::Bytes>,
149 response: crate::response::Response,
150) -> Result<(), ServerError> {
151 let head = build_h2_head(&response)?;
152
153 let end_of_stream = response.body.is_empty();
154 let mut stream = respond
155 .send_response(head, end_of_stream)
156 .map_err(h2_send_headers_err)?;
157
158 if !end_of_stream {
159 stream
160 .send_data(bytes::Bytes::from(response.body), true)
161 .map_err(h2_send_body_err)?;
162 }
163
164 Ok(())
165}
166
167#[cfg(feature = "http2")]
168#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
169fn build_h2_head(
170 response: &crate::response::Response,
171) -> Result<http::Response<()>, ServerError> {
172 let mut builder =
173 http::Response::builder().status(response.status_code);
174 for (name, value) in &response.headers {
175 builder = builder.header(name, value);
176 }
177 builder.body(()).map_err(h2_build_head_err)
178}
179
180#[cfg(all(test, feature = "http2"))]
181mod tests {
182 use super::*;
183 use bytes::Bytes;
184 use http::Version;
185 use std::io::Write;
186 use std::net::TcpListener;
187 use tempfile::TempDir;
188 use tokio::io::AsyncWriteExt;
189 use tokio::time::{Duration, sleep};
190
191 fn free_addr() -> String {
192 let listener = TcpListener::bind("127.0.0.1:0").expect("bind");
193 let addr = listener.local_addr().expect("addr");
194 drop(listener);
195 addr.to_string()
196 }
197
198 #[tokio::test]
199 async fn http2_server_serves_static_file() {
200 let root = TempDir::new().expect("tmp");
201 std::fs::write(root.path().join("index.html"), b"hello-h2")
202 .expect("write index");
203 std::fs::create_dir(root.path().join("404")).expect("404 dir");
204 std::fs::write(root.path().join("404/index.html"), b"404")
205 .expect("write 404");
206
207 let addr = free_addr();
208 let server = Server::builder()
209 .address(&addr)
210 .document_root(root.path().to_str().expect("path"))
211 .build()
212 .expect("server");
213
214 let task = tokio::spawn(start_http2(server));
215 sleep(Duration::from_millis(40)).await;
216
217 let stream = tokio::net::TcpStream::connect(&addr)
218 .await
219 .expect("connect");
220 let (mut client, connection) =
221 h2::client::handshake(stream).await.expect("handshake");
222 drop(tokio::spawn(connection));
223
224 let request = http::Request::builder()
225 .method("GET")
226 .uri("http://localhost/")
227 .body(())
228 .expect("request");
229 let (response_future, _send_stream) =
230 client.send_request(request, true).expect("send request");
231 let response = response_future.await.expect("response");
232 assert_eq!(response.status().as_u16(), 200);
233
234 let mut body = response.into_body();
235 let mut collected = Vec::new();
236 while let Some(next) = body.data().await {
237 let chunk: Bytes = next.expect("chunk");
238 collected.extend_from_slice(&chunk);
239 }
240
241 assert_eq!(collected, b"hello-h2");
242 task.abort();
243 }
244
245 #[test]
246 fn map_h2_request_preserves_method_path_headers_and_version() {
247 let request = http::Request::builder()
248 .method("GET")
249 .uri("/status")
250 .version(Version::HTTP_2)
251 .header("x-test", "value")
252 .body(())
253 .expect("request");
254 let parsed = map_h2_request(&request);
255 assert_eq!(parsed.method(), "GET");
256 assert_eq!(parsed.path(), "/status");
257 assert_eq!(parsed.version(), "HTTP/2.0");
258 assert_eq!(parsed.header("x-test"), Some("value"));
259 }
260
261 #[test]
262 fn map_h2_request_falls_back_to_http11_for_other_versions() {
263 let request = http::Request::builder()
264 .method("GET")
265 .uri("/legacy")
266 .version(Version::HTTP_11)
267 .body(())
268 .expect("request");
269 let parsed = map_h2_request(&request);
270 assert_eq!(parsed.version(), "HTTP/1.1");
271 }
272
273 #[test]
274 fn h2_error_context_helpers_wrap_source_message() {
275 let reason = h2::Reason::PROTOCOL_ERROR;
276 let handshake = h2_handshake_err(h2::Error::from(reason));
277 assert!(matches!(handshake, ServerError::Custom(_)));
278 assert!(handshake.to_string().contains("h2 handshake:"));
279
280 let accept = h2_accept_err(h2::Error::from(reason));
281 assert!(accept.to_string().contains("h2 accept:"));
282
283 let headers = h2_send_headers_err(h2::Error::from(reason));
284 assert!(
285 headers.to_string().contains("send h2 response headers:")
286 );
287
288 let body = h2_send_body_err(h2::Error::from(reason));
289 assert!(body.to_string().contains("send h2 response body:"));
290
291 let http_err = http::Response::builder()
294 .header("bad header name", "v")
295 .body(())
296 .expect_err(
297 "invalid header name should produce http::Error",
298 );
299 let built = h2_build_head_err(http_err);
300 assert!(
301 built.to_string().contains("build h2 response headers:")
302 );
303 }
304
305 #[test]
306 fn build_h2_head_rejects_invalid_header_name() {
307 let mut response =
308 crate::response::Response::new(200, "OK", Vec::new());
309 response.add_header("bad header", "value");
310 let result = build_h2_head(&response);
311 assert!(matches!(result, Err(ServerError::Custom(_))));
312 }
313
314 #[tokio::test]
315 async fn handle_h2_connection_reports_handshake_error_on_invalid_preface()
316 {
317 let root = TempDir::new().expect("tmp");
318 std::fs::write(root.path().join("index.html"), b"hello")
319 .expect("write index");
320 std::fs::create_dir(root.path().join("404")).expect("404 dir");
321 std::fs::write(root.path().join("404/index.html"), b"404")
322 .expect("write 404");
323
324 let addr = free_addr();
325 let listener =
326 tokio::net::TcpListener::bind(&addr).await.expect("bind");
327 let server = Server::builder()
328 .address(&addr)
329 .document_root(root.path().to_str().expect("path"))
330 .build()
331 .expect("server");
332
333 let accept_task = tokio::spawn(async move {
334 let (stream, _) = listener.accept().await.expect("accept");
335 handle_h2_connection(stream, server).await
336 });
337
338 let mut client =
339 std::net::TcpStream::connect(&addr).expect("connect");
340 client
341 .write_all(b"this-is-not-http2")
342 .expect("write invalid preface");
343
344 let result = accept_task.await.expect("join");
345 assert!(matches!(result, Err(ServerError::Custom(_))));
346 }
347
348 #[tokio::test]
349 async fn http2_server_returns_404_for_missing_resource() {
350 let root = TempDir::new().expect("tmp");
351 std::fs::write(root.path().join("index.html"), b"hello-h2")
352 .expect("write index");
353 std::fs::create_dir(root.path().join("404")).expect("404 dir");
354 std::fs::write(root.path().join("404/index.html"), b"404 page")
355 .expect("write 404");
356
357 let addr = free_addr();
358 let server = Server::builder()
359 .address(&addr)
360 .document_root(root.path().to_str().expect("path"))
361 .build()
362 .expect("server");
363
364 let task = tokio::spawn(start_http2(server));
365 sleep(Duration::from_millis(40)).await;
366
367 let stream = tokio::net::TcpStream::connect(&addr)
368 .await
369 .expect("connect");
370 let (mut client, connection) =
371 h2::client::handshake(stream).await.expect("handshake");
372 drop(tokio::spawn(connection));
373
374 let request = http::Request::builder()
375 .method("GET")
376 .uri("http://localhost/does-not-exist")
377 .body(())
378 .expect("request");
379 let (response_future, _send_stream) =
380 client.send_request(request, true).expect("send request");
381 let response = response_future.await.expect("response");
382 assert_eq!(response.status().as_u16(), 404);
383
384 let mut body = response.into_body();
385 let mut collected = Vec::new();
386 while let Some(next) = body.data().await {
387 let chunk: Bytes = next.expect("chunk");
388 collected.extend_from_slice(&chunk);
389 }
390 assert_eq!(collected, b"404 page");
391 task.abort();
392 }
393
394 #[tokio::test]
395 async fn http2_server_returns_405_for_unsupported_method() {
396 let root = TempDir::new().expect("tmp");
397 std::fs::write(root.path().join("index.html"), b"hello-h2")
398 .expect("write index");
399 std::fs::create_dir(root.path().join("404")).expect("404 dir");
400 std::fs::write(root.path().join("404/index.html"), b"404")
401 .expect("write 404");
402
403 let addr = free_addr();
404 let server = Server::builder()
405 .address(&addr)
406 .document_root(root.path().to_str().expect("path"))
407 .build()
408 .expect("server");
409
410 let task = tokio::spawn(start_http2(server));
411 sleep(Duration::from_millis(40)).await;
412
413 let stream = tokio::net::TcpStream::connect(&addr)
414 .await
415 .expect("connect");
416 let (mut client, connection) =
417 h2::client::handshake(stream).await.expect("handshake");
418 drop(tokio::spawn(connection));
419
420 let request = http::Request::builder()
421 .method("POST")
422 .uri("http://localhost/")
423 .body(())
424 .expect("request");
425 let (response_future, _send_stream) =
426 client.send_request(request, true).expect("send request");
427 let response = response_future.await.expect("response");
428 assert_eq!(response.status().as_u16(), 405);
429 task.abort();
430 }
431
432 #[tokio::test]
433 async fn handle_h2_connection_surfaces_send_errors_when_client_rsts()
434 {
435 let root = TempDir::new().expect("tmp");
440 std::fs::write(root.path().join("index.html"), b"hello-h2")
441 .expect("write index");
442 std::fs::create_dir(root.path().join("404")).expect("404 dir");
443 std::fs::write(root.path().join("404/index.html"), b"404")
444 .expect("write 404");
445
446 let addr = free_addr();
447 let listener =
448 tokio::net::TcpListener::bind(&addr).await.expect("bind");
449 let server = Server::builder()
450 .address(&addr)
451 .document_root(root.path().to_str().expect("path"))
452 .build()
453 .expect("server");
454
455 let accept_task = tokio::spawn(async move {
456 let (stream, _) = listener.accept().await.expect("accept");
457 handle_h2_connection(stream, server).await
458 });
459
460 let tcp = tokio::net::TcpStream::connect(&addr)
466 .await
467 .expect("connect");
468 let (mut client, connection) =
469 h2::client::handshake(tcp).await.expect("handshake");
470 let conn_task = tokio::spawn(connection);
471
472 let request = http::Request::builder()
473 .method("GET")
474 .uri("http://localhost/")
475 .body(())
476 .expect("request");
477 let (_response_future, _send) =
478 client.send_request(request, true).expect("send request");
479 drop(client);
482 conn_task.abort();
483
484 let result =
485 tokio::time::timeout(Duration::from_secs(2), accept_task)
486 .await
487 .expect("accept_task timed out");
488 let _ = result.expect("join");
491 }
492
493 #[tokio::test]
494 async fn start_http2_handles_invalid_client_preface() {
495 let root = TempDir::new().expect("tmp");
496 std::fs::write(root.path().join("index.html"), b"hello-h2")
497 .expect("write index");
498 std::fs::create_dir(root.path().join("404")).expect("404 dir");
499 std::fs::write(root.path().join("404/index.html"), b"404")
500 .expect("write 404");
501
502 let addr = free_addr();
503 let server = Server::builder()
504 .address(&addr)
505 .document_root(root.path().to_str().expect("path"))
506 .build()
507 .expect("server");
508
509 let task = tokio::spawn(start_http2(server));
510 sleep(Duration::from_millis(40)).await;
511
512 let mut client =
513 std::net::TcpStream::connect(&addr).expect("connect");
514 client
515 .write_all(b"not-http2")
516 .expect("write invalid preface");
517 sleep(Duration::from_millis(40)).await;
518 task.abort();
519 }
520
521 #[tokio::test]
522 async fn handle_h2_connection_returns_ok_when_client_closes_cleanly()
523 {
524 let root = TempDir::new().expect("tmp");
525 std::fs::write(root.path().join("index.html"), b"hello-h2")
526 .expect("write index");
527 std::fs::create_dir(root.path().join("404")).expect("404 dir");
528 std::fs::write(root.path().join("404/index.html"), b"404")
529 .expect("write 404");
530
531 let addr = free_addr();
532 let listener =
533 tokio::net::TcpListener::bind(&addr).await.expect("bind");
534 let server = Server::builder()
535 .address(&addr)
536 .document_root(root.path().to_str().expect("path"))
537 .build()
538 .expect("server");
539
540 let accept_task = tokio::spawn(async move {
541 let (stream, _) = listener.accept().await.expect("accept");
542 handle_h2_connection(stream, server).await
543 });
544
545 let stream = tokio::net::TcpStream::connect(&addr)
546 .await
547 .expect("connect");
548 let (mut client, connection) =
549 h2::client::handshake(stream).await.expect("handshake");
550 let conn_task = tokio::spawn(connection);
551
552 let request = http::Request::builder()
553 .method("GET")
554 .uri("http://localhost/")
555 .body(())
556 .expect("request");
557 let (response_future, _send_stream) =
558 client.send_request(request, true).expect("send request");
559 let _ = response_future.await.expect("response");
560 drop(client);
561 let _ =
562 tokio::time::timeout(Duration::from_millis(500), conn_task)
563 .await;
564
565 let _ = tokio::time::timeout(
566 Duration::from_millis(500),
567 accept_task,
568 )
569 .await;
570 }
571
572 #[tokio::test]
573 async fn handle_h2_connection_maps_accept_errors() {
574 let root = TempDir::new().expect("tmp");
575 std::fs::write(root.path().join("index.html"), b"hello")
576 .expect("write index");
577 std::fs::create_dir(root.path().join("404")).expect("404 dir");
578 std::fs::write(root.path().join("404/index.html"), b"404")
579 .expect("write 404");
580
581 let addr = free_addr();
582 let listener =
583 tokio::net::TcpListener::bind(&addr).await.expect("bind");
584 let server = Server::builder()
585 .address(&addr)
586 .document_root(root.path().to_str().expect("path"))
587 .build()
588 .expect("server");
589
590 let accept_task = tokio::spawn(async move {
591 let (stream, _) = listener.accept().await.expect("accept");
592 handle_h2_connection(stream, server).await
593 });
594
595 let mut client = tokio::net::TcpStream::connect(&addr)
596 .await
597 .expect("connect");
598 client
600 .write_all(b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
601 .await
602 .expect("preface");
603 client
604 .write_all(&[0, 0, 1, 0xff, 0, 0, 0, 0, 0, 0x00])
605 .await
606 .expect("malformed frame");
607 let _ = client.shutdown().await;
608
609 let result = accept_task.await.expect("join");
610 assert!(
611 result.is_ok()
612 || matches!(result, Err(ServerError::Custom(_)))
613 );
614 }
615}