diff --git a/README.md b/README.md index 4026830f7..fb162fc55 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Each result is best of five runs. All measurements are req/sec. Name | 1 thread | 1 pipeline | 3 thread | 3 pipeline | 8 thread | 8 pipeline ---- | -------- | ---------- | -------- | ---------- | -------- | ---------- -Actix | 89.100 | 815.200 | 122.100 | 1.877.000 | 107.400 | 2.350.000 +Actix | 89.300 | 871.200 | 122.100 | 1.877.000 | 107.400 | 2.560.000 Gotham | 61.000 | 178.000 | | | | Iron | | | | | 94.500 | 78.000 Rocket | | | | | 95.500 | failed diff --git a/src/context.rs b/src/context.rs index f792370f5..c9f770147 100644 --- a/src/context.rs +++ b/src/context.rs @@ -25,7 +25,7 @@ pub(crate) trait IoContext: 'static { #[derive(Debug)] pub(crate) enum Frame { - Message(Box), + Message(HttpResponse), Payload(Option), Drain(Rc>), } @@ -141,7 +141,7 @@ impl HttpContext where A: Actor { Body::StreamingContext | Body::UpgradeContext => self.streaming = true, _ => (), } - self.stream.push_back(Frame::Message(Box::new(resp))) + self.stream.push_back(Frame::Message(resp)) } /// Write payload diff --git a/src/handler.rs b/src/handler.rs index 9d60f1f6d..6ea28dec4 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -54,7 +54,7 @@ impl Handler for F pub struct Reply(ReplyItem); pub(crate) enum ReplyItem { - Message(Box), + Message(HttpResponse), Actor(Box), Future(Box>), } @@ -80,7 +80,7 @@ impl Reply { /// Send response #[inline] pub fn response>(response: R) -> Reply { - Reply(ReplyItem::Message(Box::new(response.into()))) + Reply(ReplyItem::Message(response.into())) } #[inline] @@ -111,14 +111,14 @@ impl Responder for HttpResponse { type Error = Error; fn respond_to(self, _: HttpRequest) -> Result { - Ok(Reply(ReplyItem::Message(Box::new(self)))) + Ok(Reply(ReplyItem::Message(self))) } } impl From for Reply { fn from(resp: HttpResponse) -> Reply { - Reply(ReplyItem::Message(Box::new(resp))) + Reply(ReplyItem::Message(resp)) } } @@ -142,7 +142,7 @@ impl> From> for Reply { fn from(res: Result) -> Self { match res { Ok(val) => val, - Err(err) => Reply(ReplyItem::Message(Box::new(err.into().into()))), + Err(err) => Reply(ReplyItem::Message(err.into().into())), } } } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index a1b26caf9..e98606335 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -1,6 +1,8 @@ //! Pieces pertaining to the HTTP response. use std::{mem, str, fmt}; +use std::cell::RefCell; use std::convert::Into; +use std::collections::VecDeque; use cookie::CookieJar; use bytes::{Bytes, BytesMut}; @@ -27,8 +29,8 @@ pub enum ConnectionType { Upgrade, } -/// An HTTP Response -pub struct HttpResponse { +#[derive(Debug)] +struct InnerHttpResponse { version: Option, headers: HeaderMap, status: StatusCode, @@ -41,22 +43,11 @@ pub struct HttpResponse { error: Option, } -impl HttpResponse { +impl InnerHttpResponse { - /// Create http response builder with specific status. #[inline] - pub fn build(status: StatusCode) -> HttpResponseBuilder { - HttpResponseBuilder { - response: Some(HttpResponse::new(status, Body::Empty)), - err: None, - cookies: None, - } - } - - /// Constructs a response - #[inline] - pub fn new(status: StatusCode, body: Body) -> HttpResponse { - HttpResponse { + fn new(status: StatusCode, body: Body) -> InnerHttpResponse { + InnerHttpResponse { version: None, headers: HeaderMap::with_capacity(8), status: status, @@ -70,81 +61,178 @@ impl HttpResponse { } } +} + +impl Default for InnerHttpResponse { + + fn default() -> InnerHttpResponse { + InnerHttpResponse::new(StatusCode::OK, Body::Empty) + } +} + +/// Internal use only! unsafe +struct Pool(VecDeque>); + +thread_local!(static POOL: RefCell = RefCell::new(Pool::new())); + +impl Pool { + fn new() -> Pool { + Pool(VecDeque::with_capacity(128)) + } + + fn get() -> Box { + POOL.with(|pool| { + if let Some(resp) = pool.borrow_mut().0.pop_front() { + resp + } else { + Box::new(InnerHttpResponse::default()) + } + }) + } + + #[cfg_attr(feature = "cargo-clippy", allow(boxed_local))] + fn release(mut inner: Box) { + POOL.with(|pool| { + if pool.borrow().0.len() < 128 { + inner.version.take(); + inner.headers.clear(); + inner.chunked = false; + inner.reason.take(); + inner.body = Body::Empty; + inner.encoding = ContentEncoding::Auto; + inner.connection_type.take(); + inner.response_size = 0; + inner.error.take(); + pool.borrow_mut().0.push_front(inner); + } + }) + } +} + +/// An HTTP Response +pub struct HttpResponse(Option>); + +impl Drop for HttpResponse { + fn drop(&mut self) { + if let Some(inner) = self.0.take() { + Pool::release(inner) + } + } +} + +impl HttpResponse { + + #[inline(always)] + fn get_ref(&self) -> &InnerHttpResponse { + self.0.as_ref().unwrap() + } + + #[inline(always)] + fn get_mut(&mut self) -> &mut InnerHttpResponse { + self.0.as_mut().unwrap() + } + + #[inline] + fn from_inner(inner: Box) -> HttpResponse { + HttpResponse(Some(inner)) + } + + /// Create http response builder with specific status. + #[inline] + pub fn build(status: StatusCode) -> HttpResponseBuilder { + let mut inner = Pool::get(); + inner.status = status; + HttpResponseBuilder { + response: Some(inner), + err: None, + cookies: None, + } + } + + /// Constructs a response + #[inline] + pub fn new(status: StatusCode, body: Body) -> HttpResponse { + let mut inner = Pool::get(); + inner.status = status; + inner.body = body; + HttpResponse(Some(inner)) + } + /// Constructs a error response #[inline] pub fn from_error(error: Error) -> HttpResponse { let mut resp = error.cause().error_response(); - resp.error = Some(error); + resp.get_mut().error = Some(error); resp } /// The source `error` for this response #[inline] pub fn error(&self) -> Option<&Error> { - self.error.as_ref() + self.get_ref().error.as_ref() } /// Get the HTTP version of this response. #[inline] pub fn version(&self) -> Option { - self.version + self.get_ref().version } /// Get the headers from the response. #[inline] pub fn headers(&self) -> &HeaderMap { - &self.headers + &self.get_ref().headers } /// Get a mutable reference to the headers. #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.headers + &mut self.get_mut().headers } /// Get the status from the server. #[inline] pub fn status(&self) -> StatusCode { - self.status + self.get_ref().status } /// Set the `StatusCode` for this response. #[inline] pub fn status_mut(&mut self) -> &mut StatusCode { - &mut self.status + &mut self.get_mut().status } /// Get custom reason for the response. #[inline] pub fn reason(&self) -> &str { - if let Some(reason) = self.reason { + if let Some(reason) = self.get_ref().reason { reason } else { - self.status.canonical_reason().unwrap_or("") + self.get_ref().status.canonical_reason().unwrap_or("") } } /// Set the custom reason for the response. #[inline] pub fn set_reason(&mut self, reason: &'static str) -> &mut Self { - self.reason = Some(reason); + self.get_mut().reason = Some(reason); self } /// Set connection type pub fn set_connection_type(&mut self, conn: ConnectionType) -> &mut Self { - self.connection_type = Some(conn); + self.get_mut().connection_type = Some(conn); self } /// Connection upgrade status pub fn upgrade(&self) -> bool { - self.connection_type == Some(ConnectionType::Upgrade) + self.get_ref().connection_type == Some(ConnectionType::Upgrade) } /// Keep-alive status for this connection pub fn keep_alive(&self) -> Option { - if let Some(ct) = self.connection_type { + if let Some(ct) = self.get_ref().connection_type { match ct { ConnectionType::KeepAlive => Some(true), ConnectionType::Close | ConnectionType::Upgrade => Some(false), @@ -156,54 +244,55 @@ impl HttpResponse { /// is chunked encoding enabled pub fn chunked(&self) -> bool { - self.chunked + self.get_ref().chunked } /// Content encoding pub fn content_encoding(&self) -> &ContentEncoding { - &self.encoding + &self.get_ref().encoding } /// Set content encoding pub fn set_content_encoding(&mut self, enc: ContentEncoding) -> &mut Self { - self.encoding = enc; + self.get_mut().encoding = enc; self } /// Get body os this response pub fn body(&self) -> &Body { - &self.body + &self.get_ref().body } /// Set a body pub fn set_body>(&mut self, body: B) { - self.body = body.into(); + self.get_mut().body = body.into(); } /// Set a body and return previous body value pub fn replace_body>(&mut self, body: B) -> Body { - mem::replace(&mut self.body, body.into()) + mem::replace(&mut self.get_mut().body, body.into()) } /// Size of response in bytes, excluding HTTP headers pub fn response_size(&self) -> u64 { - self.response_size + self.get_ref().response_size } /// Set content encoding pub(crate) fn set_response_size(&mut self, size: u64) { - self.response_size = size; + self.get_mut().response_size = size; } } impl fmt::Debug for HttpResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let res = write!(f, "\nHttpResponse {:?} {}{}\n", - self.version, self.status, self.reason.unwrap_or("")); - let _ = write!(f, " encoding: {:?}\n", self.encoding); + self.get_ref().version, self.get_ref().status, + self.get_ref().reason.unwrap_or("")); + let _ = write!(f, " encoding: {:?}\n", self.get_ref().encoding); let _ = write!(f, " headers:\n"); - for key in self.headers.keys() { - let vals: Vec<_> = self.headers.get_all(key).iter().collect(); + for key in self.get_ref().headers.keys() { + let vals: Vec<_> = self.get_ref().headers.get_all(key).iter().collect(); if vals.len() > 1 { let _ = write!(f, " {:?}: {:?}\n", key, vals); } else { @@ -220,7 +309,7 @@ impl fmt::Debug for HttpResponse { /// builder-like pattern. #[derive(Debug)] pub struct HttpResponseBuilder { - response: Option, + response: Option>, err: Option, cookies: Option, } @@ -381,7 +470,7 @@ impl HttpResponseBuilder { } } response.body = body.into(); - Ok(response) + Ok(HttpResponse::from_inner(response)) } /// Set a json body and generate `HttpResponse` @@ -406,8 +495,9 @@ impl HttpResponseBuilder { } } -fn parts<'a>(parts: &'a mut Option, err: &Option) - -> Option<&'a mut HttpResponse> +#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))] +fn parts<'a>(parts: &'a mut Option>, err: &Option) + -> Option<&'a mut Box> { if err.is_some() { return None diff --git a/src/middlewares/defaultheaders.rs b/src/middlewares/defaultheaders.rs index 3e9dc278a..6766e1fa4 100644 --- a/src/middlewares/defaultheaders.rs +++ b/src/middlewares/defaultheaders.rs @@ -40,7 +40,7 @@ impl DefaultHeaders { impl Middleware for DefaultHeaders { - fn response(&self, _: &mut HttpRequest, mut resp: Box) -> Response { + fn response(&self, _: &mut HttpRequest, mut resp: HttpResponse) -> Response { for (key, value) in self.headers.iter() { if !resp.headers().contains_key(key) { resp.headers_mut().insert(key, value.clone()); diff --git a/src/middlewares/mod.rs b/src/middlewares/mod.rs index d5d88fc78..b9798c97b 100644 --- a/src/middlewares/mod.rs +++ b/src/middlewares/mod.rs @@ -21,7 +21,7 @@ pub enum Started { Err(Error), /// New http response got generated. If middleware generates response /// handler execution halts. - Response(Box), + Response(HttpResponse), /// Execution completed, runs future to completion. Future(Box, Error=Error>>), } @@ -31,7 +31,7 @@ pub enum Response { /// Moddleware error Err(Error), /// New http response got generated - Done(Box), + Done(HttpResponse), /// Result is a future that resolves to a new http response Future(Box>), } @@ -56,7 +56,7 @@ pub trait Middleware { /// Method is called when handler returns response, /// but before sending http message to peer. - fn response(&self, req: &mut HttpRequest, resp: Box) -> Response { + fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Response { Response::Done(resp) } diff --git a/src/middlewares/session.rs b/src/middlewares/session.rs index d38fb0682..a807b0c03 100644 --- a/src/middlewares/session.rs +++ b/src/middlewares/session.rs @@ -107,7 +107,7 @@ impl> Middleware for SessionStorage { Started::Future(Box::new(fut)) } - fn response(&self, req: &mut HttpRequest, resp: Box) -> Response { + fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Response { if let Some(s_box) = req.extensions().remove::>() { s_box.0.write(resp) } else { @@ -129,7 +129,7 @@ pub trait SessionImpl: 'static { fn clear(&mut self); /// Write session to storage backend. - fn write(&self, resp: Box) -> Response; + fn write(&self, resp: HttpResponse) -> Response; } /// Session's storage backend trait definition. @@ -155,7 +155,7 @@ impl SessionImpl for DummySessionImpl { fn set(&mut self, key: &str, value: String) {} fn remove(&mut self, key: &str) {} fn clear(&mut self) {} - fn write(&self, resp: Box) -> Response { + fn write(&self, resp: HttpResponse) -> Response { Response::Done(resp) } } @@ -205,7 +205,7 @@ impl SessionImpl for CookieSession { self.state.clear() } - fn write(&self, mut resp: Box) -> Response { + fn write(&self, mut resp: HttpResponse) -> Response { if self.changed { let _ = self.inner.set_cookie(&mut resp, &self.state); } diff --git a/src/pipeline.rs b/src/pipeline.rs index 48f80c13b..2073ff608 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -142,7 +142,7 @@ impl Pipeline<()> { pub fn error>(err: R) -> Box { Box::new(Pipeline( PipelineInfo::new( - HttpRequest::default()), ProcessResponse::init(Box::new(err.into())))) + HttpRequest::default()), ProcessResponse::init(err.into()))) } } @@ -347,15 +347,15 @@ impl StartMiddlewares { fut: Some(fut)}), Ok(Async::Ready(resp)) => { if let Some(resp) = resp { - return RunMiddlewares::init(info, Box::new(resp)); + return RunMiddlewares::init(info, resp); } info.count += 1; } Err(err) => - return ProcessResponse::init(Box::new(err.into())), + return ProcessResponse::init(err.into()), }, Started::Err(err) => - return ProcessResponse::init(Box::new(err.into())), + return ProcessResponse::init(err.into()), } } } @@ -370,7 +370,7 @@ impl StartMiddlewares { Ok(Async::Ready(resp)) => { info.count += 1; if let Some(resp) = resp { - return Ok(RunMiddlewares::init(info, Box::new(resp))); + return Ok(RunMiddlewares::init(info, resp)); } if info.count == len { let reply = (unsafe{&*self.hnd})(info.req.clone()); @@ -388,13 +388,13 @@ impl StartMiddlewares { continue 'outer }, Started::Err(err) => - return Ok(ProcessResponse::init(Box::new(err.into()))) + return Ok(ProcessResponse::init(err.into())) } } } } Err(err) => - return Ok(ProcessResponse::init(Box::new(err.into()))) + return Ok(ProcessResponse::init(err.into())) } } } @@ -442,14 +442,14 @@ impl WaitingResponse { Ok(Async::Ready(None)) => { error!("Unexpected eof"); let err: Error = UnexpectedTaskFrame.into(); - return Ok(ProcessResponse::init(Box::new(err.into()))) + return Ok(ProcessResponse::init(err.into())) }, Ok(Async::NotReady) => { self.stream = PipelineResponse::Context(context); return Err(PipelineState::Handler(self)) }, Err(err) => - return Ok(ProcessResponse::init(Box::new(err.into()))) + return Ok(ProcessResponse::init(err.into())) } } }, @@ -460,9 +460,9 @@ impl WaitingResponse { Err(PipelineState::Handler(self)) } Ok(Async::Ready(response)) => - Ok(RunMiddlewares::init(info, Box::new(response))), + Ok(RunMiddlewares::init(info, response)), Err(err) => - Ok(ProcessResponse::init(Box::new(err.into()))), + Ok(ProcessResponse::init(err.into())), } } PipelineResponse::None => { @@ -482,7 +482,7 @@ struct RunMiddlewares { impl RunMiddlewares { - fn init(info: &mut PipelineInfo, mut resp: Box) -> PipelineState + fn init(info: &mut PipelineInfo, mut resp: HttpResponse) -> PipelineState { if info.count == 0 { return ProcessResponse::init(resp); @@ -494,7 +494,7 @@ impl RunMiddlewares { resp = match info.mws[curr].response(info.req_mut(), resp) { Response::Err(err) => { info.count = curr + 1; - return ProcessResponse::init(Box::new(err.into())) + return ProcessResponse::init(err.into()) } Response::Done(r) => { curr += 1; @@ -522,10 +522,10 @@ impl RunMiddlewares { return Ok(PipelineState::RunMiddlewares(self)), Ok(Async::Ready(resp)) => { self.curr += 1; - Box::new(resp) + resp } Err(err) => - return Ok(ProcessResponse::init(Box::new(err.into()))), + return Ok(ProcessResponse::init(err.into())), }; loop { @@ -534,7 +534,7 @@ impl RunMiddlewares { } else { match info.mws[self.curr].response(info.req_mut(), resp) { Response::Err(err) => - return Ok(ProcessResponse::init(Box::new(err.into()))), + return Ok(ProcessResponse::init(err.into())), Response::Done(r) => { self.curr += 1; resp = r @@ -551,7 +551,7 @@ impl RunMiddlewares { } struct ProcessResponse { - resp: Box, + resp: HttpResponse, iostate: IOState, running: RunningState, drain: DrainVec, @@ -608,7 +608,7 @@ impl Drop for DrainVec { impl ProcessResponse { - fn init(resp: Box) -> PipelineState + fn init(resp: HttpResponse) -> PipelineState { PipelineState::Response( ProcessResponse{ resp: resp, @@ -790,14 +790,14 @@ impl ProcessResponse { /// Middlewares start executor struct FinishingMiddlewares { - resp: Box, + resp: HttpResponse, fut: Option>>, _s: PhantomData, } impl FinishingMiddlewares { - fn init(info: &mut PipelineInfo, resp: Box) -> PipelineState { + fn init(info: &mut PipelineInfo, resp: HttpResponse) -> PipelineState { if info.count == 0 { Completed::init(info) } else { @@ -899,7 +899,7 @@ mod tests { let mut ctx = HttpContext::new(req.clone(), MyActor); let addr: Address<_> = ctx.address(); let mut info = PipelineInfo::new(req); - info.context = Some(Box::new(ctx)); + info.context = Some(ctx); let mut state = Completed::init(&mut info).completed().unwrap(); let st = state.poll(&mut info).ok().unwrap();