rust-server - 解説
実装の詳細
HTTPリクエストの構造
リクエストライン: GET /path HTTP/1.1
ヘッダー: Host: example.com
Content-Type: application/json
Content-Length: 13
空行:
ボディ: {"key":"value"}
パースの流れ
// 1. リクエストラインをパース
let line = "GET /api/users HTTP/1.1";
let parts: Vec<&str> = line.split_whitespace().collect();
// parts = ["GET", "/api/users", "HTTP/1.1"]
// 2. ヘッダーをパース(空行まで)
loop {
let line = read_line();
if line.is_empty() { break; }
// "Content-Type: application/json" → ("content-type", "application/json")
}
// 3. Content-Length に基づいてボディを読む
let length = headers.get("content-length").parse()?;
let body = read_exact(length);
レスポンスの生成
// HTTP/1.1 200 OK
// Content-Type: text/html
// Content-Length: 13
//
// Hello, World!
fn send_response(stream: &mut TcpStream, response: &Response) {
// ステータスライン
write!(stream, "HTTP/1.1 {} {}\r\n", status, status_text);
// ヘッダー
for (key, value) in &headers {
write!(stream, "{}: {}\r\n", key, value);
}
// 必須: Content-Length
write!(stream, "Content-Length: {}\r\n", body.len());
// 空行(ヘッダー終了を示す)
write!(stream, "\r\n");
// ボディ
stream.write_all(&body);
}
よくある間違い
1. 改行コードの誤り
// 間違い: LF のみ
write!(stream, "HTTP/1.1 200 OK\n");
// 正しい: CRLF
write!(stream, "HTTP/1.1 200 OK\r\n");
2. Content-Length の省略
// 間違い: Content-Length なし
// → クライアントがボディの終わりを判断できない
// 正しい: 必ず指定
write!(stream, "Content-Length: {}\r\n", body.len());
3. バッファリング問題
// 間違い: flush 忘れ
stream.write_all(&response);
// → バッファに溜まったまま送信されない可能性
// 正しい: 明示的に flush
stream.write_all(&response)?;
stream.flush()?;
パフォーマンス最適化
スレッドプールの使用
use std::sync::mpsc;
struct ThreadPool {
workers: Vec<Worker>,
sender: mpsc::Sender<Job>,
}
// 固定数のワーカースレッドで処理
// 接続ごとにスレッド生成しない
静的ファイルのキャッシュ
use std::collections::HashMap;
use std::sync::RwLock;
lazy_static! {
static ref FILE_CACHE: RwLock<HashMap<String, Vec<u8>>> = RwLock::new(HashMap::new());
}
fn serve_static(path: &str) -> Response {
// キャッシュを確認
if let Some(content) = FILE_CACHE.read().unwrap().get(path) {
return Response::with_body(content.clone());
}
// ファイルを読み込んでキャッシュ
let content = std::fs::read(path)?;
FILE_CACHE.write().unwrap().insert(path.to_string(), content.clone());
Response::with_body(content)
}
発展トピック
Keep-Alive
// 接続を維持して複数リクエストを処理
fn handle_connection(stream: TcpStream) {
loop {
let request = Request::parse(&stream)?;
// Connection: close なら終了
if request.headers.get("connection") == Some(&"close".to_string()) {
break;
}
let response = process(request);
response.headers.insert("Connection", "keep-alive");
response.send(&stream)?;
}
}
非同期I/O(tokio)
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
loop {
let (socket, _) = listener.accept().await?;
tokio::spawn(async move {
handle_connection(socket).await;
});
}
}
ミドルウェアパターン
type Middleware = fn(Request, Handler) -> Response;
fn logging_middleware(req: Request, next: Handler) -> Response {
println!("{} {}", req.method, req.path);
let start = Instant::now();
let response = next(req);
println!("Completed in {:?}", start.elapsed());
response
}
// チェイン実行
fn apply_middleware(req: Request, middlewares: &[Middleware], handler: Handler) -> Response {
if middlewares.is_empty() {
handler(req)
} else {
let (first, rest) = middlewares.split_first().unwrap();
first(req, |r| apply_middleware(r, rest, handler))
}
}