rust-server - 解答

実装コード

HTTPパーサー

// src/http.rs
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Read};
use std::net::TcpStream;

#[derive(Debug, Clone, PartialEq)]
pub enum Method {
    GET,
    POST,
    PUT,
    DELETE,
    HEAD,
    OPTIONS,
}

impl Method {
    pub fn from_str(s: &str) -> Option<Self> {
        match s {
            "GET" => Some(Method::GET),
            "POST" => Some(Method::POST),
            "PUT" => Some(Method::PUT),
            "DELETE" => Some(Method::DELETE),
            "HEAD" => Some(Method::HEAD),
            "OPTIONS" => Some(Method::OPTIONS),
            _ => None,
        }
    }
}

#[derive(Debug)]
pub struct Request {
    pub method: Method,
    pub path: String,
    pub headers: HashMap<String, String>,
    pub body: Vec<u8>,
}

impl Request {
    pub fn parse(stream: &mut TcpStream) -> Result<Self, String> {
        let mut reader = BufReader::new(stream.try_clone().unwrap());

        // リクエストラインを読む
        let mut request_line = String::new();
        reader.read_line(&mut request_line)
            .map_err(|e| e.to_string())?;

        let parts: Vec<&str> = request_line.trim().split_whitespace().collect();
        if parts.len() < 2 {
            return Err("Invalid request line".to_string());
        }

        let method = Method::from_str(parts[0])
            .ok_or_else(|| format!("Unknown method: {}", parts[0]))?;
        let path = parts[1].to_string();

        // ヘッダーを読む
        let mut headers = HashMap::new();
        loop {
            let mut line = String::new();
            reader.read_line(&mut line).map_err(|e| e.to_string())?;

            let line = line.trim();
            if line.is_empty() {
                break;
            }

            if let Some((key, value)) = line.split_once(':') {
                headers.insert(
                    key.trim().to_lowercase(),
                    value.trim().to_string()
                );
            }
        }

        // ボディを読む
        let content_length: usize = headers
            .get("content-length")
            .and_then(|v| v.parse().ok())
            .unwrap_or(0);

        let mut body = vec![0u8; content_length];
        if content_length > 0 {
            reader.read_exact(&mut body).map_err(|e| e.to_string())?;
        }

        Ok(Request {
            method,
            path,
            headers,
            body,
        })
    }
}

レスポンス

// src/response.rs
use std::collections::HashMap;
use std::io::Write;
use std::net::TcpStream;

pub struct Response {
    pub status: u16,
    pub status_text: String,
    pub headers: HashMap<String, String>,
    pub body: Vec<u8>,
}

impl Response {
    pub fn new(status: u16) -> Self {
        let status_text = match status {
            200 => "OK",
            201 => "Created",
            400 => "Bad Request",
            404 => "Not Found",
            500 => "Internal Server Error",
            _ => "Unknown",
        }.to_string();

        Response {
            status,
            status_text,
            headers: HashMap::new(),
            body: Vec::new(),
        }
    }

    pub fn html(content: &str) -> Self {
        let mut res = Response::new(200);
        res.headers.insert("Content-Type".to_string(), "text/html".to_string());
        res.body = content.as_bytes().to_vec();
        res
    }

    pub fn json(content: &str) -> Self {
        let mut res = Response::new(200);
        res.headers.insert("Content-Type".to_string(), "application/json".to_string());
        res.body = content.as_bytes().to_vec();
        res
    }

    pub fn text(content: &str) -> Self {
        let mut res = Response::new(200);
        res.headers.insert("Content-Type".to_string(), "text/plain".to_string());
        res.body = content.as_bytes().to_vec();
        res
    }

    pub fn not_found() -> Self {
        let mut res = Response::new(404);
        res.body = b"404 Not Found".to_vec();
        res
    }

    pub fn send(&self, stream: &mut TcpStream) -> std::io::Result<()> {
        // ステータスライン
        write!(stream, "HTTP/1.1 {} {}\r\n", self.status, self.status_text)?;

        // ヘッダー
        for (key, value) in &self.headers {
            write!(stream, "{}: {}\r\n", key, value)?;
        }
        write!(stream, "Content-Length: {}\r\n", self.body.len())?;
        write!(stream, "\r\n")?;

        // ボディ
        stream.write_all(&self.body)?;
        stream.flush()?;

        Ok(())
    }
}

サーバー

// src/server.rs
use std::collections::HashMap;
use std::net::{TcpListener, TcpStream, SocketAddr};
use std::sync::Arc;
use std::thread;

use crate::http::{Request, Method};
use crate::response::Response;

type Handler = fn(Request) -> Response;

struct Route {
    method: Method,
    path: String,
    handler: Handler,
}

pub struct Server {
    addr: String,
    routes: Vec<Route>,
}

impl Server {
    pub fn new(addr: &str) -> Self {
        Server {
            addr: addr.to_string(),
            routes: Vec::new(),
        }
    }

    pub fn get(&mut self, path: &str, handler: Handler) {
        self.routes.push(Route {
            method: Method::GET,
            path: path.to_string(),
            handler,
        });
    }

    pub fn post(&mut self, path: &str, handler: Handler) {
        self.routes.push(Route {
            method: Method::POST,
            path: path.to_string(),
            handler,
        });
    }

    pub fn run(self) -> std::io::Result<()> {
        let listener = TcpListener::bind(&self.addr)?;
        println!("Server listening on {}", self.addr);

        let routes = Arc::new(self.routes);

        for stream in listener.incoming() {
            match stream {
                Ok(stream) => {
                    let routes = Arc::clone(&routes);
                    thread::spawn(move || {
                        handle_connection(stream, &routes);
                    });
                }
                Err(e) => {
                    eprintln!("Connection failed: {}", e);
                }
            }
        }

        Ok(())
    }
}

fn handle_connection(mut stream: TcpStream, routes: &[Route]) {
    match Request::parse(&mut stream) {
        Ok(request) => {
            let response = find_handler(routes, &request)
                .map(|handler| handler(request.clone()))
                .unwrap_or_else(Response::not_found);

            if let Err(e) = response.send(&mut stream) {
                eprintln!("Failed to send response: {}", e);
            }
        }
        Err(e) => {
            eprintln!("Failed to parse request: {}", e);
        }
    }
}

fn find_handler(routes: &[Route], request: &Request) -> Option<Handler> {
    routes.iter()
        .find(|r| r.method == request.method && r.path == request.path)
        .map(|r| r.handler)
}

メイン

// src/main.rs
mod http;
mod response;
mod server;

use server::Server;
use response::Response;

fn main() {
    let mut server = Server::new("127.0.0.1:8080");

    server.get("/", |_| {
        Response::html("<h1>Welcome to Rust Server!</h1>")
    });

    server.get("/api/health", |_| {
        Response::json(r#"{"status": "ok"}"#)
    });

    server.post("/api/echo", |req| {
        let body = String::from_utf8_lossy(&req.body);
        Response::text(&format!("Echo: {}", body))
    });

    server.run().unwrap();
}