From d6e53c0cf07bdcd4024a0a8398b87bb13466a5f3 Mon Sep 17 00:00:00 2001 From: ksmail13 Date: Fri, 26 Dec 2025 17:46:31 +0900 Subject: [PATCH 1/3] add limit for http header size --- src/args.rs | 2 ++ src/http/handler.rs | 31 ++++++++++++++++++++++++++++++- src/http/http.rs | 29 +++++++++++++++++++++-------- src/http/value.rs | 5 +++-- src/main.rs | 2 +- 5 files changed, 57 insertions(+), 12 deletions(-) diff --git a/src/args.rs b/src/args.rs index 57bc4ff..319ad24 100644 --- a/src/args.rs +++ b/src/args.rs @@ -13,4 +13,6 @@ pub struct Args { pub worker: u32, #[arg(short, long, default_value_t = 500)] pub timeout_ms: u64, + #[arg(long, default_value_t = 8196)] + pub max_header_size: usize, } diff --git a/src/http/handler.rs b/src/http/handler.rs index d3a0b92..88d87f6 100644 --- a/src/http/handler.rs +++ b/src/http/handler.rs @@ -1,5 +1,34 @@ -use crate::http::{request::HttpRequest, response::HttpResponse}; +use std::{io::Write, time::SystemTime}; + +use crate::{ + http::{ + header::{HttpHeaderValue, content_type, date, server}, + request::HttpRequest, + response::{HeaderSetter, HttpResponse}, + value::{Error, HttpResponseCode}, + }, + process, +}; pub trait Handler { fn handle(&self, req: &mut HttpRequest, res: &mut HttpResponse); } + +pub trait ErrorHandler { + fn handle(&self, req: &mut HttpRequest, res: &mut HttpResponse, err: &Error) -> process::Error; +} + +struct DefaultErrorHandler {} + +impl ErrorHandler for DefaultErrorHandler { + fn handle(&self, req: &mut HttpRequest, res: &mut HttpResponse, err: &Error) -> process::Error { + res.set_response_code(HttpResponseCode::BadRequest); + res.set_header(&server(HttpHeaderValue::Str("server_rs"))); + res.set_header(&content_type(HttpHeaderValue::Str("text/plain"))); + res.set_header(&date(SystemTime::now())); + let _ = res.write("Invalid request".as_bytes()); + let _ = res.flush(); + + return process::Error::ParseFail(err.to_string()); + } +} diff --git a/src/http/http.rs b/src/http/http.rs index 2c78d22..2c36b51 100644 --- a/src/http/http.rs +++ b/src/http/http.rs @@ -1,13 +1,13 @@ use std::{ collections::HashMap, io::{BufRead, BufReader, Write}, - net::TcpStream, + net::{SocketAddr, TcpStream}, time::{Duration, SystemTime}, }; use crate::{ http::{ - handler::Handler, + handler::{ErrorHandler, Handler}, header::{HttpHeaderValue, content_type, date, server}, request::HttpRequest, response::{HeaderSetter, HttpResponse}, @@ -16,13 +16,16 @@ use crate::{ process::{self, Process}, }; -pub struct Http1 { +pub struct Http1 { + max_header_length: usize, handler: T, + error_handler: E, } -impl Process for Http1 +impl Process for Http1 where T: Handler, + E: ErrorHandler, { fn process( &self, @@ -36,7 +39,7 @@ where log::trace!("Write timeout: {:?}", stream.write_timeout()); let mut reader = BufReader::new(&stream); - let header_res: Result<(usize, Vec), Error> = self.read_header(&mut reader); + let header_res: Result<(usize, Vec), Error> = self.read_header(client_addr, &mut reader); if let Err(err) = header_res { return Err(process::Error::IoFail(format!("Read header failed: ({})", err))); } @@ -77,16 +80,18 @@ where } } -impl Http1 +impl Http1 where T: Handler, + E: ErrorHandler, { - pub fn new(handler: T) -> Self { - return Http1 { handler }; + pub fn new(max_header_length: usize, handler: T, error_handler: E) -> Self { + return Http1 { max_header_length, handler, error_handler }; } fn read_header<'a>( &self, + client_addr: &SocketAddr, reader: &mut BufReader<&'a TcpStream>, ) -> Result<(usize, Vec), Error> { let mut res = vec![]; @@ -99,6 +104,10 @@ where } readed += result.unwrap(); + if readed > self.max_header_length { + return Err(Error::BadRequest(client_addr.clone(), "header size limit exceed")); + } + while buf .chars() .nth(0) @@ -185,6 +194,10 @@ where return header_map; } + + fn error_handler_for_invalid_request(res: &mut HttpResponse, err: Error) { + + } } fn parse_url(query: &str) -> (String, HashMap<&str, Vec<&str>>) { diff --git a/src/http/value.rs b/src/http/value.rs index f35db92..6f7fe43 100644 --- a/src/http/value.rs +++ b/src/http/value.rs @@ -1,4 +1,4 @@ -use std::{fmt::Display, hash::Hash}; +use std::{fmt::Display, hash::Hash, net::SocketAddr}; pub enum HttpVersion { Http10, @@ -139,10 +139,10 @@ impl HttpResponseCode { #[allow(dead_code)] #[derive(Debug, Clone)] pub enum Error { - #[allow(dead_code)] ParseFail(String), ReadFail(String), WriteFail(String), + BadRequest(SocketAddr, &'static str), } impl std::fmt::Display for Error { @@ -151,6 +151,7 @@ impl std::fmt::Display for Error { Error::ParseFail(m) => ("parse fail", m), Error::ReadFail(m) => ("read fail", m), Error::WriteFail(m) => ("write fail", m), + Error::BadRequest(remote, msg) => ("bad request", &format!("{} {}", remote, msg)), }; return f.write_fmt(format_args!("HttpError: [{}] {}", name.0, name.1)); diff --git a/src/main.rs b/src/main.rs index f1829f3..bfd4374 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,7 +69,7 @@ fn main() { host: arg.host.clone(), port: arg.port, worker: arg.worker, - process: Rc::new(Http1::new(SimpleHandler)), + process: Rc::new(Http1::new(arg.max_header_size, SimpleHandler)), }]; let mut server = Server::new(ServerArgs { From b6ae8f481684003cabb6f1dae38a532dd31930bf Mon Sep 17 00:00:00 2001 From: ksmail13 Date: Mon, 29 Dec 2025 03:12:00 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=ED=97=A4=EB=8D=94=20=ED=81=AC=EA=B8=B0?= =?UTF-8?q?=EC=97=90=20=EC=A0=9C=EC=95=BD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/http/handler.rs | 31 +------------------------- src/http/http.rs | 53 +++++++++++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 53 deletions(-) diff --git a/src/http/handler.rs b/src/http/handler.rs index 88d87f6..d3a0b92 100644 --- a/src/http/handler.rs +++ b/src/http/handler.rs @@ -1,34 +1,5 @@ -use std::{io::Write, time::SystemTime}; - -use crate::{ - http::{ - header::{HttpHeaderValue, content_type, date, server}, - request::HttpRequest, - response::{HeaderSetter, HttpResponse}, - value::{Error, HttpResponseCode}, - }, - process, -}; +use crate::http::{request::HttpRequest, response::HttpResponse}; pub trait Handler { fn handle(&self, req: &mut HttpRequest, res: &mut HttpResponse); } - -pub trait ErrorHandler { - fn handle(&self, req: &mut HttpRequest, res: &mut HttpResponse, err: &Error) -> process::Error; -} - -struct DefaultErrorHandler {} - -impl ErrorHandler for DefaultErrorHandler { - fn handle(&self, req: &mut HttpRequest, res: &mut HttpResponse, err: &Error) -> process::Error { - res.set_response_code(HttpResponseCode::BadRequest); - res.set_header(&server(HttpHeaderValue::Str("server_rs"))); - res.set_header(&content_type(HttpHeaderValue::Str("text/plain"))); - res.set_header(&date(SystemTime::now())); - let _ = res.write("Invalid request".as_bytes()); - let _ = res.flush(); - - return process::Error::ParseFail(err.to_string()); - } -} diff --git a/src/http/http.rs b/src/http/http.rs index 2c36b51..dbdcbf8 100644 --- a/src/http/http.rs +++ b/src/http/http.rs @@ -7,7 +7,7 @@ use std::{ use crate::{ http::{ - handler::{ErrorHandler, Handler}, + handler::Handler, header::{HttpHeaderValue, content_type, date, server}, request::HttpRequest, response::{HeaderSetter, HttpResponse}, @@ -16,16 +16,14 @@ use crate::{ process::{self, Process}, }; -pub struct Http1 { +pub struct Http1 { max_header_length: usize, handler: T, - error_handler: E, } -impl Process for Http1 +impl Process for Http1 where T: Handler, - E: ErrorHandler, { fn process( &self, @@ -39,9 +37,14 @@ where log::trace!("Write timeout: {:?}", stream.write_timeout()); let mut reader = BufReader::new(&stream); - let header_res: Result<(usize, Vec), Error> = self.read_header(client_addr, &mut reader); + let header_res: Result<(usize, Vec), Error> = + self.read_header(client_addr, &mut reader); if let Err(err) = header_res { - return Err(process::Error::IoFail(format!("Read header failed: ({})", err))); + self.error_response_for_invalid_request(&stream); + return Err(process::Error::IoFail(format!( + "Read header failed: ({})", + err + ))); } let (header_readed, headers) = header_res.unwrap(); @@ -49,15 +52,7 @@ where let res_request: Result, Error> = self.init_request(client_addr, &headers, reader); if let Err(err) = res_request { - let mut response = HttpResponse::new(HttpVersion::default(), &stream); - - response.set_response_code(HttpResponseCode::BadRequest); - response.set_header(&server(HttpHeaderValue::Str("server_rs"))); - response.set_header(&content_type(HttpHeaderValue::Str("text/plain"))); - response.set_header(&date(SystemTime::now())); - let _ = response.write("Invalid request".as_bytes()); - let _ = response.flush(); - + self.error_response_for_invalid_request(&stream); return Err(process::Error::ParseFail(err.to_string())); } @@ -80,13 +75,15 @@ where } } -impl Http1 +impl Http1 where T: Handler, - E: ErrorHandler, { - pub fn new(max_header_length: usize, handler: T, error_handler: E) -> Self { - return Http1 { max_header_length, handler, error_handler }; + pub fn new(max_header_length: usize, handler: T) -> Self { + return Http1 { + max_header_length, + handler, + }; } fn read_header<'a>( @@ -105,7 +102,10 @@ where readed += result.unwrap(); if readed > self.max_header_length { - return Err(Error::BadRequest(client_addr.clone(), "header size limit exceed")); + return Err(Error::BadRequest( + client_addr.clone(), + "header size limit exceed", + )); } while buf @@ -195,8 +195,15 @@ where return header_map; } - fn error_handler_for_invalid_request(res: &mut HttpResponse, err: Error) { - + fn error_response_for_invalid_request(&self, stream: &TcpStream) { + let mut response = HttpResponse::new(HttpVersion::default(), stream); + + response.set_response_code(HttpResponseCode::BadRequest); + response.set_header(&server(HttpHeaderValue::Str("server_rs"))); + response.set_header(&content_type(HttpHeaderValue::Str("text/plain"))); + response.set_header(&date(SystemTime::now())); + let _ = response.write("Invalid request".as_bytes()); + let _ = response.flush(); } } From 2c9353da2eaff63add532dd9238f71153916a382 Mon Sep 17 00:00:00 2001 From: ksmail13 Date: Mon, 29 Dec 2025 03:14:46 +0900 Subject: [PATCH 3/3] =?UTF-8?q?readme=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e28da9b..00c2935 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,8 @@ Run --host 127.0.0.1 \ --port 8080 \ --worker 4 \ - --timeout-ms 2000 + --timeout-ms 2000 \ + --max-header-size 8192 ``` This command starts a server listening on `127.0.0.1:8080` with 4 preforked worker processes and a 2‑second accept timeout. @@ -83,4 +84,3 @@ Tests cover: - URL parsing (`http/http.rs`) - Echo server logic (`process/echo.rs`) - Worker manager integration (`worker/manager.rs`) -