Skip to main content

go_http/
server.rs

1// SPDX-License-Identifier: Apache-2.0
2
3/// Server — port of Go's net/http Server.
4///
5/// Goroutine-per-connection model backed by go-lib.
6///
7/// ## I/O model
8///
9/// go-lib's `TcpListener::accept()` integrates with the kqueue/epoll/IOCP
10/// netpoll and parks goroutines without blocking OS threads.
11///
12/// `TcpStream` implements `std::io::Read` and `std::io::Write` directly
13/// (go-lib ≥ 0.5.1), so `serve_conn` uses `stream.try_clone()` to split the
14/// connection into independent read and write halves — no unsafe fd
15/// manipulation required.
16use std::io::{self, Read};
17use std::sync::{Arc, Mutex};
18use std::time::Duration;
19
20use go_lib::chan::{chan, Sender};
21use go_lib::net::{TcpListener, TcpStream};
22use rustls::ServerConnection;
23use url::Url;
24
25use crate::error::HttpError;
26use crate::handler::{default_serve_mux, Handler};
27use crate::parse::request::{read_request, DEFAULT_MAX_HEADER_BYTES};
28use crate::parse::transfer::Body;
29use crate::request::Request;
30use crate::response::{ConnResponseWriter, ResponseWriter};
31
32// ---------------------------------------------------------------------------
33// Server struct
34// ---------------------------------------------------------------------------
35
36/// An HTTP/1.1 server.  Mirrors Go's `http.Server`.
37pub struct Server {
38    /// TCP address to listen on, e.g. `"127.0.0.1:8080"`.
39    pub addr: String,
40    /// Request handler.  `None` uses the global `DefaultServeMux`.
41    pub handler: Option<Arc<dyn Handler>>,
42    pub read_timeout:  Option<Duration>,
43    pub write_timeout: Option<Duration>,
44    pub idle_timeout:  Option<Duration>,
45    /// Maximum bytes consumed while reading request headers.
46    pub max_header_bytes: usize,
47    /// Populated by `listen_and_serve`; send `()` to request shutdown.
48    shutdown_tx: Mutex<Option<Sender<()>>>,
49}
50
51impl Server {
52    pub fn new(addr: impl Into<String>) -> Self {
53        Self {
54            addr:             addr.into(),
55            handler:          None,
56            read_timeout:     None,
57            write_timeout:    None,
58            idle_timeout:     None,
59            max_header_bytes: DEFAULT_MAX_HEADER_BYTES,
60            shutdown_tx:      Mutex::new(None),
61        }
62    }
63
64    /// Bind, listen, and serve HTTP/1.1 requests.
65    ///
66    /// **Must be called from within a `#[go_lib::main]` body (goroutine context).**
67    ///
68    /// Blocks the calling goroutine until `shutdown()` is called or a fatal
69    /// listener error occurs.  Port of Go's `(*Server).ListenAndServe`.
70    pub fn listen_and_serve(&self) -> Result<(), HttpError> {
71        let listener = TcpListener::bind(&self.addr as &str).map_err(HttpError::Io)?;
72
73        let handler: Arc<dyn Handler> = match &self.handler {
74            Some(h) => Arc::clone(h),
75            None    => default_serve_mux(),
76        };
77        let max_header_bytes = self.max_header_bytes;
78
79        // Shutdown signal (buffered so shutdown() never blocks).
80        let (shutdown_tx, shutdown_rx) = chan::<()>(1);
81        *self.shutdown_tx.lock().unwrap() = Some(shutdown_tx);
82
83        // Channel that delivers accepted connections to the dispatch loop.
84        let (conn_tx, conn_rx) = chan::<TcpStream>(8);
85
86        // ── Accept goroutine ──────────────────────────────────────────────────
87        // listener.accept() uses go-lib's netpoll: parks the goroutine via
88        // gopark and resumes via goready when a connection arrives.
89        // We must NOT wrap it in with_syscall because gopark is illegal
90        // during entersyscall.
91        go_lib::go!(move || {
92            loop {
93                match listener.accept() {
94                    Err(_)       => break,
95                    Ok(stream)   => {
96                        let _ = std::panic::catch_unwind(
97                            std::panic::AssertUnwindSafe(|| conn_tx.send(stream))
98                        );
99                    }
100                }
101            }
102        });
103
104        // ── Dispatch loop ─────────────────────────────────────────────────────
105        loop {
106            go_lib::select! {
107                recv(shutdown_rx) -> _sig => { break }
108                recv(conn_rx) -> conn => {
109                    match conn {
110                        None         => break,
111                        Some(stream) => {
112                            let h = Arc::clone(&handler);
113                            go_lib::go!(move || {
114                                serve_conn(stream, h, max_header_bytes);
115                            });
116                        }
117                    }
118                }
119            }
120        }
121
122        Ok(())
123    }
124
125    /// Bind, listen, and serve HTTPS/1.1 requests.
126    ///
127    /// Equivalent to `listen_and_serve` but wraps each accepted connection in
128    /// a TLS session using the certificate and key loaded from `cert_file` and
129    /// `key_file` (PEM format).
130    ///
131    /// **Must be called from within a `#[go_lib::main]` body (goroutine context).**
132    /// Port of Go's `(*Server).ListenAndServeTLS`.
133    pub fn listen_and_serve_tls(
134        &self,
135        cert_file: &str,
136        key_file:  &str,
137    ) -> Result<(), HttpError> {
138        let tls_config = crate::tls::server_config(cert_file, key_file)?;
139        let listener   = TcpListener::bind(&self.addr as &str).map_err(HttpError::Io)?;
140
141        let handler: Arc<dyn Handler> = match &self.handler {
142            Some(h) => Arc::clone(h),
143            None    => default_serve_mux(),
144        };
145        let max_header_bytes = self.max_header_bytes;
146
147        let (shutdown_tx, shutdown_rx) = chan::<()>(1);
148        *self.shutdown_tx.lock().unwrap() = Some(shutdown_tx);
149
150        let (conn_tx, conn_rx) = chan::<TcpStream>(8);
151
152        go_lib::go!(move || {
153            loop {
154                match listener.accept() {
155                    Err(_)     => break,
156                    Ok(stream) => {
157                        let _ = std::panic::catch_unwind(
158                            std::panic::AssertUnwindSafe(|| conn_tx.send(stream))
159                        );
160                    }
161                }
162            }
163        });
164
165        loop {
166            go_lib::select! {
167                recv(shutdown_rx) -> _sig => { break }
168                recv(conn_rx) -> conn => {
169                    match conn {
170                        None         => break,
171                        Some(stream) => {
172                            let h   = Arc::clone(&handler);
173                            let cfg = Arc::clone(&tls_config);
174                            go_lib::go!(move || {
175                                serve_conn_tls(stream, cfg, h, max_header_bytes);
176                            });
177                        }
178                    }
179                }
180            }
181        }
182
183        Ok(())
184    }
185
186    /// Signal the server to stop accepting new connections.
187    /// Port of Go's `(*Server).Shutdown` (simplified).
188    pub fn shutdown(&self) {
189        if let Some(tx) = self.shutdown_tx.lock().unwrap().take() {
190            let _ = std::panic::catch_unwind(
191                std::panic::AssertUnwindSafe(|| tx.send(()))
192            );
193        }
194    }
195}
196
197// ---------------------------------------------------------------------------
198// serve_conn — handle one connection through its lifetime
199// ---------------------------------------------------------------------------
200
201/// Serve HTTP/1.1 requests on a single TCP connection.
202///
203/// `TcpStream::try_clone()` (go-lib ≥ 0.5.1) duplicates the underlying fd so
204/// reading and writing can happen on independent halves without unsafe code.
205/// The write half is cloned once at the start; the read half is re-cloned for
206/// each request so `read_request` can take ownership of it (and attach it to
207/// the body reader) while the write half remains available for the response.
208fn serve_conn(
209    stream:           TcpStream,
210    handler:          Arc<dyn Handler>,
211    max_header_bytes: usize,
212) {
213    let remote_addr = stream.peer_addr()
214        .map(|a| a.to_string())
215        .unwrap_or_default();
216
217    // Write half: cloned once, reused across all keep-alive requests.
218    let mut write_half = match stream.try_clone() {
219        Ok(s)  => s,
220        Err(_) => return,
221    };
222
223    loop {
224        // ── Parse the next request ────────────────────────────────────────────
225        // Clone the stream for this request's read half.  try_clone() calls
226        // dup(2)/DuplicateHandle so the clone shares the same TCP socket read
227        // position with `stream`.  The clone is consumed by read_request and
228        // stored inside the body; when the body is dropped the clone's fd is
229        // closed, but the original `stream` (and `write_half`) remain open.
230        let read_half = match stream.try_clone() {
231            Ok(s)  => s,
232            Err(_) => break,
233        };
234
235        let parsed = match read_request(read_half, max_header_bytes) {
236            Ok(p)  => p,
237            Err(_) => break,
238        };
239
240        let connection_close = {
241            let hdr = parsed.header.get("Connection").unwrap_or("").to_ascii_lowercase();
242            hdr.contains("close") || parsed.proto_minor == 0
243        };
244
245        // ── Build Request ─────────────────────────────────────────────────────
246        let req = match build_request(parsed, remote_addr.clone()) {
247            Ok(r)  => r,
248            Err(_) => break,
249        };
250
251        // ── Dispatch ──────────────────────────────────────────────────────────
252        let mut w = ConnResponseWriter::new(&mut write_half);
253        w.header().set("Server", "go-http/0.1");
254        if connection_close {
255            w.header().set("Connection", "close");
256        }
257
258        handler.serve_http(&mut w, &req);
259
260        if w.finish().is_err() {
261            break;
262        }
263
264        if connection_close {
265            break;
266        }
267    }
268}
269
270// ---------------------------------------------------------------------------
271// TLS connection handler
272// ---------------------------------------------------------------------------
273
274/// A raw-pointer `Read` wrapper that "lends" an `impl Read` to `read_request`
275/// without giving up ownership.
276///
277/// # Safety
278/// The pointer must remain valid and exclusively accessible for the lifetime
279/// of the `RawTlsRead` value.  In `serve_conn_tls` this is guaranteed: the
280/// TLS stream lives for the entire connection loop; `RawTlsRead` is created
281/// just before `read_request` and dropped (with the body) before any write
282/// happens.  No concurrent access occurs because everything runs in one
283/// goroutine.
284struct RawTlsRead(*mut dyn Read);
285unsafe impl Send for RawTlsRead {}
286impl Read for RawTlsRead {
287    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
288        unsafe { (*self.0).read(buf) }
289    }
290}
291
292/// Serve HTTP/1.1 requests over a TLS-wrapped `TcpStream`.
293///
294/// Unlike `serve_conn`, TLS sessions cannot be `try_clone`'d — read and write
295/// share the same stateful TLS record layer.  We use `RawTlsRead` to hand the
296/// parser a borrow-as-`'static` pointer to the stream, which is safe because:
297/// - The body is fully consumed (or dropped) before any response bytes are written.
298/// - The stream outlives both the parser and the body within this goroutine.
299fn serve_conn_tls(
300    stream:           TcpStream,
301    tls_config:       Arc<rustls::ServerConfig>,
302    handler:          Arc<dyn Handler>,
303    max_header_bytes: usize,
304) {
305    let remote_addr = stream.peer_addr()
306        .map(|a| a.to_string())
307        .unwrap_or_default();
308
309    let server_conn = match ServerConnection::new(tls_config) {
310        Ok(c)  => c,
311        Err(_) => return,
312    };
313    let mut tls = rustls::StreamOwned::new(server_conn, stream);
314
315    loop {
316        // ── Parse ─────────────────────────────────────────────────────────────
317        // SAFETY: `tls` outlives `read_ptr` and there is no concurrent access.
318        let read_ptr: *mut dyn Read = &mut tls as &mut dyn Read as *mut dyn Read;
319        let parsed = match read_request(RawTlsRead(read_ptr), max_header_bytes) {
320            Ok(p)  => p,
321            Err(_) => break,
322        };
323
324        let connection_close = {
325            let hdr = parsed.header.get("Connection").unwrap_or("").to_ascii_lowercase();
326            hdr.contains("close") || parsed.proto_minor == 0
327        };
328
329        // ── Build Request ─────────────────────────────────────────────────────
330        // Use https:// scheme for the reconstructed URL.
331        let req = match build_request_scheme(parsed, remote_addr.clone(), "https") {
332            Ok(r)  => r,
333            Err(_) => break,
334        };
335
336        // ── Dispatch ──────────────────────────────────────────────────────────
337        // The body has been consumed (or is empty) — safe to use tls for writes.
338        let mut w = ConnResponseWriter::new(&mut tls);
339        w.header().set("Server", "go-http/0.1");
340        if connection_close {
341            w.header().set("Connection", "close");
342        }
343
344        handler.serve_http(&mut w, &req);
345
346        if w.finish().is_err() {
347            break;
348        }
349
350        if connection_close {
351            break;
352        }
353    }
354}
355
356// ---------------------------------------------------------------------------
357// Build a Request from a ParsedRequest
358// ---------------------------------------------------------------------------
359
360fn build_request(
361    parsed:      crate::parse::request::ParsedRequest,
362    remote_addr: String,
363) -> Result<Request, HttpError> {
364    build_request_scheme(parsed, remote_addr, "http")
365}
366
367fn build_request_scheme(
368    parsed:      crate::parse::request::ParsedRequest,
369    remote_addr: String,
370    scheme:      &str,
371) -> Result<Request, HttpError> {
372    let host = if parsed.host.is_empty() { "localhost".to_owned() } else { parsed.host.clone() };
373
374    let url_str = if parsed.request_uri.starts_with("http://")
375        || parsed.request_uri.starts_with("https://")
376    {
377        parsed.request_uri.clone()
378    } else {
379        format!("{scheme}://{host}{}", parsed.request_uri)
380    };
381
382    let url  = Url::parse(&url_str).map_err(|e| HttpError::InvalidUrl(e.to_string()))?;
383    let ctx  = go_lib::context::background();
384    let body = match parsed.body {
385        Body::Empty => None,
386        other       => Some(other),
387    };
388
389    let mut req = Request::new_with_context(&parsed.method, url.as_str(), body, ctx)?;
390    req.proto             = parsed.proto;
391    req.proto_major       = parsed.proto_major;
392    req.proto_minor       = parsed.proto_minor;
393    req.header            = parsed.header;
394    req.host              = parsed.host;
395    req.content_length    = parsed.content_length;
396    req.transfer_encoding = parsed.transfer_encoding;
397    req.remote_addr       = remote_addr;
398    Ok(req)
399}
400
401// ---------------------------------------------------------------------------
402// Free functions
403// ---------------------------------------------------------------------------
404
405/// Bind to `addr`, use `handler` (or `DefaultServeMux` if `None`), and serve.
406///
407/// Must be called from within a `#[go_lib::main]` body (goroutine context).
408/// Port of Go's `http.ListenAndServe`.
409pub fn listen_and_serve(
410    addr:    &str,
411    handler: Option<Arc<dyn Handler>>,
412) -> Result<(), HttpError> {
413    let mut srv = Server::new(addr);
414    srv.handler = handler;
415    srv.listen_and_serve()
416}
417
418/// Bind to `addr`, load TLS credentials, and serve HTTPS.
419///
420/// Must be called from within a `#[go_lib::main]` body (goroutine context).
421/// Port of Go's `http.ListenAndServeTLS`.
422pub fn listen_and_serve_tls(
423    addr:      &str,
424    cert_file: &str,
425    key_file:  &str,
426    handler:   Option<Arc<dyn Handler>>,
427) -> Result<(), HttpError> {
428    let mut srv = Server::new(addr);
429    srv.handler = handler;
430    srv.listen_and_serve_tls(cert_file, key_file)
431}
432
433pub use crate::handler::handle;
434pub use crate::handler::handle_func;
435
436// ---------------------------------------------------------------------------
437// Tests
438// ---------------------------------------------------------------------------
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443
444    #[test]
445    fn build_request_basic() {
446        use crate::parse::request::ParsedRequest;
447        use crate::parse::transfer::Body;
448
449        let mut hdr = crate::header::Header::new();
450        hdr.set("Host", "example.com");
451
452        let pr = ParsedRequest {
453            method:            "GET".into(),
454            request_uri:       "/hello?q=1".into(),
455            proto:             "HTTP/1.1".into(),
456            proto_major:       1,
457            proto_minor:       1,
458            header:            hdr,
459            body:              Body::Empty,
460            content_length:    -1,
461            transfer_encoding: vec![],
462            host:              "example.com".into(),
463        };
464
465        let req = build_request(pr, "10.0.0.1:42".into()).unwrap();
466        assert_eq!(req.method, "GET");
467        assert_eq!(req.host, "example.com");
468        assert_eq!(req.url.path(), "/hello");
469        assert_eq!(req.url.query(), Some("q=1"));
470        assert_eq!(req.remote_addr, "10.0.0.1:42");
471    }
472
473    #[test]
474    fn response_writer_output() {
475        let mut buf = Vec::<u8>::new();
476        let mut w   = ConnResponseWriter::new(&mut buf);
477        w.header().set("Content-Type", "text/plain");
478        w.write_header(200);
479        w.write(b"Hello!").unwrap();
480        w.finish().unwrap();
481
482        let s = String::from_utf8(buf).unwrap();
483        assert!(s.starts_with("HTTP/1.1 200 OK\r\n"), "bad status: {s:?}");
484        assert!(s.contains("Content-Type: text/plain\r\n"));
485        assert!(s.contains("Hello!"));
486        assert!(s.contains("0\r\n\r\n"), "missing chunked terminal: {s:?}");
487    }
488
489}