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();
}