diff --git a/CHANGES.md b/CHANGES.md index e3a6e17d0..6c4a4aa9a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## 0.4.10 (2018-03-xx) + +.. + + ## 0.4.9 (2018-03-16) * Allow to disable http/2 support diff --git a/Cargo.toml b/Cargo.toml index a6f5b6e29..552b1b17c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.4.9" +version = "0.4.10" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic, extremely fast, web framework for Rust." readme = "README.md" diff --git a/src/body.rs b/src/body.rs index fe6303438..cdf8c0391 100644 --- a/src/body.rs +++ b/src/body.rs @@ -235,9 +235,10 @@ impl<'a> From<&'a Arc>> for Binary { } impl AsRef<[u8]> for Binary { + #[inline] fn as_ref(&self) -> &[u8] { match *self { - Binary::Bytes(ref bytes) => bytes.as_ref(), + Binary::Bytes(ref bytes) => &bytes[..], Binary::Slice(slice) => slice, Binary::SharedString(ref s) => s.as_bytes(), Binary::ArcSharedString(ref s) => s.as_bytes(), diff --git a/src/header/mod.rs b/src/header/mod.rs index 29fa9e134..3698ca812 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -149,6 +149,8 @@ impl ContentEncoding { ContentEncoding::Identity | ContentEncoding::Auto => "identity", } } + + #[inline] /// default quality value pub fn quality(&self) -> f64 { match *self { diff --git a/src/helpers.rs b/src/helpers.rs index 5f54f48f9..623869d0c 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,71 +1,13 @@ -use std::{str, mem, ptr, slice}; +use std::{mem, ptr, slice}; use std::cell::RefCell; -use std::fmt::{self, Write}; use std::rc::Rc; use std::ops::{Deref, DerefMut}; use std::collections::VecDeque; -use time; use bytes::{BufMut, BytesMut}; use http::Version; use httprequest::HttpInnerMessage; -// "Sun, 06 Nov 1994 08:49:37 GMT".len() -pub(crate) const DATE_VALUE_LENGTH: usize = 29; - -pub(crate) fn date(dst: &mut BytesMut) { - CACHED.with(|cache| { - let mut buf: [u8; 39] = unsafe { mem::uninitialized() }; - buf[..6].copy_from_slice(b"date: "); - buf[6..35].copy_from_slice(cache.borrow().buffer()); - buf[35..].copy_from_slice(b"\r\n\r\n"); - dst.extend_from_slice(&buf); - }) -} - -pub(crate) fn date_value(dst: &mut BytesMut) { - CACHED.with(|cache| { - dst.extend_from_slice(cache.borrow().buffer()); - }) -} - -pub(crate) fn update_date() { - CACHED.with(|cache| { - cache.borrow_mut().update(); - }); -} - -struct CachedDate { - bytes: [u8; DATE_VALUE_LENGTH], - pos: usize, -} - -thread_local!(static CACHED: RefCell = RefCell::new(CachedDate { - bytes: [0; DATE_VALUE_LENGTH], - pos: 0, -})); - -impl CachedDate { - fn buffer(&self) -> &[u8] { - &self.bytes[..] - } - - fn update(&mut self) { - self.pos = 0; - write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap(); - assert_eq!(self.pos, DATE_VALUE_LENGTH); - } -} - -impl fmt::Write for CachedDate { - fn write_str(&mut self, s: &str) -> fmt::Result { - let len = s.len(); - self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes()); - self.pos += len; - Ok(()) - } -} - /// Internal use only! unsafe pub(crate) struct SharedMessagePool(RefCell>>); @@ -202,7 +144,7 @@ pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesM } } - bytes.extend_from_slice(&buf); + bytes.put_slice(&buf); if four { bytes.put(b' '); } @@ -214,7 +156,7 @@ pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) { b'n',b't',b'-',b'l',b'e',b'n',b'g', b't',b'h',b':',b' ',b'0',b'\r',b'\n']; buf[18] = (n as u8) + b'0'; - bytes.extend_from_slice(&buf); + bytes.put_slice(&buf); } else if n < 100 { let mut buf: [u8; 22] = [b'\r',b'\n',b'c',b'o',b'n',b't',b'e', b'n',b't',b'-',b'l',b'e',b'n',b'g', @@ -224,7 +166,7 @@ pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) { ptr::copy_nonoverlapping( DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), buf.as_mut_ptr().offset(18), 2); } - bytes.extend_from_slice(&buf); + bytes.put_slice(&buf); } else if n < 1000 { let mut buf: [u8; 23] = [b'\r',b'\n',b'c',b'o',b'n',b't',b'e', b'n',b't',b'-',b'l',b'e',b'n',b'g', @@ -238,9 +180,9 @@ pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) { // decode last 1 buf[18] = (n as u8) + b'0'; - bytes.extend_from_slice(&buf); + bytes.put_slice(&buf); } else { - bytes.extend_from_slice(b"\r\ncontent-length: "); + bytes.put_slice(b"\r\ncontent-length: "); convert_usize(n, bytes); } } @@ -299,20 +241,6 @@ pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { mod tests { use super::*; - #[test] - fn test_date_len() { - assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len()); - } - - #[test] - fn test_date() { - let mut buf1 = BytesMut::new(); - date(&mut buf1); - let mut buf2 = BytesMut::new(); - date(&mut buf2); - assert_eq!(buf1, buf2); - } - #[test] fn test_write_content_length() { let mut bytes = BytesMut::new(); diff --git a/src/server/encoding.rs b/src/server/encoding.rs index 6ba232b0c..bd6921d21 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -368,6 +368,7 @@ impl ContentEncoder { response_encoding: ContentEncoding) -> ContentEncoder { let version = resp.version().unwrap_or_else(|| req.version); + let is_head = req.method == Method::HEAD; let mut body = resp.replace_body(Body::Empty); let has_body = match body { Body::Empty => false, @@ -410,7 +411,9 @@ impl ContentEncoder { TransferEncoding::length(0, buf) }, Body::Binary(ref mut bytes) => { - if encoding.is_compression() { + if !(encoding == ContentEncoding::Identity + || encoding == ContentEncoding::Auto) + { let tmp = SharedBytes::default(); let transfer = TransferEncoding::eof(tmp.clone()); let mut enc = match encoding { @@ -431,13 +434,13 @@ impl ContentEncoder { *bytes = Binary::from(tmp.take()); encoding = ContentEncoding::Identity; } - if req.method == Method::HEAD { + if is_head { let mut b = BytesMut::new(); let _ = write!(b, "{}", bytes.len()); resp.headers_mut().insert( CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); } else { - resp.headers_mut().remove(CONTENT_LENGTH); + // resp.headers_mut().remove(CONTENT_LENGTH); } TransferEncoding::eof(buf) } @@ -460,7 +463,7 @@ impl ContentEncoder { } }; // - if req.method == Method::HEAD { + if is_head { transfer.kind = TransferEncodingKind::Length(0); } else { resp.replace_body(body); diff --git a/src/server/h1.rs b/src/server/h1.rs index b7055ea05..38d4d4345 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -51,7 +51,7 @@ pub(crate) struct Http1 { flags: Flags, settings: Rc>, addr: Option, - stream: H1Writer, + stream: H1Writer, reader: Reader, read_buf: BytesMut, tasks: VecDeque, @@ -72,7 +72,7 @@ impl Http1 { let bytes = settings.get_shared_bytes(); Http1{ flags: Flags::KEEPALIVE, - stream: H1Writer::new(stream, bytes), + stream: H1Writer::new(stream, bytes, Rc::clone(&settings)), reader: Reader::new(), tasks: VecDeque::new(), keepalive_timer: None, @@ -353,7 +353,7 @@ impl Reader { PayloadStatus::Read } } - + #[inline] fn decode(&mut self, buf: &mut BytesMut, payload: &mut PayloadInfo) -> Result @@ -502,10 +502,12 @@ impl Reader { httparse::Status::Complete(len) => { let method = Method::try_from(req.method.unwrap()) .map_err(|_| ParseError::Method)?; - let path = req.path.unwrap(); - let path_start = path.as_ptr() as usize - bytes_ptr; - let path_end = path_start + path.len(); - let path = (path_start, path_end); + //let path = req.path.unwrap(); + //let path_start = path.as_ptr() as usize - bytes_ptr; + //let path_end = path_start + path.len(); + //let path = (path_start, path_end); + let path = Uri::try_from(req.path.unwrap()).unwrap(); + //.map_err(|_| ParseError::Uri)?; let version = if req.version.unwrap() == 1 { Version::HTTP_11 @@ -525,9 +527,7 @@ impl Reader { { let msg_mut = msg.get_mut(); for header in headers[..headers_len].iter() { - let n_start = header.name.as_ptr() as usize - bytes_ptr; - let n_end = n_start + header.name.len(); - if let Ok(name) = HeaderName::try_from(slice.slice(n_start, n_end)) { + if let Ok(name) = HeaderName::try_from(header.name) { let v_start = header.value.as_ptr() as usize - bytes_ptr; let v_end = v_start + header.value.len(); let value = unsafe { @@ -539,8 +539,9 @@ impl Reader { } } - msg_mut.uri = Uri::from_shared( - slice.slice(path.0, path.1)).map_err(ParseError::Uri)?; + msg_mut.uri = path; + //msg_mut.uri = Uri::from_shared( + //slice.slice(path.0, path.1)).map_err(ParseError::Uri)?; msg_mut.method = method; msg_mut.version = version; } diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index a48c71b0b..46c3cf1a2 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -1,11 +1,12 @@ #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] use std::{io, mem}; +use std::rc::Rc; use bytes::BufMut; use futures::{Async, Poll}; use tokio_io::AsyncWrite; use http::{Method, Version}; -use http::header::{HeaderValue, CONNECTION, DATE}; +use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE}; use helpers; use body::{Body, Binary}; @@ -15,6 +16,7 @@ use httpresponse::HttpResponse; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use super::shared::SharedBytes; use super::encoding::ContentEncoder; +use super::settings::WorkerSettings; const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific @@ -27,7 +29,7 @@ bitflags! { } } -pub(crate) struct H1Writer { +pub(crate) struct H1Writer { flags: Flags, stream: T, encoder: ContentEncoder, @@ -35,11 +37,14 @@ pub(crate) struct H1Writer { headers_size: u32, buffer: SharedBytes, buffer_capacity: usize, + settings: Rc>, } -impl H1Writer { +impl H1Writer { - pub fn new(stream: T, buf: SharedBytes) -> H1Writer { + pub fn new(stream: T, buf: SharedBytes, settings: Rc>) + -> H1Writer + { H1Writer { flags: Flags::empty(), encoder: ContentEncoder::empty(buf.clone()), @@ -48,6 +53,7 @@ impl H1Writer { buffer: buf, buffer_capacity: 0, stream, + settings, } } @@ -87,7 +93,7 @@ impl H1Writer { } } -impl Writer for H1Writer { +impl Writer for H1Writer { #[inline] fn written(&self) -> u64 { @@ -126,11 +132,14 @@ impl Writer for H1Writer { // render message { let mut buffer = self.buffer.get_mut(); - if let Body::Binary(ref bytes) = body { - buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); + let mut is_bin = if let Body::Binary(ref bytes) = body { + buffer.reserve( + 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); + true } else { buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE); - } + false + }; // status line helpers::write_status_line(version, msg.status().as_u16(), &mut buffer); @@ -139,21 +148,28 @@ impl Writer for H1Writer { match body { Body::Empty => if req.method != Method::HEAD { - SharedBytes::extend_from_slice_(buffer, b"\r\ncontent-length: 0\r\n"); + SharedBytes::put_slice( + buffer, b"\r\ncontent-length: 0\r\n"); } else { - SharedBytes::extend_from_slice_(buffer, b"\r\n"); + SharedBytes::put_slice(buffer, b"\r\n"); }, Body::Binary(ref bytes) => helpers::write_content_length(bytes.len(), &mut buffer), _ => - SharedBytes::extend_from_slice_(buffer, b"\r\n"), + SharedBytes::put_slice(buffer, b"\r\n"), } // write headers let mut pos = 0; + let mut has_date = false; let mut remaining = buffer.remaining_mut(); let mut buf: &mut [u8] = unsafe{ mem::transmute(buffer.bytes_mut()) }; for (key, value) in msg.headers() { + if is_bin && key == CONTENT_LENGTH { + is_bin = false; + continue + } + has_date = has_date || key == DATE; let v = value.as_ref(); let k = key.as_str().as_bytes(); let len = k.len() + v.len() + 4; @@ -182,9 +198,9 @@ impl Writer for H1Writer { } unsafe{buffer.advance_mut(pos)}; - // using helpers::date is quite a lot faster - if !msg.headers().contains_key(DATE) { - helpers::date(&mut buffer); + // optimized date header + if !has_date { + self.settings.set_date(&mut buffer); } else { // msg eof SharedBytes::extend_from_slice_(buffer, b"\r\n"); diff --git a/src/server/h2.rs b/src/server/h2.rs index 6cc682a11..0219d84b8 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -43,7 +43,7 @@ struct Http2 settings: Rc>, addr: Option, state: State>, - tasks: VecDeque, + tasks: VecDeque>, keepalive_timer: Option, } @@ -274,20 +274,20 @@ bitflags! { } } -struct Entry { +struct Entry { task: Box, payload: PayloadType, recv: RecvStream, - stream: H2Writer, + stream: H2Writer, flags: EntryFlags, } -impl Entry { - fn new(parts: Parts, - recv: RecvStream, - resp: SendResponse, - addr: Option, - settings: &Rc>) -> Entry +impl Entry { + fn new(parts: Parts, + recv: RecvStream, + resp: SendResponse, + addr: Option, + settings: &Rc>) -> Entry where H: HttpHandler + 'static { // Payload and Content-Encoding @@ -320,7 +320,8 @@ impl Entry { Entry {task: task.unwrap_or_else(|| Pipeline::error(HttpNotFound)), payload: psender, - stream: H2Writer::new(resp, settings.get_shared_bytes()), + stream: H2Writer::new( + resp, settings.get_shared_bytes(), Rc::clone(settings)), flags: EntryFlags::empty(), recv, } diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index ed97682e6..738a0593f 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -1,6 +1,7 @@ #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] use std::{io, cmp}; +use std::rc::Rc; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; use http2::{Reason, SendStream}; @@ -15,6 +16,7 @@ use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; use super::encoding::ContentEncoder; use super::shared::SharedBytes; +use super::settings::WorkerSettings; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; const CHUNK_SIZE: usize = 16_384; @@ -28,7 +30,7 @@ bitflags! { } } -pub(crate) struct H2Writer { +pub(crate) struct H2Writer { respond: SendResponse, stream: Option>, encoder: ContentEncoder, @@ -36,13 +38,17 @@ pub(crate) struct H2Writer { written: u64, buffer: SharedBytes, buffer_capacity: usize, + settings: Rc>, } -impl H2Writer { +impl H2Writer { - pub fn new(respond: SendResponse, buf: SharedBytes) -> H2Writer { + pub fn new(respond: SendResponse, + buf: SharedBytes, settings: Rc>) -> H2Writer + { H2Writer { respond, + settings, stream: None, encoder: ContentEncoder::empty(buf.clone()), flags: Flags::empty(), @@ -59,7 +65,7 @@ impl H2Writer { } } -impl Writer for H2Writer { +impl Writer for H2Writer { fn written(&self) -> u64 { self.written @@ -84,7 +90,7 @@ impl Writer for H2Writer { // using helpers::date is quite a lot faster if !msg.headers().contains_key(DATE) { let mut bytes = BytesMut::with_capacity(29); - helpers::date_value(&mut bytes); + self.settings.set_date(&mut bytes); msg.headers_mut().insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); } @@ -95,7 +101,8 @@ impl Writer for H2Writer { helpers::convert_usize(bytes.len(), &mut val); let l = val.len(); msg.headers_mut().insert( - CONTENT_LENGTH, HeaderValue::try_from(val.split_to(l-2).freeze()).unwrap()); + CONTENT_LENGTH, + HeaderValue::try_from(val.split_to(l-2).freeze()).unwrap()); } Body::Empty => { msg.headers_mut().insert(CONTENT_LENGTH, HeaderValue::from_static("0")); diff --git a/src/server/settings.rs b/src/server/settings.rs index 82b190b85..7f901a71b 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -1,7 +1,10 @@ -use std::{fmt, net}; +use std::{fmt, mem, net}; +use std::fmt::Write; use std::rc::Rc; use std::sync::Arc; use std::cell::{Cell, RefCell, RefMut, UnsafeCell}; +use time; +use bytes::BytesMut; use futures_cpupool::{Builder, CpuPool}; use helpers; @@ -95,6 +98,8 @@ impl ServerSettings { } } +// "Sun, 06 Nov 1994 08:49:37 GMT".len() +const DATE_VALUE_LENGTH: usize = 29; pub(crate) struct WorkerSettings { h: RefCell>, @@ -104,6 +109,7 @@ pub(crate) struct WorkerSettings { messages: Rc, channels: Cell, node: Box>, + date: UnsafeCell, } impl WorkerSettings { @@ -121,6 +127,7 @@ impl WorkerSettings { messages: Rc::new(helpers::SharedMessagePool::new()), channels: Cell::new(0), node: Box::new(Node::head()), + date: UnsafeCell::new(Date::new()), } } @@ -164,4 +171,63 @@ impl WorkerSettings { error!("Number of removed channels is bigger than added channel. Bug in actix-web"); } } + + pub fn update_date(&self) { + unsafe{&mut *self.date.get()}.update(); + } + + pub fn set_date(&self, dst: &mut BytesMut) { + let mut buf: [u8; 39] = unsafe { mem::uninitialized() }; + buf[..6].copy_from_slice(b"date: "); + buf[6..35].copy_from_slice(&(unsafe{&*self.date.get()}.bytes)); + buf[35..].copy_from_slice(b"\r\n\r\n"); + dst.extend_from_slice(&buf); + } +} + +struct Date { + bytes: [u8; DATE_VALUE_LENGTH], + pos: usize, +} + +impl Date { + fn new() -> Date { + let mut date = Date{bytes: [0; DATE_VALUE_LENGTH], pos: 0}; + date.update(); + date + } + fn update(&mut self) { + self.pos = 0; + write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap(); + } +} + +impl fmt::Write for Date { + fn write_str(&mut self, s: &str) -> fmt::Result { + let len = s.len(); + self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes()); + self.pos += len; + Ok(()) + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_date_len() { + assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len()); + } + + #[test] + fn test_date() { + let settings = WorkerSettings::<()>::new(Vec::new(), KeepAlive::Os); + let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); + settings.set_date(&mut buf1); + let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); + settings.set_date(&mut buf2); + assert_eq!(buf1, buf2); + } } diff --git a/src/server/srv.rs b/src/server/srv.rs index 69ff9012f..bb065c47e 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -18,7 +18,6 @@ use native_tls::TlsAcceptor; #[cfg(feature="alpn")] use openssl::ssl::{AlpnError, SslAcceptorBuilder}; -use helpers; use super::{IntoHttpHandler, IoStream, KeepAlive}; use super::{PauseServer, ResumeServer, StopServer}; use super::channel::{HttpChannel, WrapperStream}; @@ -58,13 +57,8 @@ enum ServerCommand { WorkerDied(usize, Info), } -impl Actor for HttpServer where H: IntoHttpHandler -{ +impl Actor for HttpServer where H: IntoHttpHandler { type Context = Context; - - fn started(&mut self, ctx: &mut Self::Context) { - self.update_time(ctx); - } } impl HttpServer where H: IntoHttpHandler + 'static @@ -95,11 +89,6 @@ impl HttpServer where H: IntoHttpHandler + 'static } } - fn update_time(&self, ctx: &mut Context) { - helpers::update_date(); - ctx.run_later(Duration::new(1, 0), |slf, ctx| slf.update_time(ctx)); - } - /// Set number of workers to start. /// /// By default http server uses number of available logical cpu as threads count. diff --git a/src/server/worker.rs b/src/server/worker.rs index a8ca3b2ac..3fe9cec19 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -22,7 +22,6 @@ use tokio_openssl::SslAcceptorExt; use actix::*; use actix::msgs::StopArbiter; -use helpers; use server::{HttpHandler, KeepAlive}; use server::channel::HttpChannel; use server::settings::WorkerSettings; @@ -76,7 +75,7 @@ impl Worker { } fn update_time(&self, ctx: &mut Context) { - helpers::update_date(); + self.settings.update_date(); ctx.run_later(time::Duration::new(1, 0), |slf, ctx| slf.update_time(ctx)); }