From c2eb37924642ce6f72425dfbc9cdb33caaf65715 Mon Sep 17 00:00:00 2001 From: ksmail13 Date: Wed, 11 Mar 2026 01:34:26 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[Refactor]=20HttpHeader=EB=A5=BC=20enum?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=ED=95=98=EA=B3=A0=20Htt?= =?UTF-8?q?pResponseHeader=EB=A1=9C=20=ED=97=A4=EB=8D=94=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=A9=EC=8B=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HttpHeader를 struct에서 enum(StrKey, StringKey)으로 전환하여 유연성 확보 - HttpResponse의 HashMap 기반 헤더 관리를 성능 최적화된 HttpResponseHeader로 대체 - set_header 시 참조 대신 소유권을 넘기도록 변경하여 불필요한 복사 방지 --- httprs-bin/src/main.rs | 2 +- httprs/src/http/header.rs | 249 ++++++++++++++++++++++++++++++++---- httprs/src/http/mod.rs | 8 +- httprs/src/http/response.rs | 55 ++++---- 4 files changed, 259 insertions(+), 55 deletions(-) diff --git a/httprs-bin/src/main.rs b/httprs-bin/src/main.rs index 791d659..4eae18b 100644 --- a/httprs-bin/src/main.rs +++ b/httprs-bin/src/main.rs @@ -28,7 +28,7 @@ const BUF_SIZE: usize = 1024; impl Handler for SimpleHandler { fn handle(&self, req: &mut HttpRequest, res: &mut HttpResponse) { res.set_response_code(HttpResponseCode::Ok); - res.set_header(&content_type(HttpHeaderValue::Str("text/plain"))); + res.set_header(content_type(HttpHeaderValue::Str("text/plain"))); if let Err(e) = writeln!(res, "Echo response") { log::error!("error {}", e); diff --git a/httprs/src/http/header.rs b/httprs/src/http/header.rs index 606e92b..c0d46cd 100644 --- a/httprs/src/http/header.rs +++ b/httprs/src/http/header.rs @@ -1,4 +1,4 @@ -use std::fmt::Write; +use std::fmt::{Debug, Write}; use std::rc::Rc; use std::time::SystemTime; @@ -92,42 +92,35 @@ impl ToString for HeaderValueTime { } #[allow(dead_code)] -#[derive(Debug)] -pub struct HttpHeader { - key_str: Option<&'static str>, - key_string: Option>, - value: Rc, +#[derive(Debug, Clone)] +pub enum HttpHeader { + StrKey(&'static str, Rc), + StringKey(Rc, Rc), } impl HttpHeader { - pub fn key_str(&self) -> Option<&'static str> { - self.key_str - } - - pub fn key_string(&self) -> Option> { - self.key_string.clone() + pub fn key_str(&self) -> &str { + match self { + HttpHeader::StrKey(key, _) => key, + HttpHeader::StringKey(key, _) => key.as_str(), + } } pub fn value(&self) -> &Rc { - &self.value + match self { + HttpHeader::StrKey(_, value) => value, + HttpHeader::StringKey(_, value) => value, + } } } fn from_str_key(key: &'static str, value: Rc) -> HttpHeader { - HttpHeader { - key_str: Some(key), - key_string: None, - value: value, - } + HttpHeader::StrKey(key, value) } #[allow(dead_code)] fn from_string_key(key: String, value: Rc) -> HttpHeader { - HttpHeader { - key_str: None, - key_string: Some(Rc::new(key)), - value: value, - } + HttpHeader::StringKey(Rc::new(key), value) } // common @@ -195,6 +188,144 @@ pub fn www_authenticate(value: HttpHeaderValue) -> HttpHeader { from_str_key("WWW-Authenticate", value.to_value()) } +#[derive(Debug)] +pub struct HttpResponseHeader { + defined: [Option; Self::HEADER_SIZE], + undefined: Vec, +} + +impl HttpResponseHeader { + const HEADER_SIZE: usize = 16; + const HEADER_DEFINED_SIZE: usize = 10; + const HEADER_LIST: [&'static str; Self::HEADER_SIZE] = [ + "Allow", + "Content-Encoding", + "Content-Length", + "Content-Type", + "Date", + "Expires", + "Last-Modified", + "Location", + "Server", + "WWW-Authenticate", // http1.0 + "", + "", + "", + "", + "", + "", + ]; + + pub fn new() -> Self { + Self { + defined: [const { None }; Self::HEADER_SIZE], + undefined: Vec::new(), + } + } + + pub fn add(&mut self, header: HttpHeader) { + if let Some(index) = Self::HEADER_LIST + .iter() + .position(|&k| k.eq_ignore_ascii_case(header.key_str())) + { + self.defined[index] = Some(header); + return; + } + + if let Some(idx) = self + .undefined + .iter() + .position(|h| h.key_str().eq_ignore_ascii_case(header.key_str())) + { + self.undefined[idx] = header; + return; + } + + self.undefined.push(header); + } + + pub fn set(&mut self, key: &'static str, value: Rc) { + if let Some(index) = Self::HEADER_LIST + .iter() + .position(|&k| k.eq_ignore_ascii_case(key)) + { + self.defined[index] = Some(HttpHeader::StrKey(key, value)); + return; + } + + if let Some(idx) = self + .undefined + .iter() + .position(|h| h.key_str().eq_ignore_ascii_case(key)) + { + self.undefined[idx] = HttpHeader::StrKey(key, value); + return; + } + + self.undefined.push(HttpHeader::StrKey(key, value)); + } + + pub fn get(&self, key: &'static str) -> Option> { + if let Some(index) = Self::HEADER_LIST + .iter() + .position(|&k| k.eq_ignore_ascii_case(key)) + { + return self.defined[index].as_ref().map(|h| h.value().clone()); + } + + if let Some(idx) = self + .undefined + .iter() + .position(|h| h.key_str().eq_ignore_ascii_case(key)) + { + return Some(self.undefined[idx].value().clone()); + } + + None + } +} + +pub struct HttpResponseHeaderIterator<'a> { + headers: &'a HttpResponseHeader, + index: usize, +} + +impl<'a> Iterator for HttpResponseHeaderIterator<'a> { + type Item = &'a HttpHeader; + + fn next(&mut self) -> Option<&'a HttpHeader> { + while self.index < HttpResponseHeader::HEADER_DEFINED_SIZE { + let value = &self.headers.defined[self.index]; + self.index += 1; + let peek = value.as_ref(); + if peek.is_some() { + return peek; + } + } + + let header = self + .headers + .undefined + .get(self.index - HttpResponseHeader::HEADER_DEFINED_SIZE); + if header.is_some() { + self.index += 1; + } + return header; + } +} + +impl<'a> IntoIterator for &'a HttpResponseHeader { + type Item = &'a HttpHeader; + type IntoIter = HttpResponseHeaderIterator<'a>; + + fn into_iter(self) -> HttpResponseHeaderIterator<'a> { + HttpResponseHeaderIterator { + headers: self, + index: 0, + } + } +} + #[cfg(test)] mod test { use std::time::SystemTime; @@ -215,4 +346,76 @@ mod test { HeaderValueTime::from_system_time(SystemTime::now()).to_string() ) } + + #[test] + fn test_http_response_header() { + use super::{HttpHeaderValue, HttpResponseHeader}; + let mut headers = HttpResponseHeader::new(); + + // 1. 정의된 헤더 테스트 (Predefined) + let content_type = HttpHeaderValue::Str("text/plain").to_value(); + headers.set("Content-Type", content_type.clone()); + assert_eq!( + headers.get("Content-Type").unwrap().to_string().as_ref(), + "text/plain" + ); + + // 대소문자 구분 없음 확인 + assert_eq!( + headers.get("content-type").unwrap().to_string().as_ref(), + "text/plain" + ); + + // 2. 정의되지 않은 커스텀 헤더 테스트 (Undefined) + let x_custom = HttpHeaderValue::Str("custom-value").to_value(); + headers.set("X-Custom-Header", x_custom.clone()); + assert_eq!( + headers.get("X-Custom-Header").unwrap().to_string().as_ref(), + "custom-value" + ); + + // 커스텀 헤더 대소문자 구분 없음 확인 + assert_eq!( + headers.get("x-custom-header").unwrap().to_string().as_ref(), + "custom-value" + ); + + // 3. 덮어쓰기 테스트 + let new_content_type = HttpHeaderValue::Str("application/json").to_value(); + headers.set("CONTENT-TYPE", new_content_type.clone()); + assert_eq!( + headers.get("Content-Type").unwrap().to_string().as_ref(), + "application/json" + ); + + // 4. 존재하지 않는 헤더 + assert!(headers.get("Non-Existent").is_none()); + } + #[test] + fn test_http_response_header_iterator() { + use super::{HttpHeaderValue, HttpResponseHeader}; + let mut headers = HttpResponseHeader::new(); + + // 정의된 헤더 2개 추가 + headers.set( + "Content-Type", + HttpHeaderValue::Str("application/json").to_value(), + ); + headers.set("Server", HttpHeaderValue::Str("httprs/1.0").to_value()); + + // 정의되지 않은 헤더 2개 추가 + headers.set("X-Custom-1", HttpHeaderValue::Str("val1").to_value()); + headers.set("X-Custom-2", HttpHeaderValue::Str("val2").to_value()); + + let result: Vec<_> = headers.into_iter().collect(); + + // 총 4개의 헤더가 있어야 함 + assert_eq!(result.len(), 4); + + // 결과 확인 (순서는 Defined -> Undefined 순) + assert_eq!(result[0].key_str(), "Content-Type"); + assert_eq!(result[1].key_str(), "Server"); + assert_eq!(result[2].key_str(), "X-Custom-1"); + assert_eq!(result[3].key_str(), "X-Custom-2"); + } } diff --git a/httprs/src/http/mod.rs b/httprs/src/http/mod.rs index 9e6b273..0110241 100644 --- a/httprs/src/http/mod.rs +++ b/httprs/src/http/mod.rs @@ -72,7 +72,7 @@ where server::Error::ParseFail(e.to_string()) })?; let mut response = HttpResponse::from_request(&request, Box::new(&stream)); - response.set_header(&server(HttpHeaderValue::Str("server_rs"))); + response.set_header(server(HttpHeaderValue::Str("server_rs"))); self.handler.handle(&mut request, &mut response); @@ -223,9 +223,9 @@ where let mut response = HttpResponse::new(HttpVersion::default(), Box::new(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())); + 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(); } diff --git a/httprs/src/http/response.rs b/httprs/src/http/response.rs index 473dfdc..cd49b26 100644 --- a/httprs/src/http/response.rs +++ b/httprs/src/http/response.rs @@ -8,7 +8,7 @@ use std::{ use flate2::{Compression, write::GzEncoder}; use crate::http::{ - header::{HttpHeader, content_length, date}, + header::{HttpHeader, HttpResponseHeader, content_length, date}, request::HttpRequest, value::{HttpMethod, HttpResponseCode, HttpVersion}, }; @@ -16,8 +16,7 @@ use crate::http::{ pub struct HttpResponse<'a> { version: HttpVersion, code: HttpResponseCode, - header: HashMap<&'static str, Rc>, - header_str: HashMap, Rc>, + header: HttpResponseHeader, writer: Box, buffer: Vec>, header_only: bool, @@ -29,8 +28,7 @@ impl<'a> HttpResponse<'a> { return Self { version: version, code: HttpResponseCode::Ok, - header: HashMap::new(), - header_str: HashMap::new(), + header: HttpResponseHeader::new(), writer: writer, buffer: vec![], header_only: false, @@ -42,8 +40,7 @@ impl<'a> HttpResponse<'a> { return Self { version: request.version(), code: HttpResponseCode::Ok, - header: HashMap::new(), - header_str: HashMap::new(), + header: HttpResponseHeader::new(), writer: writer, buffer: vec![], header_only: request.method() == HttpMethod::HEAD, @@ -64,16 +61,29 @@ impl<'a> Write for HttpResponse<'a> { } fn flush(&mut self) -> std::io::Result<()> { - self.set_header(&content_length( + self.set_header(content_length( self.buffer.iter().map(|b| b.len()).sum::(), )); - self.set_header(&date(SystemTime::now())); + self.set_header(date(SystemTime::now())); let mut buf_writer = BufWriter::with_capacity(1 << 15, &mut self.writer); - let mut written = buf_writer.write(format!("{} {}", self.version, self.code).as_bytes())?; - written += buf_writer.write(LINE_END)?; - written += Self::write_header(&self.header, &self.header_str, &mut buf_writer)?; + let mut header_lines: Vec = vec![]; + let status_line = format!("{} {}", self.version.clone(), self.code); + header_lines.push(status_line); + + self.header.into_iter().for_each(|i| { + let k = i.key_str(); + let v = i.value().to_string(); + header_lines.push(format!("{}: {}", k, v)); + }); + + let header_slices: Vec> = header_lines + .iter() + .flat_map(|h| [IoSlice::new(h.as_bytes()), IoSlice::new(LINE_END)]) + .collect(); + let written = buf_writer.write_vectored(&header_slices)?; + buf_writer.write(LINE_END)?; if self.header_only { buf_writer.flush()?; @@ -102,14 +112,9 @@ pub trait HeaderSetter { fn set_header(&mut self, header: T); } -impl<'a> HeaderSetter<&HttpHeader> for HttpResponse<'a> { - fn set_header(&mut self, header: &HttpHeader) { - let value = header.value().clone(); - if let Some(key) = header.key_str() { - self.header.insert(key, value); - } else if let Some(key) = header.key_string() { - self.header_str.insert(key, value); - } +impl<'a> HeaderSetter for HttpResponse<'a> { + fn set_header(&mut self, header: HttpHeader) { + self.header.add(header); } } @@ -169,7 +174,7 @@ impl HttpResponse<'_> { #[cfg(test)] mod tests { use super::*; - use crate::http::header::HttpHeaderValue; + use crate::http::header::{HttpHeaderValue, content_encoding}; use flate2::read::GzDecoder; use std::io::Read; @@ -184,9 +189,7 @@ mod tests { HttpResponse::new(HttpVersion::default(), Box::new(&mut output_buffer)); // Content-Encoding 헤더를 gzip으로 설정 - response.set_header(&crate::http::header::content_encoding( - HttpHeaderValue::Str("gzip"), - )); + response.set_header(content_encoding(HttpHeaderValue::Str("gzip"))); // 테스트 데이터 작성 let test_data = b"Hello, this is a test message for gzip encoding!"; @@ -255,9 +258,7 @@ mod tests { let mut response = HttpResponse::new(HttpVersion::default(), Box::new(&mut output_buffer)); - response.set_header(&crate::http::header::content_encoding( - HttpHeaderValue::Str("gzip"), - )); + response.set_header(content_encoding(HttpHeaderValue::Str("gzip"))); // 반복되는 큰 데이터 (압축이 잘 될 것으로 예상) response.write(test_data.as_bytes()).unwrap(); From c328576ad9cc3e6842446678568ec3da80824cc4 Mon Sep 17 00:00:00 2001 From: ksmail13 Date: Sat, 14 Mar 2026 00:13:56 +0900 Subject: [PATCH 2/2] refactoring header - cleanup unused functions - ToString to ToStr and return &str - refactoring response the response header --- httprs/src/http/header.rs | 91 ++++++++++++++++++------------------ httprs/src/http/response.rs | 93 ++++++++++--------------------------- 2 files changed, 68 insertions(+), 116 deletions(-) diff --git a/httprs/src/http/header.rs b/httprs/src/http/header.rs index c0d46cd..9a1082b 100644 --- a/httprs/src/http/header.rs +++ b/httprs/src/http/header.rs @@ -5,8 +5,8 @@ use std::time::SystemTime; use crate::http::value::WeightedValue; use crate::util::date::Date; -pub trait ToString: std::fmt::Debug { - fn to_string(&self) -> Rc; +pub trait ToStr: std::fmt::Debug { + fn to_str(&self) -> &str; } #[allow(dead_code)] @@ -16,7 +16,7 @@ pub enum HttpHeaderValue { } impl HttpHeaderValue { - pub fn to_value(&self) -> Rc { + pub fn to_value(&self) -> Rc { match self { HttpHeaderValue::String(string) => Rc::new(HeaderValueString { string: Rc::new(string.clone()), @@ -31,9 +31,9 @@ struct HeaderValueStr { str: &'static str, } -impl ToString for HeaderValueStr { - fn to_string(&self) -> Rc { - Rc::new(self.str.to_string()) +impl ToStr for HeaderValueStr { + fn to_str(&self) -> &str { + self.str } } @@ -42,9 +42,9 @@ struct HeaderValueString { string: Rc, } -impl ToString for HeaderValueString { - fn to_string(&self) -> Rc { - self.string.clone() +impl ToStr for HeaderValueString { + fn to_str(&self) -> &str { + self.string.as_ref() } } @@ -52,50 +52,55 @@ impl ToString for HeaderValueString { #[derive(Debug)] struct HeaderValueWeighted { weighted: Vec, + string: String, } -impl ToString for HeaderValueWeighted { - fn to_string(&self) -> Rc { - let mut val = self.weighted.iter().fold(String::new(), |mut s, w| { +impl HeaderValueWeighted { + pub fn from(weighted: Vec) -> Self { + let mut string = weighted.iter().fold(String::new(), |mut s, w| { s.push_str(&w.value()); if let Some(w) = w.weight() { let _ = write!(s, ";q={:.2}", w).map_err(|e| e.to_string()); } s }); - val.remove(val.len() - 1); - return Rc::new(val); + string.pop(); + Self { weighted, string } + } +} + +impl ToStr for HeaderValueWeighted { + fn to_str(&self) -> &str { + self.string.as_str() } } #[derive(Debug)] struct HeaderValueTime { - time: Date, + string: String, } impl HeaderValueTime { - fn time_to_header_string(&self) -> String { - self.time.to_rfc1123() - } - pub fn from_system_time(time: SystemTime) -> Self { + let date = Date::from(time); + let date_string = date.to_rfc1123(); Self { - time: Date::from(time), + string: date_string, } } } -impl ToString for HeaderValueTime { - fn to_string(&self) -> Rc { - Rc::new(self.time_to_header_string()) +impl ToStr for HeaderValueTime { + fn to_str(&self) -> &str { + self.string.as_str() } } #[allow(dead_code)] #[derive(Debug, Clone)] pub enum HttpHeader { - StrKey(&'static str, Rc), - StringKey(Rc, Rc), + StrKey(&'static str, Rc), + StringKey(Rc, Rc), } impl HttpHeader { @@ -106,7 +111,7 @@ impl HttpHeader { } } - pub fn value(&self) -> &Rc { + pub fn value(&self) -> &Rc { match self { HttpHeader::StrKey(_, value) => value, HttpHeader::StringKey(_, value) => value, @@ -114,12 +119,12 @@ impl HttpHeader { } } -fn from_str_key(key: &'static str, value: Rc) -> HttpHeader { +fn from_str_key(key: &'static str, value: Rc) -> HttpHeader { HttpHeader::StrKey(key, value) } #[allow(dead_code)] -fn from_string_key(key: String, value: Rc) -> HttpHeader { +fn from_string_key(key: String, value: Rc) -> HttpHeader { HttpHeader::StringKey(Rc::new(key), value) } @@ -131,7 +136,7 @@ pub fn date(time: std::time::SystemTime) -> HttpHeader { // entity #[allow(dead_code)] pub fn allow(values: Vec) -> HttpHeader { - from_str_key("Allow", Rc::new(HeaderValueWeighted { weighted: values })) + from_str_key("Allow", Rc::new(HeaderValueWeighted::from(values))) } #[allow(dead_code)] @@ -244,7 +249,7 @@ impl HttpResponseHeader { self.undefined.push(header); } - pub fn set(&mut self, key: &'static str, value: Rc) { + pub fn set(&mut self, key: &'static str, value: Rc) { if let Some(index) = Self::HEADER_LIST .iter() .position(|&k| k.eq_ignore_ascii_case(key)) @@ -265,7 +270,7 @@ impl HttpResponseHeader { self.undefined.push(HttpHeader::StrKey(key, value)); } - pub fn get(&self, key: &'static str) -> Option> { + pub fn get(&self, key: &'static str) -> Option> { if let Some(index) = Self::HEADER_LIST .iter() .position(|&k| k.eq_ignore_ascii_case(key)) @@ -330,20 +335,18 @@ impl<'a> IntoIterator for &'a HttpResponseHeader { mod test { use std::time::SystemTime; - use crate::http::header::{HeaderValueTime, ToString}; + use crate::http::header::{HeaderValueTime, ToStr}; #[test] fn test_time_to_header_string() { assert_eq!( - HeaderValueTime::from_system_time(SystemTime::UNIX_EPOCH) - .to_string() - .as_ref(), + HeaderValueTime::from_system_time(SystemTime::UNIX_EPOCH).to_str(), "Thu, 01 Jan 1970 00:00:00 GMT" ); println!( "{}", - HeaderValueTime::from_system_time(SystemTime::now()).to_string() + HeaderValueTime::from_system_time(SystemTime::now()).to_str() ) } @@ -355,28 +358,22 @@ mod test { // 1. 정의된 헤더 테스트 (Predefined) let content_type = HttpHeaderValue::Str("text/plain").to_value(); headers.set("Content-Type", content_type.clone()); - assert_eq!( - headers.get("Content-Type").unwrap().to_string().as_ref(), - "text/plain" - ); + assert_eq!(headers.get("Content-Type").unwrap().to_str(), "text/plain"); // 대소문자 구분 없음 확인 - assert_eq!( - headers.get("content-type").unwrap().to_string().as_ref(), - "text/plain" - ); + assert_eq!(headers.get("content-type").unwrap().to_str(), "text/plain"); // 2. 정의되지 않은 커스텀 헤더 테스트 (Undefined) let x_custom = HttpHeaderValue::Str("custom-value").to_value(); headers.set("X-Custom-Header", x_custom.clone()); assert_eq!( - headers.get("X-Custom-Header").unwrap().to_string().as_ref(), + headers.get("X-Custom-Header").unwrap().to_str(), "custom-value" ); // 커스텀 헤더 대소문자 구분 없음 확인 assert_eq!( - headers.get("x-custom-header").unwrap().to_string().as_ref(), + headers.get("x-custom-header").unwrap().to_str(), "custom-value" ); @@ -384,7 +381,7 @@ mod test { let new_content_type = HttpHeaderValue::Str("application/json").to_value(); headers.set("CONTENT-TYPE", new_content_type.clone()); assert_eq!( - headers.get("Content-Type").unwrap().to_string().as_ref(), + headers.get("Content-Type").unwrap().to_str(), "application/json" ); diff --git a/httprs/src/http/response.rs b/httprs/src/http/response.rs index cd49b26..73bf316 100644 --- a/httprs/src/http/response.rs +++ b/httprs/src/http/response.rs @@ -1,7 +1,5 @@ use std::{ - collections::HashMap, - io::{BufWriter, IoSlice, Write}, - rc::Rc, + io::{IoSlice, Write}, time::SystemTime, }; @@ -66,27 +64,28 @@ impl<'a> Write for HttpResponse<'a> { )); self.set_header(date(SystemTime::now())); - let mut buf_writer = BufWriter::with_capacity(1 << 15, &mut self.writer); - - let mut header_lines: Vec = vec![]; + let mut header_lines: Vec<&str> = vec![]; let status_line = format!("{} {}", self.version.clone(), self.code); - header_lines.push(status_line); - - self.header.into_iter().for_each(|i| { - let k = i.key_str(); - let v = i.value().to_string(); - header_lines.push(format!("{}: {}", k, v)); - }); + header_lines.push(status_line.as_str()); + header_lines.push(LINE_END); + + for i in self.header.into_iter() { + header_lines.push(i.key_str()); + header_lines.push(KV_SEP); + header_lines.push(i.value().to_str()); + header_lines.push(LINE_END); + } - let header_slices: Vec> = header_lines - .iter() - .flat_map(|h| [IoSlice::new(h.as_bytes()), IoSlice::new(LINE_END)]) - .collect(); - let written = buf_writer.write_vectored(&header_slices)?; - buf_writer.write(LINE_END)?; + let written = self.writer.write_vectored( + &header_lines + .iter() + .map(|s| IoSlice::new(s.as_bytes())) + .collect::>(), + )?; + self.writer.write(LINE_END.as_bytes())?; if self.header_only { - buf_writer.flush()?; + self.writer.flush()?; self.written = written; return Ok(()); } @@ -94,10 +93,10 @@ impl<'a> Write for HttpResponse<'a> { let data: Vec> = self.buffer.iter().map(|b| IoSlice::new(&b)).collect(); let mut writer: Box = match self.header.get("Content-Encoding") { - Some(v) if *v.to_string() == "gzip" => { - Box::new(GzEncoder::new(buf_writer, Compression::default())) + Some(v) if v.to_str() == "gzip" => { + Box::new(GzEncoder::new(&mut self.writer, Compression::default())) } - _ => Box::new(buf_writer), + _ => Box::new(&mut self.writer), }; let body_written = writer.write_vectored(&data)?; @@ -118,57 +117,13 @@ impl<'a> HeaderSetter for HttpResponse<'a> { } } -const LINE_END: &[u8] = "\r\n".as_bytes(); -const KV_SEP: &[u8] = ": ".as_bytes(); +const LINE_END: &str = "\r\n"; +const KV_SEP: &str = ": "; impl HttpResponse<'_> { pub fn set_response_code(&mut self, code: HttpResponseCode) { self.code = code; } - - pub fn write_header( - header: &HashMap<&'static str, Rc>, - header_str: &HashMap, Rc>, - writer: &mut BufWriter<&mut Box>, - ) -> std::io::Result { - let mut written = 0; - if !header.is_empty() { - for (key, value) in header.clone().into_iter() { - written += Self::write_header_value( - writer, - &key.as_bytes(), - value.to_string().as_bytes(), - )?; - } - } - - if !header_str.is_empty() { - for (key, value) in header_str.clone().into_iter() { - written += Self::write_header_value( - writer, - &key.as_bytes(), - value.to_string().as_bytes(), - )?; - } - } - - written += writer.write(LINE_END)?; - - return Ok(written); - } - - fn write_header_value( - writer: &mut BufWriter<&mut Box>, - k: &[u8], - v: &[u8], - ) -> std::io::Result { - let mut written = writer.write(k)?; - written += writer.write(KV_SEP)?; - written += writer.write(v)?; - written += writer.write(LINE_END)?; - - return Ok(written); - } } #[cfg(test)]