-
Notifications
You must be signed in to change notification settings - Fork 649
Description
Describe the bug
The local development server implemented in cargo xtask serve (xtask/src/main.rs:cmd_serve()) is currently fully synchronous and single-threaded. It processes incoming HTTP connections sequentially with a blocking .read() call.
When modern browsers attempt to load a locally served mdBook page, they open multiple concurrent connections to fetch HTML, CSS, JavaScript, and font assets simultaneously. However, because the server handles only one connection at a time, these requests enqueue. Furthermore, the server does not send a Connection: close header but silently drops the socket after writing the response.
This causes browsers to hang onto Keep-Alive connections indefinitely, leading to artificially slow page rendering, missing CSS/JS if the connections time out, or "Connection Reset" errors. It behaves like a local Denial of Service (DoS) resulting from synchronous I/O.
Steps To Reproduce
- Run the local preview server with:
cargo xtask serve - Open a web browser and navigate to
http://127.0.0.1:3000. - Open the browser's developer tools (Network tab) and disable caching.
- Hard-refresh the page a few times.
- Notice that some assets (CSS/JS files) take long to start downloading or fail to load completely.
The Solution / Code Fix
To fix this natively without adding new heavy dependencies, we simply need to:
- Spawn a thread for each incoming connection (
std::thread::spawn). - Add
Connection: closeto the HTTP response headers to inform the browser that the socket won't be reused for Keep-Alive requests.
Here is the exact code patch for xtask/src/main.rs inside the cmd_serve function:
for stream in listener.incoming() {
let Ok(mut stream) = stream else { continue };
// Clone the pathbuf so we can move it into the thread
let site_canon = site_canon.clone();
std::thread::spawn(move || {
let mut buf = [0u8; 4096];
let n = stream.read(&mut buf).unwrap_or(0);
let request = String::from_utf8_lossy(&buf[..n]);
let path = request
.lines()
.next()
.and_then(|line| line.split_whitespace().nth(1))
.unwrap_or("/");
if let Some(file_path) = resolve_site_file(&site_canon, path) {
let body = std::fs::read(&file_path).unwrap_or_default();
let mime = guess_mime(&file_path);
// ADDED: Connection: close
let header = format!(
"HTTP/1.1 200 OK\r\nContent-Type: {mime}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
body.len()
);
let _ = stream.write_all(header.as_bytes());
let _ = stream.write_all(&body);
} else {
let body = b"404 Not Found";
// ADDED: Connection: close
let header = format!(
"HTTP/1.1 404 Not Found\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
body.len()
);
let _ = stream.write_all(header.as_bytes());
let _ = stream.write_all(body);
}
});
}
**Assign this isuue i will solve.**