基于Tokio的自定义HTTP服务器:浏览器无法接收响应问题排查
问题:Tokio实现的HTTP服务器无法向浏览器返回响应
我使用Tokio框架实现了一个简易HTTP服务器,服务器可以接收到浏览器的请求,但浏览器却无法收到服务器的响应。
依赖配置:
# Cargo.toml [dependencies] tokio = { version = "1", features = ["full"] }
实现代码:
use tokio::{self, net}; use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::net::TcpStream; async fn process(mut stream: TcpStream) { let mut buf = [0u8; 4096]; loop { let n = stream.read(&mut buf).await.unwrap(); if n == 0 { break; } let s = String::from_utf8_lossy(&buf[0..n]).to_string(); print!("{}", s); if s.ends_with("\r\n\r\n") { println!("request received"); break; } } let response_str = "HTTP/1.1 200 OK <!DOCTYPE html> <html lang=\"en\"> <head> <meta charset=\"udf-8\"> <title>Hello!</title> </head> <body> HELLO </body> </html> "; println!("response"); // browser doesn't receive the response that this line of code send. stream.write(response_str.as_bytes()).await.unwrap(); stream.flush().await.unwrap(); println!("response DONE"); } #[tokio::main] async fn main() { let listener = net::TcpListener::bind("127.0.0.1:9998").await.unwrap(); loop { let (stream, _) = listener.accept().await.unwrap(); tokio::spawn(async move{ process(stream).await; }); } }
请问我的代码中存在什么bug吗?
问题分析与修复方案
你的代码存在两个核心问题,导致浏览器无法接收响应:
1. HTTP响应格式不符合规范
HTTP协议明确要求:
- 响应头与响应体之间必须用
\r\n\r\n分隔 - 必须包含关键响应头(比如
Content-Type,告知浏览器响应内容类型;Content-Length,告知浏览器响应体长度)
当前的响应字符串直接在状态行后拼接HTML内容,既没有正确的头体分隔,也缺少必要的响应头,浏览器无法识别这是合法的HTTP响应。
2. 请求读取逻辑存在缺陷
TCP是流式协议,请求数据可能被拆分成多个数据包分批到达。当前代码仅判断单次读取的片段是否以\r\n\r\n结尾,若请求结束标记被拆分到两次读取中,判断条件永远无法满足,代码会一直卡在读取循环里,根本无法进入发送响应的步骤。
修复后的完整代码
use tokio::{self, net}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; async fn process(mut stream: TcpStream) { let mut buf = [0u8; 4096]; // 累积读取的请求内容,避免拆分读取导致的判断失效 let mut request = String::new(); loop { let n = stream.read(&mut buf).await.unwrap(); if n == 0 { break; } let chunk = String::from_utf8_lossy(&buf[0..n]).to_string(); print!("{}", chunk); request.push_str(&chunk); // 判断累积的请求内容是否包含请求结束标记 if request.contains("\r\n\r\n") { println!("request received"); break; } } // 构造符合HTTP规范的响应:添加必要头,确保头和体用\r\n\r\n分隔 let html_body = r#"<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Hello!</title> </head> <body> HELLO </body> </html>"#; let content_length = html_body.len(); let response_str = format!( "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: {}\r\n\r\n{}", content_length, html_body ); println!("response"); stream.write(response_str.as_bytes()).await.unwrap(); stream.flush().await.unwrap(); println!("response DONE"); } #[tokio::main] async fn main() { let listener = net::TcpListener::bind("127.0.0.1:9998").await.unwrap(); loop { let (stream, _) = listener.accept().await.unwrap(); tokio::spawn(async move{ process(stream).await; }); } }
关键修复点说明
- 请求读取逻辑:改用String累积所有读取到的请求数据,判断累积内容是否包含
\r\n\r\n,确保即使请求分多次到达也能正确识别结束。 - 响应格式修正:
- 添加
Content-Type头,明确告知浏览器响应是UTF-8编码的HTML文本 - 添加
Content-Length头,告知浏览器响应体的长度 - 用
\r\n\r\n分隔响应头和响应体,严格符合HTTP协议规范 - 修正了原HTML中
udf-8的拼写错误为utf-8
- 添加
内容的提问来源于stack exchange,提问作者Keith
相关产品推荐
相关产品推荐

