From 13b0ee735519795e29efe62ea3e021610b9232b3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 16:22:00 -0700 Subject: [PATCH 001/208] stopping point --- Cargo.toml | 1 + src/lib.rs | 1 + src/server/channel.rs | 5 +- src/server/error.rs | 47 +++-- src/server/h1.rs | 295 +++++++++++++--------------- src/server/h1codec.rs | 251 ++++++++++++++++++++++++ src/server/h1decoder.rs | 54 ++--- src/server/h1disp.rs | 425 ++++++++++++++++++++++++++++++++++++++++ src/server/h2.rs | 2 +- src/server/message.rs | 9 +- src/server/mod.rs | 8 +- src/server/output.rs | 8 +- src/server/service.rs | 9 +- src/server/settings.rs | 5 + tests/test_h1v2.rs | 58 ++++++ 15 files changed, 958 insertions(+), 220 deletions(-) create mode 100644 src/server/h1codec.rs create mode 100644 src/server/h1disp.rs create mode 100644 tests/test_h1v2.rs diff --git a/Cargo.toml b/Cargo.toml index 12f98ac37..d70a65cf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,6 +105,7 @@ futures = "0.1" futures-cpupool = "0.1" slab = "0.4" tokio = "0.1" +tokio-codec = "0.1" tokio-io = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" diff --git a/src/lib.rs b/src/lib.rs index 1ed408099..f494c05de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,6 +115,7 @@ extern crate parking_lot; extern crate rand; extern crate slab; extern crate tokio; +extern crate tokio_codec; extern crate tokio_current_thread; extern crate tokio_io; extern crate tokio_reactor; diff --git a/src/server/channel.rs b/src/server/channel.rs index cbbe1a95e..b1fef964e 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -9,6 +9,7 @@ use tokio_timer::Delay; use super::error::HttpDispatchError; use super::settings::ServiceConfig; use super::{h1, h2, HttpHandler, IoStream}; +use error::Error; use http::StatusCode; const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; @@ -90,7 +91,7 @@ where H: HttpHandler + 'static, { type Item = (); - type Error = HttpDispatchError; + type Error = HttpDispatchError; fn poll(&mut self) -> Poll { // keep-alive timer @@ -242,7 +243,7 @@ where H: HttpHandler + 'static, { type Item = (); - type Error = HttpDispatchError; + type Error = HttpDispatchError; fn poll(&mut self) -> Poll { if !self.node_reg { diff --git a/src/server/error.rs b/src/server/error.rs index 70f100998..3ae9a107b 100644 --- a/src/server/error.rs +++ b/src/server/error.rs @@ -1,11 +1,12 @@ +use std::fmt::{Debug, Display}; use std::io; use futures::{Async, Poll}; use http2; use super::{helpers, HttpHandlerTask, Writer}; +use error::{Error, ParseError}; use http::{StatusCode, Version}; -use Error; /// Errors produced by `AcceptorError` service. #[derive(Debug)] @@ -20,60 +21,70 @@ pub enum AcceptorError { Timeout, } -#[derive(Fail, Debug)] +#[derive(Debug)] /// A set of errors that can occur during dispatching http requests -pub enum HttpDispatchError { +pub enum HttpDispatchError { /// Application error - #[fail(display = "Application specific error: {}", _0)] - App(Error), + // #[fail(display = "Application specific error: {}", _0)] + App(E), /// An `io::Error` that occurred while trying to read or write to a network /// stream. - #[fail(display = "IO error: {}", _0)] + // #[fail(display = "IO error: {}", _0)] Io(io::Error), + /// Http request parse error. + // #[fail(display = "Parse error: {}", _0)] + Parse(ParseError), + /// The first request did not complete within the specified timeout. - #[fail(display = "The first request did not complete within the specified timeout")] + // #[fail(display = "The first request did not complete within the specified timeout")] SlowRequestTimeout, /// Shutdown timeout - #[fail(display = "Connection shutdown timeout")] + // #[fail(display = "Connection shutdown timeout")] ShutdownTimeout, /// HTTP2 error - #[fail(display = "HTTP2 error: {}", _0)] + // #[fail(display = "HTTP2 error: {}", _0)] Http2(http2::Error), /// Payload is not consumed - #[fail(display = "Task is completed but request's payload is not consumed")] + // #[fail(display = "Task is completed but request's payload is not consumed")] PayloadIsNotConsumed, /// Malformed request - #[fail(display = "Malformed request")] + // #[fail(display = "Malformed request")] MalformedRequest, /// Internal error - #[fail(display = "Internal error")] + // #[fail(display = "Internal error")] InternalError, /// Unknown error - #[fail(display = "Unknown error")] + // #[fail(display = "Unknown error")] Unknown, } -impl From for HttpDispatchError { - fn from(err: Error) -> Self { - HttpDispatchError::App(err) +// impl From for HttpDispatchError { +// fn from(err: E) -> Self { +// HttpDispatchError::App(err) +// } +// } + +impl From for HttpDispatchError { + fn from(err: ParseError) -> Self { + HttpDispatchError::Parse(err) } } -impl From for HttpDispatchError { +impl From for HttpDispatchError { fn from(err: io::Error) -> Self { HttpDispatchError::Io(err) } } -impl From for HttpDispatchError { +impl From for HttpDispatchError { fn from(err: http2::Error) -> Self { HttpDispatchError::Http2(err) } diff --git a/src/server/h1.rs b/src/server/h1.rs index 4fb730f71..e2b4bf45e 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -7,15 +7,16 @@ use futures::{Async, Future, Poll}; use tokio_current_thread::spawn; use tokio_timer::Delay; -use error::{Error, PayloadError}; +use error::{Error, ParseError, PayloadError}; use http::{StatusCode, Version}; use payload::{Payload, PayloadStatus, PayloadWriter}; use super::error::{HttpDispatchError, ServerError}; -use super::h1decoder::{DecoderError, H1Decoder, Message}; +use super::h1decoder::{H1Decoder, Message}; use super::h1writer::H1Writer; use super::handler::{HttpHandler, HttpHandlerTask, HttpHandlerTaskFut}; use super::input::PayloadType; +use super::message::Request; use super::settings::ServiceConfig; use super::{IoStream, Writer}; @@ -44,7 +45,7 @@ pub struct Http1Dispatcher { payload: Option, buf: BytesMut, tasks: VecDeque>, - error: Option, + error: Option>, ka_expire: Instant, ka_timer: Option, } @@ -109,7 +110,7 @@ where Http1Dispatcher { stream: H1Writer::new(stream, settings.clone()), - decoder: H1Decoder::new(), + decoder: H1Decoder::new(settings.request_pool()), payload: None, tasks: VecDeque::new(), error: None, @@ -133,7 +134,7 @@ where let mut disp = Http1Dispatcher { flags: Flags::STARTED | Flags::READ_DISCONNECTED | Flags::FLUSHED, stream: H1Writer::new(stream, settings.clone()), - decoder: H1Decoder::new(), + decoder: H1Decoder::new(settings.request_pool()), payload: None, tasks: VecDeque::new(), error: None, @@ -201,7 +202,7 @@ where } #[inline] - pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { + pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { // check connection keep-alive self.poll_keep_alive()?; @@ -247,7 +248,7 @@ where } /// Flush stream - fn poll_flush(&mut self, shutdown: bool) -> Poll<(), HttpDispatchError> { + fn poll_flush(&mut self, shutdown: bool) -> Poll<(), HttpDispatchError> { if shutdown || self.flags.contains(Flags::STARTED) { match self.stream.poll_completed(shutdown) { Ok(Async::NotReady) => { @@ -277,7 +278,7 @@ where } /// keep-alive timer. returns `true` is keep-alive, otherwise drop - fn poll_keep_alive(&mut self) -> Result<(), HttpDispatchError> { + fn poll_keep_alive(&mut self) -> Result<(), HttpDispatchError> { if let Some(ref mut timer) = self.ka_timer { match timer.poll() { Ok(Async::Ready(_)) => { @@ -336,7 +337,7 @@ where #[inline] /// read data from the stream - pub(self) fn poll_io(&mut self) -> Result { + pub(self) fn poll_io(&mut self) -> Result> { if !self.flags.contains(Flags::POLLED) { self.flags.insert(Flags::POLLED); if !self.buf.is_empty() { @@ -367,7 +368,7 @@ where Ok(updated) } - pub(self) fn poll_handler(&mut self) -> Result<(), HttpDispatchError> { + pub(self) fn poll_handler(&mut self) -> Result<(), HttpDispatchError> { self.poll_io()?; let mut retry = self.can_read(); @@ -409,7 +410,7 @@ where // it is not possible to recover from error // during pipe handling, so just drop connection self.client_disconnected(false); - return Err(err.into()); + return Err(HttpDispatchError::App(err)); } } } @@ -423,7 +424,7 @@ where Ok(Async::NotReady) => false, Ok(Async::Ready(_)) => true, Err(err) => { - self.error = Some(err.into()); + self.error = Some(HttpDispatchError::App(err)); true } }; @@ -462,66 +463,75 @@ where .push_back(Entry::Error(ServerError::err(Version::HTTP_11, status))); } - pub(self) fn parse(&mut self) -> Result { + fn handle_message( + &mut self, mut msg: Request, payload: bool, + ) -> Result<(), HttpDispatchError> { + self.flags.insert(Flags::STARTED); + + if payload { + let (ps, pl) = Payload::new(false); + *msg.inner.payload.borrow_mut() = Some(pl); + self.payload = Some(PayloadType::new(&msg.inner.headers, ps)); + } + + // stream extensions + msg.inner_mut().stream_extensions = self.stream.get_mut().extensions(); + + // set remote addr + msg.inner_mut().addr = self.addr; + + // search handler for request + match self.settings.handler().handle(msg) { + Ok(mut task) => { + if self.tasks.is_empty() { + match task.poll_io(&mut self.stream) { + Ok(Async::Ready(ready)) => { + // override keep-alive state + if self.stream.keepalive() { + self.flags.insert(Flags::KEEPALIVE); + } else { + self.flags.remove(Flags::KEEPALIVE); + } + // prepare stream for next response + self.stream.reset(); + + if !ready { + // task is done with io operations + // but still needs to do more work + spawn(HttpHandlerTaskFut::new(task)); + } + } + Ok(Async::NotReady) => (), + Err(err) => { + error!("Unhandled error: {}", err); + self.client_disconnected(false); + return Err(HttpDispatchError::App(err)); + } + } + } else { + self.tasks.push_back(Entry::Task(task)); + } + } + Err(_) => { + // handler is not found + self.push_response_entry(StatusCode::NOT_FOUND); + } + } + Ok(()) + } + + pub(self) fn parse(&mut self) -> Result> { let mut updated = false; 'outer: loop { - match self.decoder.decode(&mut self.buf, &self.settings) { - Ok(Some(Message::Message { mut msg, payload })) => { + match self.decoder.decode(&mut self.buf) { + Ok(Some(Message::Message(msg))) => { updated = true; - self.flags.insert(Flags::STARTED); - - if payload { - let (ps, pl) = Payload::new(false); - *msg.inner.payload.borrow_mut() = Some(pl); - self.payload = Some(PayloadType::new(&msg.inner.headers, ps)); - } - - // stream extensions - msg.inner_mut().stream_extensions = - self.stream.get_mut().extensions(); - - // set remote addr - msg.inner_mut().addr = self.addr; - - // search handler for request - match self.settings.handler().handle(msg) { - Ok(mut task) => { - if self.tasks.is_empty() { - match task.poll_io(&mut self.stream) { - Ok(Async::Ready(ready)) => { - // override keep-alive state - if self.stream.keepalive() { - self.flags.insert(Flags::KEEPALIVE); - } else { - self.flags.remove(Flags::KEEPALIVE); - } - // prepare stream for next response - self.stream.reset(); - - if !ready { - // task is done with io operations - // but still needs to do more work - spawn(HttpHandlerTaskFut::new(task)); - } - continue 'outer; - } - Ok(Async::NotReady) => (), - Err(err) => { - error!("Unhandled error: {}", err); - self.client_disconnected(false); - return Err(err.into()); - } - } - } - self.tasks.push_back(Entry::Task(task)); - continue 'outer; - } - Err(_) => { - // handler is not found - self.push_response_entry(StatusCode::NOT_FOUND); - } - } + self.handle_message(msg, false)?; + } + Ok(Some(Message::MessageWithPayload(msg))) => { + updated = true; + self.handle_message(msg, true)?; } Ok(Some(Message::Chunk(chunk))) => { updated = true; @@ -556,8 +566,8 @@ where Err(e) => { if let Some(mut payload) = self.payload.take() { let e = match e { - DecoderError::Io(e) => PayloadError::Io(e), - DecoderError::Error(_) => PayloadError::EncodingCorrupted, + ParseError::Io(e) => PayloadError::Io(e), + _ => PayloadError::EncodingCorrupted, }; payload.set_error(e); } @@ -593,6 +603,7 @@ mod tests { use super::*; use application::{App, HttpApplication}; + use error::ParseError; use httpmessage::HttpMessage; use server::h1decoder::Message; use server::handler::IntoHttpHandler; @@ -612,13 +623,14 @@ mod tests { impl Message { fn message(self) -> Request { match self { - Message::Message { msg, payload: _ } => msg, + Message::Message(msg) => msg, + Message::MessageWithPayload(msg) => msg, _ => panic!("error"), } } fn is_payload(&self) -> bool { match *self { - Message::Message { msg: _, payload } => payload, + Message::MessageWithPayload(_) => true, _ => panic!("error"), } } @@ -639,7 +651,7 @@ mod tests { macro_rules! parse_ready { ($e:expr) => {{ let settings = wrk_settings(); - match H1Decoder::new().decode($e, &settings) { + match H1Decoder::new(settings.request_pool()).decode($e) { Ok(Some(msg)) => msg.message(), Ok(_) => unreachable!("Eof during parsing http request"), Err(err) => unreachable!("Error during parsing http request: {:?}", err), @@ -651,10 +663,10 @@ mod tests { ($e:expr) => {{ let settings = wrk_settings(); - match H1Decoder::new().decode($e, &settings) { + match H1Decoder::new(settings.request_pool()).decode($e) { Err(err) => match err { - DecoderError::Error(_) => (), - _ => unreachable!("Parse error expected"), + ParseError::Io(_) => unreachable!("Parse error expected"), + _ => (), }, _ => unreachable!("Error expected"), } @@ -747,8 +759,8 @@ mod tests { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf, &settings) { + let mut reader = H1Decoder::new(settings.request_pool()); + match reader.decode(&mut buf) { Ok(Some(msg)) => { let req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); @@ -764,14 +776,14 @@ mod tests { let mut buf = BytesMut::from("PUT /test HTTP/1"); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf, &settings) { + let mut reader = H1Decoder::new(settings.request_pool()); + match reader.decode(&mut buf) { Ok(None) => (), _ => unreachable!("Error"), } buf.extend(b".1\r\n\r\n"); - match reader.decode(&mut buf, &settings) { + match reader.decode(&mut buf) { Ok(Some(msg)) => { let mut req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); @@ -787,8 +799,8 @@ mod tests { let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf, &settings) { + let mut reader = H1Decoder::new(settings.request_pool()); + match reader.decode(&mut buf) { Ok(Some(msg)) => { let mut req = msg.message(); assert_eq!(req.version(), Version::HTTP_10); @@ -805,20 +817,15 @@ mod tests { BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf, &settings) { + let mut reader = H1Decoder::new(settings.request_pool()); + match reader.decode(&mut buf) { Ok(Some(msg)) => { let mut req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), b"body" ); } @@ -832,20 +839,15 @@ mod tests { BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf, &settings) { + let mut reader = H1Decoder::new(settings.request_pool()); + match reader.decode(&mut buf) { Ok(Some(msg)) => { let mut req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), b"body" ); } @@ -857,11 +859,11 @@ mod tests { fn test_parse_partial_eof() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); + let mut reader = H1Decoder::new(settings.request_pool()); + assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b"\r\n"); - match reader.decode(&mut buf, &settings) { + match reader.decode(&mut buf) { Ok(Some(msg)) => { let req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); @@ -877,17 +879,17 @@ mod tests { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } + let mut reader = H1Decoder::new(settings.request_pool()); + assert!{ reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"t"); - assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } + assert!{ reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"es"); - assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } + assert!{ reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"t: value\r\n\r\n"); - match reader.decode(&mut buf, &settings) { + match reader.decode(&mut buf) { Ok(Some(msg)) => { let req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); @@ -907,8 +909,8 @@ mod tests { Set-Cookie: c2=cookie2\r\n\r\n", ); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let mut reader = H1Decoder::new(settings.request_pool()); + let msg = reader.decode(&mut buf).unwrap().unwrap(); let req = msg.message(); let val: Vec<_> = req @@ -1109,19 +1111,14 @@ mod tests { upgrade: websocket\r\n\r\n\ some raw data", ); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let mut reader = H1Decoder::new(settings.request_pool()); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert!(msg.is_payload()); let req = msg.message(); assert!(!req.keep_alive()); assert!(req.upgrade()); assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), b"some raw data" ); } @@ -1169,32 +1166,22 @@ mod tests { transfer-encoding: chunked\r\n\r\n", ); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let mut reader = H1Decoder::new(settings.request_pool()); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert!(msg.is_payload()); let req = msg.message(); assert!(req.chunked().unwrap()); buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), b"data" ); assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), b"line" ); - assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof()); + assert!(reader.decode(&mut buf).unwrap().unwrap().eof()); } #[test] @@ -1204,8 +1191,8 @@ mod tests { transfer-encoding: chunked\r\n\r\n", ); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let mut reader = H1Decoder::new(settings.request_pool()); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert!(msg.is_payload()); let req = msg.message(); assert!(req.chunked().unwrap()); @@ -1216,14 +1203,14 @@ mod tests { transfer-encoding: chunked\r\n\r\n" .iter(), ); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"data"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"line"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert!(msg.eof()); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert!(msg.is_payload()); let req2 = msg.message(); assert!(req2.chunked().unwrap()); @@ -1239,30 +1226,30 @@ mod tests { ); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let mut reader = H1Decoder::new(settings.request_pool()); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert!(msg.is_payload()); let req = msg.message(); assert!(req.chunked().unwrap()); buf.extend(b"4\r\n1111\r\n"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"1111"); buf.extend(b"4\r\ndata\r"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"data"); buf.extend(b"\n4"); - assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); + assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b"\r"); - assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); + assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b"\n"); - assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); + assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b"li"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"li"); //trailers @@ -1270,12 +1257,12 @@ mod tests { //not_ready!(reader.parse(&mut buf, &mut readbuf)); buf.extend(b"ne\r\n0\r\n"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"ne"); - assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); + assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b"\r\n"); - assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof()); + assert!(reader.decode(&mut buf).unwrap().unwrap().eof()); } #[test] @@ -1286,17 +1273,17 @@ mod tests { ); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let mut reader = H1Decoder::new(settings.request_pool()); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert!(msg.is_payload()); assert!(msg.message().chunked().unwrap()); buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") - let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk(); + let chunk = reader.decode(&mut buf).unwrap().unwrap().chunk(); assert_eq!(chunk, Bytes::from_static(b"data")); - let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk(); + let chunk = reader.decode(&mut buf).unwrap().unwrap().chunk(); assert_eq!(chunk, Bytes::from_static(b"line")); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert!(msg.eof()); } } diff --git a/src/server/h1codec.rs b/src/server/h1codec.rs new file mode 100644 index 000000000..ea56110d3 --- /dev/null +++ b/src/server/h1codec.rs @@ -0,0 +1,251 @@ +#![allow(unused_imports, unused_variables, dead_code)] +use std::io::{self, Write}; + +use bytes::{BufMut, Bytes, BytesMut}; +use tokio_codec::{Decoder, Encoder}; + +use super::h1decoder::{H1Decoder, Message}; +use super::helpers; +use super::message::RequestPool; +use super::output::{ResponseInfo, ResponseLength}; +use body::Body; +use error::ParseError; +use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; +use http::Version; +use httpresponse::HttpResponse; + +pub(crate) enum OutMessage { + Response(HttpResponse), + Payload(Bytes), +} + +pub(crate) struct H1Codec { + decoder: H1Decoder, + encoder: H1Writer, +} + +impl H1Codec { + pub fn new(pool: &'static RequestPool) -> Self { + H1Codec { + decoder: H1Decoder::new(pool), + encoder: H1Writer::new(), + } + } +} + +impl Decoder for H1Codec { + type Item = Message; + type Error = ParseError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + self.decoder.decode(src) + } +} + +impl Encoder for H1Codec { + type Item = OutMessage; + type Error = io::Error; + + fn encode( + &mut self, item: Self::Item, dst: &mut BytesMut, + ) -> Result<(), Self::Error> { + match item { + OutMessage::Response(res) => { + self.encoder.encode(res, dst)?; + } + OutMessage::Payload(bytes) => { + dst.extend_from_slice(&bytes); + } + } + Ok(()) + } +} + +bitflags! { + struct Flags: u8 { + const STARTED = 0b0000_0001; + const UPGRADE = 0b0000_0010; + const KEEPALIVE = 0b0000_0100; + const DISCONNECTED = 0b0000_1000; + } +} + +const AVERAGE_HEADER_SIZE: usize = 30; + +struct H1Writer { + flags: Flags, + written: u64, + headers_size: u32, +} + +impl H1Writer { + fn new() -> H1Writer { + H1Writer { + flags: Flags::empty(), + written: 0, + headers_size: 0, + } + } + + fn written(&self) -> u64 { + self.written + } + + pub fn reset(&mut self) { + self.written = 0; + self.flags = Flags::KEEPALIVE; + } + + pub fn upgrade(&self) -> bool { + self.flags.contains(Flags::UPGRADE) + } + + pub fn keepalive(&self) -> bool { + self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE) + } + + fn encode( + &mut self, mut msg: HttpResponse, buffer: &mut BytesMut, + ) -> io::Result<()> { + // prepare task + let info = ResponseInfo::new(false); // req.inner.method == Method::HEAD); + + //if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { + //self.flags = Flags::STARTED | Flags::KEEPALIVE; + //} else { + self.flags = Flags::STARTED; + //} + + // Connection upgrade + let version = msg.version().unwrap_or_else(|| Version::HTTP_11); //req.inner.version); + if msg.upgrade() { + self.flags.insert(Flags::UPGRADE); + msg.headers_mut() + .insert(CONNECTION, HeaderValue::from_static("upgrade")); + } + // keep-alive + else if self.flags.contains(Flags::KEEPALIVE) { + if version < Version::HTTP_11 { + msg.headers_mut() + .insert(CONNECTION, HeaderValue::from_static("keep-alive")); + } + } else if version >= Version::HTTP_11 { + msg.headers_mut() + .insert(CONNECTION, HeaderValue::from_static("close")); + } + let body = msg.replace_body(Body::Empty); + + // render message + { + let reason = msg.reason().as_bytes(); + if let Body::Binary(ref bytes) = body { + buffer.reserve( + 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + + bytes.len() + + reason.len(), + ); + } else { + buffer.reserve( + 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len(), + ); + } + + // status line + helpers::write_status_line(version, msg.status().as_u16(), buffer); + buffer.extend_from_slice(reason); + + // content length + match info.length { + ResponseLength::Chunked => { + buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") + } + ResponseLength::Zero => { + buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n") + } + ResponseLength::Length(len) => { + helpers::write_content_length(len, buffer) + } + ResponseLength::Length64(len) => { + buffer.extend_from_slice(b"\r\ncontent-length: "); + write!(buffer.writer(), "{}", len)?; + buffer.extend_from_slice(b"\r\n"); + } + ResponseLength::None => buffer.extend_from_slice(b"\r\n"), + } + if let Some(ce) = info.content_encoding { + buffer.extend_from_slice(b"content-encoding: "); + buffer.extend_from_slice(ce.as_ref()); + buffer.extend_from_slice(b"\r\n"); + } + + // write headers + let mut pos = 0; + let mut has_date = false; + let mut remaining = buffer.remaining_mut(); + let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) }; + for (key, value) in msg.headers() { + match *key { + TRANSFER_ENCODING => continue, + CONTENT_LENGTH => match info.length { + ResponseLength::None => (), + _ => continue, + }, + DATE => { + has_date = true; + } + _ => (), + } + + let v = value.as_ref(); + let k = key.as_str().as_bytes(); + let len = k.len() + v.len() + 4; + if len > remaining { + unsafe { + buffer.advance_mut(pos); + } + pos = 0; + buffer.reserve(len); + remaining = buffer.remaining_mut(); + unsafe { + buf = &mut *(buffer.bytes_mut() as *mut _); + } + } + + buf[pos..pos + k.len()].copy_from_slice(k); + pos += k.len(); + buf[pos..pos + 2].copy_from_slice(b": "); + pos += 2; + buf[pos..pos + v.len()].copy_from_slice(v); + pos += v.len(); + buf[pos..pos + 2].copy_from_slice(b"\r\n"); + pos += 2; + remaining -= len; + } + unsafe { + buffer.advance_mut(pos); + } + + // optimized date header, set_date writes \r\n + if !has_date { + // self.settings.set_date(&mut buffer, true); + buffer.extend_from_slice(b"\r\n"); + } else { + // msg eof + buffer.extend_from_slice(b"\r\n"); + } + self.headers_size = buffer.len() as u32; + } + + if let Body::Binary(bytes) = body { + self.written = bytes.len() as u64; + // buffer.write(bytes.as_ref())?; + buffer.extend_from_slice(bytes.as_ref()); + } else { + // capacity, makes sense only for streaming or actor + // self.buffer_capacity = msg.write_buffer_capacity(); + + msg.replace_body(body); + } + Ok(()) + } +} diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index 434dc42df..c6f0974aa 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -4,8 +4,7 @@ use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; use httparse; -use super::message::{MessageFlags, Request}; -use super::settings::ServiceConfig; +use super::message::{MessageFlags, Request, RequestPool}; use error::ParseError; use http::header::{HeaderName, HeaderValue}; use http::{header, HttpTryFrom, Method, Uri, Version}; @@ -16,35 +15,26 @@ const MAX_HEADERS: usize = 96; pub(crate) struct H1Decoder { decoder: Option, + pool: &'static RequestPool, } #[derive(Debug)] -pub(crate) enum Message { - Message { msg: Request, payload: bool }, +pub enum Message { + Message(Request), + MessageWithPayload(Request), Chunk(Bytes), Eof, } -#[derive(Debug)] -pub(crate) enum DecoderError { - Io(io::Error), - Error(ParseError), -} - -impl From for DecoderError { - fn from(err: io::Error) -> DecoderError { - DecoderError::Io(err) - } -} - impl H1Decoder { - pub fn new() -> H1Decoder { - H1Decoder { decoder: None } + pub fn new(pool: &'static RequestPool) -> H1Decoder { + H1Decoder { + pool, + decoder: None, + } } - pub fn decode( - &mut self, src: &mut BytesMut, settings: &ServiceConfig, - ) -> Result, DecoderError> { + pub fn decode(&mut self, src: &mut BytesMut) -> Result, ParseError> { // read payload if self.decoder.is_some() { match self.decoder.as_mut().unwrap().decode(src)? { @@ -57,21 +47,19 @@ impl H1Decoder { } } - match self - .parse_message(src, settings) - .map_err(DecoderError::Error)? - { + match self.parse_message(src)? { Async::Ready((msg, decoder)) => { self.decoder = decoder; - Ok(Some(Message::Message { - msg, - payload: self.decoder.is_some(), - })) + if self.decoder.is_some() { + Ok(Some(Message::MessageWithPayload(msg))) + } else { + Ok(Some(Message::Message(msg))) + } } Async::NotReady => { if src.len() >= MAX_BUFFER_SIZE { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - Err(DecoderError::Error(ParseError::TooLarge)) + Err(ParseError::TooLarge) } else { Ok(None) } @@ -79,8 +67,8 @@ impl H1Decoder { } } - fn parse_message( - &self, buf: &mut BytesMut, settings: &ServiceConfig, + fn parse_message( + &self, buf: &mut BytesMut, ) -> Poll<(Request, Option), ParseError> { // Parse http message let mut has_upgrade = false; @@ -119,7 +107,7 @@ impl H1Decoder { let slice = buf.split_to(len).freeze(); // convert headers - let mut msg = settings.get_request(); + let mut msg = RequestPool::get(self.pool); { let inner = msg.inner_mut(); inner diff --git a/src/server/h1disp.rs b/src/server/h1disp.rs new file mode 100644 index 000000000..b1c2c8a21 --- /dev/null +++ b/src/server/h1disp.rs @@ -0,0 +1,425 @@ +// #![allow(unused_imports, unused_variables, dead_code)] +use std::collections::VecDeque; +use std::fmt::{Debug, Display}; +use std::net::SocketAddr; +// use std::time::{Duration, Instant}; + +use actix_net::service::Service; + +use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; +use tokio_codec::Framed; +// use tokio_current_thread::spawn; +use tokio_io::AsyncWrite; +// use tokio_timer::Delay; + +use error::{ParseError, PayloadError}; +use payload::{Payload, PayloadStatus, PayloadWriter}; + +use body::Body; +use httpresponse::HttpResponse; + +use super::error::HttpDispatchError; +use super::h1codec::{H1Codec, OutMessage}; +use super::h1decoder::Message; +use super::input::PayloadType; +use super::message::{Request, RequestPool}; +use super::IoStream; + +const MAX_PIPELINED_MESSAGES: usize = 16; + +bitflags! { + pub struct Flags: u8 { + const STARTED = 0b0000_0001; + const KEEPALIVE_ENABLED = 0b0000_0010; + const KEEPALIVE = 0b0000_0100; + const SHUTDOWN = 0b0000_1000; + const READ_DISCONNECTED = 0b0001_0000; + const WRITE_DISCONNECTED = 0b0010_0000; + const POLLED = 0b0100_0000; + const FLUSHED = 0b1000_0000; + } +} + +/// Dispatcher for HTTP/1.1 protocol +pub struct Http1Dispatcher +where + S::Error: Debug + Display, +{ + service: S, + flags: Flags, + addr: Option, + framed: Framed, + error: Option>, + + state: State, + payload: Option, + messages: VecDeque, +} + +enum State { + None, + Response(S::Future), + SendResponse(Option), + SendResponseWithPayload(Option<(OutMessage, Body)>), + Payload(Body), +} + +impl State { + fn is_empty(&self) -> bool { + if let State::None = self { + true + } else { + false + } + } +} + +impl Http1Dispatcher +where + T: IoStream, + S: Service, + S::Error: Debug + Display, +{ + pub fn new(stream: T, pool: &'static RequestPool, service: S) -> Self { + let addr = stream.peer_addr(); + let flags = Flags::FLUSHED; + let codec = H1Codec::new(pool); + let framed = Framed::new(stream, codec); + + Http1Dispatcher { + payload: None, + state: State::None, + error: None, + messages: VecDeque::new(), + service, + flags, + addr, + framed, + } + } + + #[inline] + fn can_read(&self) -> bool { + if self.flags.contains(Flags::READ_DISCONNECTED) { + return false; + } + + if let Some(ref info) = self.payload { + info.need_read() == PayloadStatus::Read + } else { + true + } + } + + // if checked is set to true, delay disconnect until all tasks have finished. + fn client_disconnected(&mut self, checked: bool) { + self.flags.insert(Flags::READ_DISCONNECTED); + if let Some(mut payload) = self.payload.take() { + payload.set_error(PayloadError::Incomplete); + } + + // if !checked || self.tasks.is_empty() { + // self.flags + // .insert(Flags::WRITE_DISCONNECTED | Flags::FLUSHED); + + // // notify tasks + // for mut task in self.tasks.drain(..) { + // task.disconnected(); + // match task.poll_completed() { + // Ok(Async::NotReady) => { + // // spawn not completed task, it does not require access to io + // // at this point + // spawn(HttpHandlerTaskFut::new(task.into_task())); + // } + // Ok(Async::Ready(_)) => (), + // Err(err) => { + // error!("Unhandled application error: {}", err); + // } + // } + // } + // } + } + + /// Flush stream + fn poll_flush(&mut self) -> Poll<(), HttpDispatchError> { + if self.flags.contains(Flags::STARTED) && !self.flags.contains(Flags::FLUSHED) { + match self.framed.poll_complete() { + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(err) => { + debug!("Error sending data: {}", err); + self.client_disconnected(false); + Err(err.into()) + } + Ok(Async::Ready(_)) => { + // if payload is not consumed we can not use connection + if self.payload.is_some() && self.state.is_empty() { + return Err(HttpDispatchError::PayloadIsNotConsumed); + } + self.flags.insert(Flags::FLUSHED); + Ok(Async::Ready(())) + } + } + } else { + Ok(Async::Ready(())) + } + } + + pub(self) fn poll_handler(&mut self) -> Result<(), HttpDispatchError> { + self.poll_io()?; + let mut retry = self.can_read(); + + // process + loop { + let state = match self.state { + State::None => loop { + break if let Some(msg) = self.messages.pop_front() { + let mut task = self.service.call(msg); + match task.poll() { + Ok(Async::Ready(res)) => { + if res.body().is_streaming() { + unimplemented!() + } else { + Some(Ok(State::SendResponse(Some( + OutMessage::Response(res), + )))) + } + } + Ok(Async::NotReady) => Some(Ok(State::Response(task))), + Err(err) => Some(Err(HttpDispatchError::App(err))), + } + } else { + None + }; + }, + State::Payload(ref mut body) => unimplemented!(), + State::Response(ref mut fut) => { + match fut.poll() { + Ok(Async::Ready(res)) => { + if res.body().is_streaming() { + unimplemented!() + } else { + Some(Ok(State::SendResponse(Some( + OutMessage::Response(res), + )))) + } + } + Ok(Async::NotReady) => None, + Err(err) => { + // it is not possible to recover from error + // during pipe handling, so just drop connection + Some(Err(HttpDispatchError::App(err))) + } + } + } + State::SendResponse(ref mut item) => { + let msg = item.take().expect("SendResponse is empty"); + match self.framed.start_send(msg) { + Ok(AsyncSink::Ready) => { + self.flags.remove(Flags::FLUSHED); + Some(Ok(State::None)) + } + Ok(AsyncSink::NotReady(msg)) => { + *item = Some(msg); + return Ok(()); + } + Err(err) => Some(Err(HttpDispatchError::Io(err))), + } + } + State::SendResponseWithPayload(ref mut item) => { + let (msg, body) = item.take().expect("SendResponse is empty"); + match self.framed.start_send(msg) { + Ok(AsyncSink::Ready) => { + self.flags.remove(Flags::FLUSHED); + Some(Ok(State::Payload(body))) + } + Ok(AsyncSink::NotReady(msg)) => { + *item = Some((msg, body)); + return Ok(()); + } + Err(err) => Some(Err(HttpDispatchError::Io(err))), + } + } + }; + + match state { + Some(Ok(state)) => self.state = state, + Some(Err(err)) => { + // error!("Unhandled error1: {}", err); + self.client_disconnected(false); + return Err(err); + } + None => { + // if read-backpressure is enabled and we consumed some data. + // we may read more dataand retry + if !retry && self.can_read() && self.poll_io()? { + retry = self.can_read(); + continue; + } + break; + } + } + } + + Ok(()) + } + + fn one_message(&mut self, msg: Message) -> Result<(), HttpDispatchError> { + self.flags.insert(Flags::STARTED); + + match msg { + Message::Message(mut msg) => { + // set remote addr + msg.inner_mut().addr = self.addr; + + // handle request early + if self.state.is_empty() { + let mut task = self.service.call(msg); + match task.poll() { + Ok(Async::Ready(res)) => { + if res.body().is_streaming() { + unimplemented!() + } else { + self.state = + State::SendResponse(Some(OutMessage::Response(res))); + } + } + Ok(Async::NotReady) => self.state = State::Response(task), + Err(err) => { + error!("Unhandled application error: {}", err); + self.client_disconnected(false); + return Err(HttpDispatchError::App(err)); + } + } + } else { + self.messages.push_back(msg); + } + } + Message::MessageWithPayload(mut msg) => { + // set remote addr + msg.inner_mut().addr = self.addr; + + // payload + let (ps, pl) = Payload::new(false); + *msg.inner.payload.borrow_mut() = Some(pl); + self.payload = Some(PayloadType::new(&msg.inner.headers, ps)); + + self.messages.push_back(msg); + } + Message::Chunk(chunk) => { + if let Some(ref mut payload) = self.payload { + payload.feed_data(chunk); + } else { + error!("Internal server error: unexpected payload chunk"); + self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); + // self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); + self.error = Some(HttpDispatchError::InternalError); + } + } + Message::Eof => { + if let Some(mut payload) = self.payload.take() { + payload.feed_eof(); + } else { + error!("Internal server error: unexpected eof"); + self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); + // self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); + self.error = Some(HttpDispatchError::InternalError); + } + } + } + + Ok(()) + } + + pub(self) fn poll_io(&mut self) -> Result> { + let mut updated = false; + + if self.messages.len() < MAX_PIPELINED_MESSAGES { + 'outer: loop { + match self.framed.poll() { + Ok(Async::Ready(Some(msg))) => { + updated = true; + self.one_message(msg)?; + } + Ok(Async::Ready(None)) => { + if self.flags.contains(Flags::READ_DISCONNECTED) { + self.client_disconnected(true); + } + break; + } + Ok(Async::NotReady) => break, + Err(e) => { + if let Some(mut payload) = self.payload.take() { + let e = match e { + ParseError::Io(e) => PayloadError::Io(e), + _ => PayloadError::EncodingCorrupted, + }; + payload.set_error(e); + } + + // Malformed requests should be responded with 400 + // self.push_response_entry(StatusCode::BAD_REQUEST); + self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); + self.error = Some(HttpDispatchError::MalformedRequest); + break; + } + } + } + } + + Ok(updated) + } +} + +impl Future for Http1Dispatcher +where + T: IoStream, + S: Service, + S::Error: Debug + Display, +{ + type Item = (); + type Error = HttpDispatchError; + + #[inline] + fn poll(&mut self) -> Poll<(), Self::Error> { + // shutdown + if self.flags.contains(Flags::SHUTDOWN) { + if self.flags.contains(Flags::WRITE_DISCONNECTED) { + return Ok(Async::Ready(())); + } + try_ready!(self.poll_flush()); + return Ok(AsyncWrite::shutdown(self.framed.get_mut())?); + } + + // process incoming requests + if !self.flags.contains(Flags::WRITE_DISCONNECTED) { + self.poll_handler()?; + + // flush stream + self.poll_flush()?; + + // deal with keep-alive and stream eof (client-side write shutdown) + if self.state.is_empty() && self.flags.contains(Flags::FLUSHED) { + // handle stream eof + if self + .flags + .intersects(Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED) + { + return Ok(Async::Ready(())); + } + // no keep-alive + if self.flags.contains(Flags::STARTED) + && (!self.flags.contains(Flags::KEEPALIVE_ENABLED) + || !self.flags.contains(Flags::KEEPALIVE)) + { + self.flags.insert(Flags::SHUTDOWN); + return self.poll(); + } + } + Ok(Async::NotReady) + } else if let Some(err) = self.error.take() { + Err(err) + } else { + Ok(Async::Ready(())) + } + } +} diff --git a/src/server/h2.rs b/src/server/h2.rs index 2fe2fa073..27ae47851 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -86,7 +86,7 @@ where &self.settings } - pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { + pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { // server if let State::Connection(ref mut conn) = self.state { // keep-alive timer diff --git a/src/server/message.rs b/src/server/message.rs index 9c4bc1ec4..74ec5f17c 100644 --- a/src/server/message.rs +++ b/src/server/message.rs @@ -241,10 +241,7 @@ impl fmt::Debug for Request { } } -pub(crate) struct RequestPool( - RefCell>>, - RefCell, -); +pub struct RequestPool(RefCell>>, RefCell); thread_local!(static POOL: &'static RequestPool = RequestPool::create()); @@ -257,7 +254,7 @@ impl RequestPool { Box::leak(Box::new(pool)) } - pub fn pool(settings: ServerSettings) -> &'static RequestPool { + pub(crate) fn pool(settings: ServerSettings) -> &'static RequestPool { POOL.with(|p| { *p.1.borrow_mut() = settings; *p @@ -275,7 +272,7 @@ impl RequestPool { #[inline] /// Release request instance - pub fn release(&self, msg: Rc) { + pub(crate) fn release(&self, msg: Rc) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { v.push_front(msg); diff --git a/src/server/mod.rs b/src/server/mod.rs index 3277dba5a..ce3f2fbf6 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -122,7 +122,10 @@ pub(crate) mod builder; mod channel; mod error; pub(crate) mod h1; -pub(crate) mod h1decoder; +#[doc(hidden)] +pub mod h1codec; +#[doc(hidden)] +pub mod h1decoder; mod h1writer; mod h2; mod h2writer; @@ -145,6 +148,9 @@ pub use self::ssl::*; pub use self::error::{AcceptorError, HttpDispatchError}; pub use self::settings::ServerSettings; +#[doc(hidden)] +pub mod h1disp; + #[doc(hidden)] pub use self::acceptor::AcceptorTimeout; diff --git a/src/server/output.rs b/src/server/output.rs index 70c24facc..1da7e9025 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -10,7 +10,7 @@ use bytes::BytesMut; use flate2::write::{GzEncoder, ZlibEncoder}; #[cfg(feature = "flate2")] use flate2::Compression; -use http::header::{ACCEPT_ENCODING, CONTENT_LENGTH}; +use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; use super::message::InnerRequest; @@ -18,6 +18,12 @@ use body::{Binary, Body}; use header::ContentEncoding; use httpresponse::HttpResponse; +// #[derive(Debug)] +// pub(crate) struct RequestInfo { +// pub version: Version, +// pub accept_encoding: Option, +// } + #[derive(Debug)] pub(crate) enum ResponseLength { Chunked, diff --git a/src/server/service.rs b/src/server/service.rs index e3402e305..a55c33f72 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -10,6 +10,7 @@ use super::error::HttpDispatchError; use super::handler::HttpHandler; use super::settings::ServiceConfig; use super::IoStream; +use error::Error; /// `NewService` implementation for HTTP1/HTTP2 transports pub struct HttpService @@ -42,7 +43,7 @@ where { type Request = Io; type Response = (); - type Error = HttpDispatchError; + type Error = HttpDispatchError; type InitError = (); type Service = HttpServiceHandler; type Future = FutureResult; @@ -81,7 +82,7 @@ where { type Request = Io; type Response = (); - type Error = HttpDispatchError; + type Error = HttpDispatchError; type Future = HttpChannel; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -124,7 +125,7 @@ where { type Request = Io; type Response = (); - type Error = HttpDispatchError; + type Error = HttpDispatchError; type InitError = (); type Service = H1ServiceHandler; type Future = FutureResult; @@ -164,7 +165,7 @@ where { type Request = Io; type Response = (); - type Error = HttpDispatchError; + type Error = HttpDispatchError; type Future = H1Channel; fn poll_ready(&mut self) -> Poll<(), Self::Error> { diff --git a/src/server/settings.rs b/src/server/settings.rs index 9b27ed5e5..9df1e457f 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -215,6 +215,11 @@ impl ServiceConfig { RequestPool::get(self.0.messages) } + #[doc(hidden)] + pub fn request_pool(&self) -> &'static RequestPool { + self.0.messages + } + fn update_date(&self) { // Unsafe: WorkerSetting is !Sync and !Send unsafe { (*self.0.date.get()).0 = false }; diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs new file mode 100644 index 000000000..7e8e9a42c --- /dev/null +++ b/tests/test_h1v2.rs @@ -0,0 +1,58 @@ +extern crate actix; +extern crate actix_net; +extern crate actix_web; +extern crate futures; + +use std::thread; + +use actix::System; +use actix_net::server::Server; +use actix_net::service::{IntoNewService, IntoService}; +use futures::future; + +use actix_web::server::h1disp::Http1Dispatcher; +use actix_web::server::KeepAlive; +use actix_web::server::ServiceConfig; +use actix_web::{client, test, App, Error, HttpRequest, HttpResponse}; + +#[test] +fn test_h1_v2() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + let app = App::new() + .route("/", http::Method::GET, |_: HttpRequest| "OK") + .finish(); + let settings = ServiceConfig::build(app) + .keep_alive(KeepAlive::Disabled) + .client_timeout(1000) + .client_shutdown(1000) + .server_hostname("localhost") + .server_address(addr) + .finish(); + + (move |io| { + let pool = settings.request_pool(); + Http1Dispatcher::new( + io, + pool, + (|req| { + println!("REQ: {:?}", req); + future::ok::<_, Error>(HttpResponse::Ok().finish()) + }).into_service(), + ) + }).into_new_service() + }).unwrap() + .run(); + }); + + let mut sys = System::new("test"); + { + let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + } +} From 6aa2de7b8d655f9fd0dc25d67c90d32a580a101c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 17:00:27 -0700 Subject: [PATCH 002/208] remove actix-web artifacts --- CHANGES.md | 712 +----------- Cargo.toml | 15 +- MIGRATION.md | 176 --- Makefile | 14 - README.md | 44 +- build.rs | 16 - src/application.rs | 879 --------------- src/body.rs | 29 +- src/client/connector.rs | 1344 ---------------------- src/client/mod.rs | 118 -- src/client/parser.rs | 238 ---- src/client/pipeline.rs | 552 --------- src/client/request.rs | 782 ------------- src/client/response.rs | 124 --- src/client/writer.rs | 412 ------- src/context.rs | 294 ----- src/de.rs | 443 -------- src/error.rs | 15 +- src/extractor.rs | 1024 ----------------- src/fs.rs | 1786 ------------------------------ src/handler.rs | 562 ---------- src/helpers.rs | 571 ---------- src/httpmessage.rs | 41 - src/httpresponse.rs | 110 +- src/json.rs | 99 +- src/lib.rs | 101 +- src/middleware/cors.rs | 1183 -------------------- src/middleware/csrf.rs | 275 ----- src/middleware/defaultheaders.rs | 120 -- src/middleware/errhandlers.rs | 141 --- src/middleware/identity.rs | 387 ------- src/middleware/logger.rs | 384 ------- src/middleware/mod.rs | 68 -- src/middleware/session.rs | 617 ----------- src/multipart.rs | 815 -------------- src/param.rs | 303 ----- src/pipeline.rs | 869 --------------- src/pred.rs | 328 ------ src/resource.rs | 324 ------ src/route.rs | 666 ----------- src/router.rs | 1247 --------------------- src/scope.rs | 1236 --------------------- src/server/acceptor.rs | 396 ------- src/server/builder.rs | 117 -- src/server/channel.rs | 436 -------- src/server/error.rs | 29 - src/server/h1writer.rs | 356 ------ src/server/h2.rs | 453 -------- src/server/h2writer.rs | 259 ----- src/server/handler.rs | 208 ---- src/server/http.rs | 584 ---------- src/server/incoming.rs | 69 -- src/server/mod.rs | 147 +-- src/server/output.rs | 2 +- src/server/settings.rs | 44 +- src/server/ssl/mod.rs | 12 - src/server/ssl/nativetls.rs | 34 - src/server/ssl/openssl.rs | 87 -- src/server/ssl/rustls.rs | 87 -- src/with.rs | 383 ------- tests/test_client.rs | 508 --------- tests/test_custom_pipeline.rs | 81 -- tests/test_h1v2.rs | 14 +- tests/test_handlers.rs | 677 ----------- tests/test_middleware.rs | 1055 ------------------ tests/test_server.rs | 1357 ----------------------- tests/test_ws.rs | 394 ------- 67 files changed, 51 insertions(+), 27202 deletions(-) delete mode 100644 MIGRATION.md delete mode 100644 Makefile delete mode 100644 build.rs delete mode 100644 src/application.rs delete mode 100644 src/client/connector.rs delete mode 100644 src/client/mod.rs delete mode 100644 src/client/parser.rs delete mode 100644 src/client/pipeline.rs delete mode 100644 src/client/request.rs delete mode 100644 src/client/response.rs delete mode 100644 src/client/writer.rs delete mode 100644 src/context.rs delete mode 100644 src/de.rs delete mode 100644 src/extractor.rs delete mode 100644 src/fs.rs delete mode 100644 src/handler.rs delete mode 100644 src/helpers.rs delete mode 100644 src/middleware/cors.rs delete mode 100644 src/middleware/csrf.rs delete mode 100644 src/middleware/defaultheaders.rs delete mode 100644 src/middleware/errhandlers.rs delete mode 100644 src/middleware/identity.rs delete mode 100644 src/middleware/logger.rs delete mode 100644 src/middleware/mod.rs delete mode 100644 src/middleware/session.rs delete mode 100644 src/multipart.rs delete mode 100644 src/param.rs delete mode 100644 src/pipeline.rs delete mode 100644 src/pred.rs delete mode 100644 src/resource.rs delete mode 100644 src/route.rs delete mode 100644 src/router.rs delete mode 100644 src/scope.rs delete mode 100644 src/server/acceptor.rs delete mode 100644 src/server/builder.rs delete mode 100644 src/server/channel.rs delete mode 100644 src/server/h1writer.rs delete mode 100644 src/server/h2.rs delete mode 100644 src/server/h2writer.rs delete mode 100644 src/server/handler.rs delete mode 100644 src/server/http.rs delete mode 100644 src/server/incoming.rs delete mode 100644 src/server/ssl/mod.rs delete mode 100644 src/server/ssl/nativetls.rs delete mode 100644 src/server/ssl/openssl.rs delete mode 100644 src/server/ssl/rustls.rs delete mode 100644 src/with.rs delete mode 100644 tests/test_client.rs delete mode 100644 tests/test_custom_pipeline.rs delete mode 100644 tests/test_handlers.rs delete mode 100644 tests/test_middleware.rs delete mode 100644 tests/test_server.rs delete mode 100644 tests/test_ws.rs diff --git a/CHANGES.md b/CHANGES.md index 3c55c3f64..c3c38011c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,713 +1,5 @@ # Changes -## [0.7.9] - 2018-09-x +## [0.1.0] - 2018-09-x -### Added - -* Added client shutdown timeout setting - -* Added slow request timeout setting - -* Respond with 408 response on slow request timeout #523 - - -### Fixed - -* HTTP1 decoding errors are reported to the client. #512 - -* Correctly compose multiple allowed origins in CORS. #517 - -* Websocket server finished() isn't called if client disconnects #511 - -* Responses with the following codes: 100, 101, 102, 204 -- are sent without Content-Length header. #521 - -* Correct usage of `no_http2` flag in `bind_*` methods. #519 - - -## [0.7.8] - 2018-09-17 - -### Added - -* Use server `Keep-Alive` setting as slow request timeout #439 - -### Changed - -* Use 5 seconds keep-alive timer by default. - -### Fixed - -* Fixed wrong error message for i16 type #510 - - -## [0.7.7] - 2018-09-11 - -### Fixed - -* Fix linked list of HttpChannels #504 - -* Fix requests to TestServer fail #508 - - -## [0.7.6] - 2018-09-07 - -### Fixed - -* Fix system_exit in HttpServer #501 - -* Fix parsing of route param containin regexes with repetition #500 - -### Changes - -* Unhide `SessionBackend` and `SessionImpl` traits #455 - - -## [0.7.5] - 2018-09-04 - -### Added - -* Added the ability to pass a custom `TlsConnector`. - -* Allow to register handlers on scope level #465 - - -### Fixed - -* Handle socket read disconnect - -* Handling scoped paths without leading slashes #460 - - -### Changed - -* Read client response until eof if connection header set to close #464 - - -## [0.7.4] - 2018-08-23 - -### Added - -* Added `HttpServer::maxconn()` and `HttpServer::maxconnrate()`, - accept backpressure #250 - -* Allow to customize connection handshake process via `HttpServer::listen_with()` - and `HttpServer::bind_with()` methods - -* Support making client connections via `tokio-uds`'s `UnixStream` when "uds" feature is enabled #472 - -### Changed - -* It is allowed to use function with up to 10 parameters for handler with `extractor parameters`. - `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple - even for handler with one parameter. - -* native-tls - 0.2 - -* `Content-Disposition` is re-worked. Its parser is now more robust and handles quoted content better. See #461 - -### Fixed - -* Use zlib instead of raw deflate for decoding and encoding payloads with - `Content-Encoding: deflate`. - -* Fixed headers formating for CORS Middleware Access-Control-Expose-Headers #436 - -* Fix adding multiple response headers #446 - -* Client includes port in HOST header when it is not default(e.g. not 80 and 443). #448 - -* Panic during access without routing being set #452 - -* Fixed http/2 error handling - -### Deprecated - -* `HttpServer::no_http2()` is deprecated, use `OpensslAcceptor::with_flags()` or - `RustlsAcceptor::with_flags()` instead - -* `HttpServer::listen_tls()`, `HttpServer::listen_ssl()`, `HttpServer::listen_rustls()` have been - deprecated in favor of `HttpServer::listen_with()` with specific `acceptor`. - -* `HttpServer::bind_tls()`, `HttpServer::bind_ssl()`, `HttpServer::bind_rustls()` have been - deprecated in favor of `HttpServer::bind_with()` with specific `acceptor`. - - -## [0.7.3] - 2018-08-01 - -### Added - -* Support HTTP/2 with rustls #36 - -* Allow TestServer to open a websocket on any URL (TestServer::ws_at()) #433 - -### Fixed - -* Fixed failure 0.1.2 compatibility - -* Do not override HOST header for client request #428 - -* Gz streaming, use `flate2::write::GzDecoder` #228 - -* HttpRequest::url_for is not working with scopes #429 - -* Fixed headers' formating for CORS Middleware `Access-Control-Expose-Headers` header value to HTTP/1.1 & HTTP/2 spec-compliant format #436 - - -## [0.7.2] - 2018-07-26 - -### Added - -* Add implementation of `FromRequest` for `Option` and `Result` - -* Allow to handle application prefix, i.e. allow to handle `/app` path - for application with `/app` prefix. - Check [`App::prefix()`](https://actix.rs/actix-web/actix_web/struct.App.html#method.prefix) - api doc. - -* Add `CookieSessionBackend::http_only` method to set `HttpOnly` directive of cookies - -### Changed - -* Upgrade to cookie 0.11 - -* Removed the timestamp from the default logger middleware - -### Fixed - -* Missing response header "content-encoding" #421 - -* Fix stream draining for http/2 connections #290 - - -## [0.7.1] - 2018-07-21 - -### Fixed - -* Fixed default_resource 'not yet implemented' panic #410 - - -## [0.7.0] - 2018-07-21 - -### Added - -* Add `fs::StaticFileConfig` to provide means of customizing static - file services. It allows to map `mime` to `Content-Disposition`, - specify whether to use `ETag` and `Last-Modified` and allowed methods. - -* Add `.has_prefixed_resource()` method to `router::ResourceInfo` - for route matching with prefix awareness - -* Add `HttpMessage::readlines()` for reading line by line. - -* Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests. - -* Add method to configure custom error handler to `Form` extractor. - -* Add methods to `HttpResponse` to retrieve, add, and delete cookies - -* Add `.set_content_type()` and `.set_content_disposition()` methods - to `fs::NamedFile` to allow overriding the values inferred by default - -* Add `fs::file_extension_to_mime()` helper function to get the MIME - type for a file extension - -* Add `.content_disposition()` method to parse Content-Disposition of - multipart fields - -* Re-export `actix::prelude::*` as `actix_web::actix` module. - -* `HttpRequest::url_for_static()` for a named route with no variables segments - -* Propagation of the application's default resource to scopes that haven't set a default resource. - - -### Changed - -* Min rustc version is 1.26 - -* Use tokio instead of tokio-core - -* `CookieSessionBackend` sets percent encoded cookies for outgoing HTTP messages. - -* Became possible to use enums with query extractor. - Issue [#371](https://github.com/actix/actix-web/issues/371). - [Example](https://github.com/actix/actix-web/blob/master/tests/test_handlers.rs#L94-L134) - -* `HttpResponse::into_builder()` now moves cookies into the builder - instead of dropping them - -* For safety and performance reasons `Handler::handle()` uses `&self` instead of `&mut self` - -* `Handler::handle()` uses `&HttpRequest` instead of `HttpRequest` - -* Added header `User-Agent: Actix-web/` to default headers when building a request - -* port `Extensions` type from http create, we don't need `Send + Sync` - -* `HttpRequest::query()` returns `Ref>` - -* `HttpRequest::cookies()` returns `Ref>>` - -* `StaticFiles::new()` returns `Result, Error>` instead of `StaticFiles` - -* `StaticFiles` uses the default handler if the file does not exist - - -### Removed - -* Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead. - -* Remove `HttpMessage::range()` - - -## [0.6.15] - 2018-07-11 - -### Fixed - -* Fix h2 compatibility #352 - -* Fix duplicate tail of StaticFiles with index_file. #344 - - -## [0.6.14] - 2018-06-21 - -### Added - -* Allow to disable masking for websockets client - -### Fixed - -* SendRequest execution fails with the "internal error: entered unreachable code" #329 - - -## [0.6.13] - 2018-06-11 - -* http/2 end-of-frame is not set if body is empty bytes #307 - -* InternalError can trigger memory unsafety #301 - - -## [0.6.12] - 2018-06-08 - -### Added - -* Add `Host` filter #287 - -* Allow to filter applications - -* Improved failure interoperability with downcasting #285 - -* Allow to use custom resolver for `ClientConnector` - - -## [0.6.11] - 2018-06-05 - -* Support chunked encoding for UrlEncoded body #262 - -* `HttpRequest::url_for()` for a named route with no variables segments #265 - -* `Middleware::response()` is not invoked if error result was returned by another `Middleware::start()` #255 - -* CORS: Do not validate Origin header on non-OPTION requests #271 - -* Fix multipart upload "Incomplete" error #282 - - -## [0.6.10] - 2018-05-24 - -### Added - -* Allow to use path without trailing slashes for scope registration #241 - -* Allow to set encoding for exact NamedFile #239 - -### Fixed - -* `TestServer::post()` actually sends `GET` request #240 - - -## 0.6.9 (2018-05-22) - -* Drop connection if request's payload is not fully consumed #236 - -* Fix streaming response with body compression - - -## 0.6.8 (2018-05-20) - -* Fix scope resource path extractor #234 - -* Re-use tcp listener on pause/resume - - -## 0.6.7 (2018-05-17) - -* Fix compilation with --no-default-features - - -## 0.6.6 (2018-05-17) - -* Panic during middleware execution #226 - -* Add support for listen_tls/listen_ssl #224 - -* Implement extractor for `Session` - -* Ranges header support for NamedFile #60 - - -## 0.6.5 (2018-05-15) - -* Fix error handling during request decoding #222 - - -## 0.6.4 (2018-05-11) - -* Fix segfault in ServerSettings::get_response_builder() - - -## 0.6.3 (2018-05-10) - -* Add `Router::with_async()` method for async handler registration. - -* Added error response functions for 501,502,503,504 - -* Fix client request timeout handling - - -## 0.6.2 (2018-05-09) - -* WsWriter trait is optional. - - -## 0.6.1 (2018-05-08) - -* Fix http/2 payload streaming #215 - -* Fix connector's default `keep-alive` and `lifetime` settings #212 - -* Send `ErrorNotFound` instead of `ErrorBadRequest` when path extractor fails #214 - -* Allow to exclude certain endpoints from logging #211 - - -## 0.6.0 (2018-05-08) - -* Add route scopes #202 - -* Allow to use ssl and non-ssl connections at the same time #206 - -* Websocket CloseCode Empty/Status is ambiguous #193 - -* Add Content-Disposition to NamedFile #204 - -* Allow to access Error's backtrace object - -* Allow to override files listing renderer for `StaticFiles` #203 - -* Various extractor usability improvements #207 - - -## 0.5.6 (2018-04-24) - -* Make flate2 crate optional #200 - - -## 0.5.5 (2018-04-24) - -* Fix panic when Websocket is closed with no error code #191 - -* Allow to use rust backend for flate2 crate #199 - -## 0.5.4 (2018-04-19) - -* Add identity service middleware - -* Middleware response() is not invoked if there was an error in async handler #187 - -* Use Display formatting for InternalError Display implementation #188 - - -## 0.5.3 (2018-04-18) - -* Impossible to quote slashes in path parameters #182 - - -## 0.5.2 (2018-04-16) - -* Allow to configure StaticFiles's CpuPool, via static method or env variable - -* Add support for custom handling of Json extractor errors #181 - -* Fix StaticFiles does not support percent encoded paths #177 - -* Fix Client Request with custom Body Stream halting on certain size requests #176 - - -## 0.5.1 (2018-04-12) - -* Client connector provides stats, `ClientConnector::stats()` - -* Fix end-of-stream handling in parse_payload #173 - -* Fix StaticFiles generate a lot of threads #174 - - -## 0.5.0 (2018-04-10) - -* Type-safe path/query/form parameter handling, using serde #70 - -* HttpResponse builder's methods `.body()`, `.finish()`, `.json()` - return `HttpResponse` instead of `Result` - -* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()` - -* Added `signed` and `private` `CookieSessionBackend`s - -* Added `HttpRequest::resource()`, returns current matched resource - -* Added `ErrorHandlers` middleware - -* Fix router cannot parse Non-ASCII characters in URL #137 - -* Fix client connection pooling - -* Fix long client urls #129 - -* Fix panic on invalid URL characters #130 - -* Fix logger request duration calculation #152 - -* Fix prefix and static file serving #168 - - -## 0.4.10 (2018-03-20) - -* Use `Error` instead of `InternalError` for `error::ErrorXXXX` methods - -* Allow to set client request timeout - -* Allow to set client websocket handshake timeout - -* Refactor `TestServer` configuration - -* Fix server websockets big payloads support - -* Fix http/2 date header generation - - -## 0.4.9 (2018-03-16) - -* Allow to disable http/2 support - -* Wake payload reading task when data is available - -* Fix server keep-alive handling - -* Send Query Parameters in client requests #120 - -* Move brotli encoding to a feature - -* Add option of default handler for `StaticFiles` handler #57 - -* Add basic client connection pooling - - -## 0.4.8 (2018-03-12) - -* Allow to set read buffer capacity for server request - -* Handle WouldBlock error for socket accept call - - -## 0.4.7 (2018-03-11) - -* Fix panic on unknown content encoding - -* Fix connection get closed too early - -* Fix streaming response handling for http/2 - -* Better sleep on error support - - -## 0.4.6 (2018-03-10) - -* Fix client cookie handling - -* Fix json content type detection - -* Fix CORS middleware #117 - -* Optimize websockets stream support - - -## 0.4.5 (2018-03-07) - -* Fix compression #103 and #104 - -* Fix client cookie handling #111 - -* Non-blocking processing of a `NamedFile` - -* Enable compression support for `NamedFile` - -* Better support for `NamedFile` type - -* Add `ResponseError` impl for `SendRequestError`. This improves ergonomics of the client. - -* Add native-tls support for client - -* Allow client connection timeout to be set #108 - -* Allow to use std::net::TcpListener for HttpServer - -* Handle panics in worker threads - - -## 0.4.4 (2018-03-04) - -* Allow to use Arc> as response/request body - -* Fix handling of requests with an encoded body with a length > 8192 #93 - -## 0.4.3 (2018-03-03) - -* Fix request body read bug - -* Fix segmentation fault #79 - -* Set reuse address before bind #90 - - -## 0.4.2 (2018-03-02) - -* Better naming for websockets implementation - -* Add `Pattern::with_prefix()`, make it more usable outside of actix - -* Add csrf middleware for filter for cross-site request forgery #89 - -* Fix disconnect on idle connections - - -## 0.4.1 (2018-03-01) - -* Rename `Route::p()` to `Route::filter()` - -* Better naming for http codes - -* Fix payload parse in situation when socket data is not ready. - -* Fix Session mutable borrow lifetime #87 - - -## 0.4.0 (2018-02-28) - -* Actix 0.5 compatibility - -* Fix request json/urlencoded loaders - -* Simplify HttpServer type definition - -* Added HttpRequest::encoding() method - -* Added HttpRequest::mime_type() method - -* Added HttpRequest::uri_mut(), allows to modify request uri - -* Added StaticFiles::index_file() - -* Added http client - -* Added websocket client - -* Added TestServer::ws(), test websockets client - -* Added TestServer http client support - -* Allow to override content encoding on application level - - -## 0.3.3 (2018-01-25) - -* Stop processing any events after context stop - -* Re-enable write back-pressure for h1 connections - -* Refactor HttpServer::start_ssl() method - -* Upgrade openssl to 0.10 - - -## 0.3.2 (2018-01-21) - -* Fix HEAD requests handling - -* Log request processing errors - -* Always enable content encoding if encoding explicitly selected - -* Allow multiple Applications on a single server with different state #49 - -* CORS middleware: allowed_headers is defaulting to None #50 - - -## 0.3.1 (2018-01-13) - -* Fix directory entry path #47 - -* Do not enable chunked encoding for HTTP/1.0 - -* Allow explicitly disable chunked encoding - - -## 0.3.0 (2018-01-12) - -* HTTP/2 Support - -* Refactor streaming responses - -* Refactor error handling - -* Asynchronous middlewares - -* Refactor logger middleware - -* Content compression/decompression (br, gzip, deflate) - -* Server multi-threading - -* Graceful shutdown support - - -## 0.2.1 (2017-11-03) - -* Allow to start tls server with `HttpServer::serve_tls` - -* Export `Frame` enum - -* Add conversion impl from `HttpResponse` and `BinaryBody` to a `Frame` - - -## 0.2.0 (2017-10-30) - -* Do not use `http::Uri` as it can not parse some valid paths - -* Refactor response `Body` - -* Refactor `RouteRecognizer` usability - -* Refactor `HttpContext::write` - -* Refactor `Payload` stream - -* Re-use `BinaryBody` for `Frame::Payload` - -* Stop http actor on `write_eof` - -* Fix disconnection handling. - - -## 0.1.0 (2017-10-23) - -* First release +* Initial impl diff --git a/Cargo.toml b/Cargo.toml index d70a65cf0..258301daa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "actix-web" -version = "0.7.9" +name = "actix-http" +version = "0.1.0" authors = ["Nikolay Kim "] -description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." +description = "Actix http" readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" @@ -14,7 +14,6 @@ categories = ["network-programming", "asynchronous", "web-programming::websocket"] license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] -build = "build.rs" [package.metadata.docs.rs] features = ["tls", "alpn", "rust-tls", "session", "brotli", "flate2-c"] @@ -25,7 +24,7 @@ appveyor = { repository = "fafhrd91/actix-web-hdy9d" } codecov = { repository = "actix/actix-web", branch = "master", service = "github" } [lib] -name = "actix_web" +name = "actix_http" path = "src/lib.rs" [features] @@ -130,6 +129,7 @@ webpki-roots = { version = "0.15", optional = true } tokio-uds = { version="0.2", optional = true } [dev-dependencies] +actix-web = "0.7" env_logger = "0.5" serde_derive = "1.0" @@ -140,8 +140,3 @@ version_check = "0.1" lto = true opt-level = 3 codegen-units = 1 - -[workspace] -members = [ - "./", -] diff --git a/MIGRATION.md b/MIGRATION.md deleted file mode 100644 index 3c0bdd943..000000000 --- a/MIGRATION.md +++ /dev/null @@ -1,176 +0,0 @@ -## 0.7.4 - -* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple - even for handler with one parameter. - - -## 0.7 - -* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload - use `HttpMessage::payload()` method. - - instead of - - ```rust - fn index(req: HttpRequest) -> impl Responder { - req - .from_err() - .fold(...) - .... - } - ``` - - use `.payload()` - - ```rust - fn index(req: HttpRequest) -> impl Responder { - req - .payload() // <- get request payload stream - .from_err() - .fold(...) - .... - } - ``` - -* [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html) - trait uses `&HttpRequest` instead of `&mut HttpRequest`. - -* Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. - - instead of - - ```rust - fn index(query: Query<..>, info: Json impl Responder {} - ``` - - use tuple of extractors and use `.with()` for registration: - - ```rust - fn index((query, json): (Query<..>, Json impl Responder {} - ``` - -* `Handler::handle()` uses `&self` instead of `&mut self` - -* `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value - -* Removed deprecated `HttpServer::threads()`, use - [HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead. - -* Renamed `client::ClientConnectorError::Connector` to - `client::ClientConnectorError::Resolver` - -* `Route::with()` does not return `ExtractorConfig`, to configure - extractor use `Route::with_config()` - - instead of - - ```rust - fn main() { - let app = App::new().resource("/index.html", |r| { - r.method(http::Method::GET) - .with(index) - .limit(4096); // <- limit size of the payload - }); - } - ``` - - use - - ```rust - - fn main() { - let app = App::new().resource("/index.html", |r| { - r.method(http::Method::GET) - .with_config(index, |cfg| { // <- register handler - cfg.limit(4096); // <- limit size of the payload - }) - }); - } - ``` - -* `Route::with_async()` does not return `ExtractorConfig`, to configure - extractor use `Route::with_async_config()` - - -## 0.6 - -* `Path` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest` - -* `ws::Message::Close` now includes optional close reason. - `ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed. - -* `HttpServer::threads()` renamed to `HttpServer::workers()`. - -* `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated. - Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead. - -* `HttpRequest::extensions()` returns read only reference to the request's Extension - `HttpRequest::extensions_mut()` returns mutable reference. - -* Instead of - - `use actix_web::middleware::{ - CookieSessionBackend, CookieSessionError, RequestSession, - Session, SessionBackend, SessionImpl, SessionStorage};` - - use `actix_web::middleware::session` - - `use actix_web::middleware::session{CookieSessionBackend, CookieSessionError, - RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};` - -* `FromRequest::from_request()` accepts mutable reference to a request - -* `FromRequest::Result` has to implement `Into>` - -* [`Responder::respond_to()`]( - https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to) - is generic over `S` - -* Use `Query` extractor instead of HttpRequest::query()`. - - ```rust - fn index(q: Query>) -> Result<..> { - ... - } - ``` - - or - - ```rust - let q = Query::>::extract(req); - ``` - -* Websocket operations are implemented as `WsWriter` trait. - you need to use `use actix_web::ws::WsWriter` - - -## 0.5 - -* `HttpResponseBuilder::body()`, `.finish()`, `.json()` - methods return `HttpResponse` instead of `Result` - -* `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version` - moved to `actix_web::http` module - -* `actix_web::header` moved to `actix_web::http::header` - -* `NormalizePath` moved to `actix_web::http` module - -* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function, - shortcut for `actix_web::server::HttpServer::new()` - -* `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself - -* `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead. - -* `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type - -* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()` - functions should be used instead - -* `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>` - instead of `Result<_, http::Error>` - -* `Application` renamed to a `App` - -* `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev` diff --git a/Makefile b/Makefile deleted file mode 100644 index e3b8b2cf1..000000000 --- a/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -.PHONY: default build test doc book clean - -CARGO_FLAGS := --features "$(FEATURES) alpn tls" - -default: test - -build: - cargo build $(CARGO_FLAGS) - -test: build clippy - cargo test $(CARGO_FLAGS) - -doc: build - cargo doc --no-deps $(CARGO_FLAGS) diff --git a/README.md b/README.md index 4e396cb91..b092a1723 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,6 @@ -# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -Actix web is a simple, pragmatic and extremely fast web framework for Rust. - -* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/docs/http2/) protocols -* Streaming and pipelining -* Keep-alive and slow requests handling -* Client/server [WebSockets](https://actix.rs/docs/websockets/) support -* Transparent content compression/decompression (br, gzip, deflate) -* Configurable [request routing](https://actix.rs/docs/url-dispatch/) -* Graceful server shutdown -* Multipart streams -* Static assets -* SSL support with OpenSSL or `native-tls` -* Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/)) -* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) -* Built on top of [Actix actor framework](https://github.com/actix/actix) +Actix http ## Documentation & community resources @@ -44,30 +30,6 @@ fn main() { } ``` -### More examples - -* [Basics](https://github.com/actix/examples/tree/master/basics/) -* [Stateful](https://github.com/actix/examples/tree/master/state/) -* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/) -* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/) -* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/) -* [Tera](https://github.com/actix/examples/tree/master/template_tera/) / - [Askama](https://github.com/actix/examples/tree/master/template_askama/) templates -* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/) -* [r2d2](https://github.com/actix/examples/tree/master/r2d2/) -* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/) -* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/) -* [Json](https://github.com/actix/examples/tree/master/json/) - -You may consider checking out -[this directory](https://github.com/actix/examples/tree/master/) for more examples. - -## Benchmarks - -* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext) - -* Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks). - ## License This project is licensed under either of @@ -80,5 +42,5 @@ at your option. ## Code of Conduct Contribution to the actix-web crate is organized under the terms of the -Contributor Covenant, the maintainer of actix-web, @fafhrd91, promises to +Contributor Covenant, the maintainer of actix-net, @fafhrd91, promises to intervene to uphold that code of conduct. diff --git a/build.rs b/build.rs deleted file mode 100644 index c8457944c..000000000 --- a/build.rs +++ /dev/null @@ -1,16 +0,0 @@ -extern crate version_check; - -fn main() { - match version_check::is_min_version("1.26.0") { - Some((true, _)) => println!("cargo:rustc-cfg=actix_impl_trait"), - _ => (), - }; - match version_check::is_nightly() { - Some(true) => { - println!("cargo:rustc-cfg=actix_nightly"); - println!("cargo:rustc-cfg=actix_impl_trait"); - } - Some(false) => (), - None => (), - }; -} diff --git a/src/application.rs b/src/application.rs deleted file mode 100644 index d8a6cbe7b..000000000 --- a/src/application.rs +++ /dev/null @@ -1,879 +0,0 @@ -use std::rc::Rc; - -use handler::{AsyncResult, FromRequest, Handler, Responder, WrapHandler}; -use header::ContentEncoding; -use http::Method; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::Middleware; -use pipeline::{Pipeline, PipelineHandler}; -use pred::Predicate; -use resource::Resource; -use router::{ResourceDef, Router}; -use scope::Scope; -use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request}; -use with::WithFactory; - -/// Application -pub struct HttpApplication { - state: Rc, - prefix: String, - prefix_len: usize, - inner: Rc>, - filters: Option>>>, - middlewares: Rc>>>, -} - -#[doc(hidden)] -pub struct Inner { - router: Router, - encoding: ContentEncoding, -} - -impl PipelineHandler for Inner { - #[inline] - fn encoding(&self) -> ContentEncoding { - self.encoding - } - - fn handle(&self, req: &HttpRequest) -> AsyncResult { - self.router.handle(req) - } -} - -impl HttpApplication { - #[cfg(test)] - pub(crate) fn run(&self, req: Request) -> AsyncResult { - let info = self - .inner - .router - .recognize(&req, &self.state, self.prefix_len); - let req = HttpRequest::new(req, Rc::clone(&self.state), info); - - self.inner.handle(&req) - } -} - -impl HttpHandler for HttpApplication { - type Task = Pipeline>; - - fn handle(&self, msg: Request) -> Result>, Request> { - let m = { - if self.prefix_len == 0 { - true - } else { - let path = msg.path(); - path.starts_with(&self.prefix) - && (path.len() == self.prefix_len - || path.split_at(self.prefix_len).1.starts_with('/')) - } - }; - if m { - if let Some(ref filters) = self.filters { - for filter in filters { - if !filter.check(&msg, &self.state) { - return Err(msg); - } - } - } - - let info = self - .inner - .router - .recognize(&msg, &self.state, self.prefix_len); - - let inner = Rc::clone(&self.inner); - let req = HttpRequest::new(msg, Rc::clone(&self.state), info); - Ok(Pipeline::new(req, Rc::clone(&self.middlewares), inner)) - } else { - Err(msg) - } - } -} - -struct ApplicationParts { - state: S, - prefix: String, - router: Router, - encoding: ContentEncoding, - middlewares: Vec>>, - filters: Vec>>, -} - -/// Structure that follows the builder pattern for building application -/// instances. -pub struct App { - parts: Option>, -} - -impl App<()> { - /// Create application with empty state. Application can - /// be configured with a builder-like pattern. - pub fn new() -> App<()> { - App::with_state(()) - } -} - -impl Default for App<()> { - fn default() -> Self { - App::new() - } -} - -impl App -where - S: 'static, -{ - /// Create application with specified state. Application can be - /// configured with a builder-like pattern. - /// - /// State is shared with all resources within same application and - /// could be accessed with `HttpRequest::state()` method. - /// - /// **Note**: http server accepts an application factory rather than - /// an application instance. Http server constructs an application - /// instance for each thread, thus application state must be constructed - /// multiple times. If you want to share state between different - /// threads, a shared object should be used, e.g. `Arc`. Application - /// state does not need to be `Send` or `Sync`. - pub fn with_state(state: S) -> App { - App { - parts: Some(ApplicationParts { - state, - prefix: "".to_owned(), - router: Router::new(ResourceDef::prefix("")), - middlewares: Vec::new(), - filters: Vec::new(), - encoding: ContentEncoding::Auto, - }), - } - } - - /// Get reference to the application state - pub fn state(&self) -> &S { - let parts = self.parts.as_ref().expect("Use after finish"); - &parts.state - } - - /// Set application prefix. - /// - /// Only requests that match the application's prefix get - /// processed by this application. - /// - /// The application prefix always contains a leading slash (`/`). - /// If the supplied prefix does not contain leading slash, it is - /// inserted. - /// - /// Prefix should consist of valid path segments. i.e for an - /// application with the prefix `/app` any request with the paths - /// `/app`, `/app/` or `/app/test` would match, but the path - /// `/application` would not. - /// - /// In the following example only requests with an `/app/` path - /// prefix get handled. Requests with path `/app/test/` would be - /// handled, while requests with the paths `/application` or - /// `/other/...` would return `NOT FOUND`. It is also possible to - /// handle `/app` path, to do this you can register resource for - /// empty string `""` - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new() - /// .prefix("/app") - /// .resource("", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app` path - /// .resource("/", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app/` path - /// .resource("/test", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// }) - /// .finish(); - /// } - /// ``` - pub fn prefix>(mut self, prefix: P) -> App { - { - let parts = self.parts.as_mut().expect("Use after finish"); - let mut prefix = prefix.into(); - if !prefix.starts_with('/') { - prefix.insert(0, '/') - } - parts.router.set_prefix(&prefix); - parts.prefix = prefix; - } - self - } - - /// Add match predicate to application. - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn main() { - /// App::new() - /// .filter(pred::Host("www.rust-lang.org")) - /// .resource("/path", |r| r.f(|_| HttpResponse::Ok())) - /// # .finish(); - /// # } - /// ``` - pub fn filter + 'static>(mut self, p: T) -> App { - { - let parts = self.parts.as_mut().expect("Use after finish"); - parts.filters.push(Box::new(p)); - } - self - } - - /// Configure route for a specific path. - /// - /// This is a simplified version of the `App::resource()` method. - /// Handler functions need to accept one request extractor - /// argument. - /// - /// This method could be called multiple times, in that case - /// multiple routes would be registered for same resource path. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new() - /// .route("/test", http::Method::GET, |_: HttpRequest| { - /// HttpResponse::Ok() - /// }) - /// .route("/test", http::Method::POST, |_: HttpRequest| { - /// HttpResponse::MethodNotAllowed() - /// }); - /// } - /// ``` - pub fn route(mut self, path: &str, method: Method, f: F) -> App - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - { - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_route(path, method, f); - - self - } - - /// Configure scope for common root path. - /// - /// Scopes collect multiple paths under a common path prefix. - /// Scope path can contain variable path segments as resources. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().scope("/{project_id}", |scope| { - /// scope - /// .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - /// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) - /// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) - /// }); - /// } - /// ``` - /// - /// In the above example, three routes get added: - /// * /{project_id}/path1 - /// * /{project_id}/path2 - /// * /{project_id}/path3 - /// - pub fn scope(mut self, path: &str, f: F) -> App - where - F: FnOnce(Scope) -> Scope, - { - let scope = f(Scope::new(path)); - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_scope(scope); - self - } - - /// Configure resource for a specific path. - /// - /// Resources may have variable path segments. For example, a - /// resource with the path `/a/{name}/c` would match all incoming - /// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. - /// - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. This is done by - /// looking up the identifier in the `Params` object returned by - /// `HttpRequest.match_info()` method. - /// - /// By default, each segment matches the regular expression `[^{}/]+`. - /// - /// You can also specify a custom regex in the form `{identifier:regex}`: - /// - /// For instance, to route `GET`-requests on any route matching - /// `/users/{userid}/{friend}` and store `userid` and `friend` in - /// the exposed `Params` object: - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().resource("/users/{userid}/{friend}", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// }); - /// } - /// ``` - pub fn resource(mut self, path: &str, f: F) -> App - where - F: FnOnce(&mut Resource) -> R + 'static, - { - { - let parts = self.parts.as_mut().expect("Use after finish"); - - // create resource - let mut resource = Resource::new(ResourceDef::new(path)); - - // configure - f(&mut resource); - - parts.router.register_resource(resource); - } - self - } - - /// Configure resource for a specific path. - #[doc(hidden)] - pub fn register_resource(&mut self, resource: Resource) { - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_resource(resource); - } - - /// Default resource to be used if no matching route could be found. - pub fn default_resource(mut self, f: F) -> App - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // create and configure default resource - let mut resource = Resource::new(ResourceDef::new("")); - f(&mut resource); - - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_default_resource(resource.into()); - - self - } - - /// Set default content encoding. `ContentEncoding::Auto` is set by default. - pub fn default_encoding(mut self, encoding: ContentEncoding) -> App { - { - let parts = self.parts.as_mut().expect("Use after finish"); - parts.encoding = encoding; - } - self - } - - /// Register an external resource. - /// - /// External resources are useful for URL generation purposes only - /// and are never considered for matching at request time. Calls to - /// `HttpRequest::url_for()` will work as expected. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{App, HttpRequest, HttpResponse, Result}; - /// - /// fn index(req: &HttpRequest) -> Result { - /// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; - /// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); - /// Ok(HttpResponse::Ok().into()) - /// } - /// - /// fn main() { - /// let app = App::new() - /// .resource("/index.html", |r| r.get().f(index)) - /// .external_resource("youtube", "https://youtube.com/watch/{video_id}") - /// .finish(); - /// } - /// ``` - pub fn external_resource(mut self, name: T, url: U) -> App - where - T: AsRef, - U: AsRef, - { - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_external(name.as_ref(), ResourceDef::external(url.as_ref())); - self - } - - /// Configure handler for specific path prefix. - /// - /// A path prefix consists of valid path segments, i.e for the - /// prefix `/app` any request with the paths `/app`, `/app/` or - /// `/app/test` would match, but the path `/application` would - /// not. - /// - /// Path tail is available as `tail` parameter in request's match_dict. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().handler("/app", |req: &HttpRequest| match *req.method() { - /// http::Method::GET => HttpResponse::Ok(), - /// http::Method::POST => HttpResponse::MethodNotAllowed(), - /// _ => HttpResponse::NotFound(), - /// }); - /// } - /// ``` - pub fn handler>(mut self, path: &str, handler: H) -> App { - { - let mut path = path.trim().trim_right_matches('/').to_owned(); - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/'); - }; - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_handler(&path, Box::new(WrapHandler::new(handler)), None); - } - self - } - - /// Register a middleware. - pub fn middleware>(mut self, mw: M) -> App { - self.parts - .as_mut() - .expect("Use after finish") - .middlewares - .push(Box::new(mw)); - self - } - - /// Run external configuration as part of the application building - /// process - /// - /// This function is useful for moving parts of configuration to a - /// different module or event library. For example we can move - /// some of the resources' configuration to different module. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{fs, middleware, App, HttpResponse}; - /// - /// // this function could be located in different module - /// fn config(app: App) -> App { - /// app.resource("/test", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// }) - /// } - /// - /// fn main() { - /// let app = App::new() - /// .middleware(middleware::Logger::default()) - /// .configure(config) // <- register resources - /// .handler("/static", fs::StaticFiles::new(".").unwrap()); - /// } - /// ``` - pub fn configure(self, cfg: F) -> App - where - F: Fn(App) -> App, - { - cfg(self) - } - - /// Finish application configuration and create `HttpHandler` object. - pub fn finish(&mut self) -> HttpApplication { - let mut parts = self.parts.take().expect("Use after finish"); - let prefix = parts.prefix.trim().trim_right_matches('/'); - parts.router.finish(); - - let inner = Rc::new(Inner { - router: parts.router, - encoding: parts.encoding, - }); - let filters = if parts.filters.is_empty() { - None - } else { - Some(parts.filters) - }; - - HttpApplication { - inner, - filters, - state: Rc::new(parts.state), - middlewares: Rc::new(parts.middlewares), - prefix: prefix.to_owned(), - prefix_len: prefix.len(), - } - } - - /// Convenience method for creating `Box` instances. - /// - /// This method is useful if you need to register multiple - /// application instances with different state. - /// - /// ```rust - /// # use std::thread; - /// # extern crate actix_web; - /// use actix_web::{server, App, HttpResponse}; - /// - /// struct State1; - /// - /// struct State2; - /// - /// fn main() { - /// # thread::spawn(|| { - /// server::new(|| { - /// vec![ - /// App::with_state(State1) - /// .prefix("/app1") - /// .resource("/", |r| r.f(|r| HttpResponse::Ok())) - /// .boxed(), - /// App::with_state(State2) - /// .prefix("/app2") - /// .resource("/", |r| r.f(|r| HttpResponse::Ok())) - /// .boxed(), - /// ] - /// }).bind("127.0.0.1:8080") - /// .unwrap() - /// .run() - /// # }); - /// } - /// ``` - pub fn boxed(mut self) -> Box>> { - Box::new(BoxedApplication { app: self.finish() }) - } -} - -struct BoxedApplication { - app: HttpApplication, -} - -impl HttpHandler for BoxedApplication { - type Task = Box; - - fn handle(&self, req: Request) -> Result { - self.app.handle(req).map(|t| { - let task: Self::Task = Box::new(t); - task - }) - } -} - -impl IntoHttpHandler for App { - type Handler = HttpApplication; - - fn into_handler(mut self) -> HttpApplication { - self.finish() - } -} - -impl<'a, S: 'static> IntoHttpHandler for &'a mut App { - type Handler = HttpApplication; - - fn into_handler(self) -> HttpApplication { - self.finish() - } -} - -#[doc(hidden)] -impl Iterator for App { - type Item = HttpApplication; - - fn next(&mut self) -> Option { - if self.parts.is_some() { - Some(self.finish()) - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use body::{Binary, Body}; - use http::StatusCode; - use httprequest::HttpRequest; - use httpresponse::HttpResponse; - use pred; - use test::{TestRequest, TestServer}; - - #[test] - fn test_default_resource() { - let app = App::new() - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .finish(); - - let req = TestRequest::with_uri("/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let app = App::new() - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) - .finish(); - let req = TestRequest::with_uri("/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); - } - - #[test] - fn test_unhandled_prefix() { - let app = App::new() - .prefix("/test") - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .finish(); - let ctx = TestRequest::default().request(); - assert!(app.handle(ctx).is_err()); - } - - #[test] - fn test_state() { - let app = App::with_state(10) - .resource("/", |r| r.f(|_| HttpResponse::Ok())) - .finish(); - let req = TestRequest::with_state(10).request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_prefix() { - let app = App::new() - .prefix("/test") - .resource("/blah", |r| r.f(|_| HttpResponse::Ok())) - .finish(); - let req = TestRequest::with_uri("/test").request(); - let resp = app.handle(req); - assert!(resp.is_ok()); - - let req = TestRequest::with_uri("/test/").request(); - let resp = app.handle(req); - assert!(resp.is_ok()); - - let req = TestRequest::with_uri("/test/blah").request(); - let resp = app.handle(req); - assert!(resp.is_ok()); - - let req = TestRequest::with_uri("/testing").request(); - let resp = app.handle(req); - assert!(resp.is_err()); - } - - #[test] - fn test_handler() { - let app = App::new() - .handler("/test", |_: &_| HttpResponse::Ok()) - .finish(); - - let req = TestRequest::with_uri("/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_handler2() { - let app = App::new() - .handler("test", |_: &_| HttpResponse::Ok()) - .finish(); - - let req = TestRequest::with_uri("/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_handler_with_prefix() { - let app = App::new() - .prefix("prefix") - .handler("/test", |_: &_| HttpResponse::Ok()) - .finish(); - - let req = TestRequest::with_uri("/prefix/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/prefix/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/prefix/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/prefix/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/prefix/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_route() { - let app = App::new() - .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) - .route("/test", Method::POST, |_: HttpRequest| { - HttpResponse::Created() - }).finish(); - - let req = TestRequest::with_uri("/test").method(Method::GET).request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - - let req = TestRequest::with_uri("/test") - .method(Method::HEAD) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_handler_prefix() { - let app = App::new() - .prefix("/app") - .handler("/test", |_: &_| HttpResponse::Ok()) - .finish(); - - let req = TestRequest::with_uri("/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_option_responder() { - let app = App::new() - .resource("/none", |r| r.f(|_| -> Option<&'static str> { None })) - .resource("/some", |r| r.f(|_| Some("some"))) - .finish(); - - let req = TestRequest::with_uri("/none").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/some").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - assert_eq!(resp.as_msg().body(), &Body::Binary(Binary::Slice(b"some"))); - } - - #[test] - fn test_filter() { - let mut srv = TestServer::with_factory(|| { - App::new() - .filter(pred::Get()) - .handler("/test", |_: &_| HttpResponse::Ok()) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let request = srv.post().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_prefix_root() { - let mut srv = TestServer::with_factory(|| { - App::new() - .prefix("/test") - .resource("/", |r| r.f(|_| HttpResponse::Ok())) - .resource("", |r| r.f(|_| HttpResponse::Created())) - }); - - let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::CREATED); - } - -} diff --git a/src/body.rs b/src/body.rs index a93db1e92..e689b704c 100644 --- a/src/body.rs +++ b/src/body.rs @@ -3,10 +3,7 @@ use futures::Stream; use std::sync::Arc; use std::{fmt, mem}; -use context::ActorHttpContext; use error::Error; -use handler::Responder; -use httprequest::HttpRequest; use httpresponse::HttpResponse; /// Type represent streaming body @@ -21,8 +18,8 @@ pub enum Body { /// Unspecified streaming response. Developer is responsible for setting /// right `Content-Length` or `Transfer-Encoding` headers. Streaming(BodyStream), - /// Special body type for actor response. - Actor(Box), + // /// Special body type for actor response. + // Actor(Box), } /// Represents various types of binary body. @@ -45,7 +42,7 @@ impl Body { #[inline] pub fn is_streaming(&self) -> bool { match *self { - Body::Streaming(_) | Body::Actor(_) => true, + Body::Streaming(_) => true, _ => false, } } @@ -94,7 +91,7 @@ impl PartialEq for Body { Body::Binary(ref b2) => b == b2, _ => false, }, - Body::Streaming(_) | Body::Actor(_) => false, + Body::Streaming(_) => false, } } } @@ -105,7 +102,6 @@ impl fmt::Debug for Body { Body::Empty => write!(f, "Body::Empty"), Body::Binary(ref b) => write!(f, "Body::Binary({:?})", b), Body::Streaming(_) => write!(f, "Body::Streaming(_)"), - Body::Actor(_) => write!(f, "Body::Actor(_)"), } } } @@ -119,12 +115,6 @@ where } } -impl From> for Body { - fn from(ctx: Box) -> Body { - Body::Actor(ctx) - } -} - impl Binary { #[inline] /// Returns `true` if body is empty @@ -254,17 +244,6 @@ impl AsRef<[u8]> for Binary { } } -impl Responder for Binary { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(HttpResponse::build_from(req) - .content_type("application/octet-stream") - .body(self)) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/client/connector.rs b/src/client/connector.rs deleted file mode 100644 index 07c7b646d..000000000 --- a/src/client/connector.rs +++ /dev/null @@ -1,1344 +0,0 @@ -use std::collections::{HashMap, VecDeque}; -use std::net::Shutdown; -use std::time::{Duration, Instant}; -use std::{fmt, io, mem, time}; - -use actix::resolver::{Connect as ResolveConnect, Resolver, ResolverError}; -use actix::{ - fut, Actor, ActorFuture, ActorResponse, Addr, AsyncContext, Context, - ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised, - SystemService, WrapFuture, -}; - -use futures::sync::{mpsc, oneshot}; -use futures::{Async, Future, Poll}; -use http::{Error as HttpError, HttpTryFrom, Uri}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_timer::Delay; - -#[cfg(any(feature = "alpn", feature = "ssl"))] -use { - openssl::ssl::{Error as SslError, SslConnector, SslMethod}, - tokio_openssl::SslConnectorExt, -}; - -#[cfg(all( - feature = "tls", - not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")) -))] -use { - native_tls::{Error as SslError, TlsConnector as NativeTlsConnector}, - tokio_tls::TlsConnector as SslConnector, -}; - -#[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls", feature = "ssl")) -))] -use { - rustls::ClientConfig, std::io::Error as SslError, std::sync::Arc, - tokio_rustls::ClientConfigExt, webpki::DNSNameRef, webpki_roots, -}; - -#[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls", feature = "ssl")) -))] -type SslConnector = Arc; - -#[cfg(not(any( - feature = "alpn", - feature = "ssl", - feature = "tls", - feature = "rust-tls" -)))] -type SslConnector = (); - -use server::IoStream; -use {HAS_OPENSSL, HAS_RUSTLS, HAS_TLS}; - -/// Client connector usage stats -#[derive(Default, Message)] -pub struct ClientConnectorStats { - /// Number of waited-on connections - pub waits: usize, - /// Size of the wait queue - pub wait_queue: usize, - /// Number of reused connections - pub reused: usize, - /// Number of opened connections - pub opened: usize, - /// Number of closed connections - pub closed: usize, - /// Number of connections with errors - pub errors: usize, - /// Number of connection timeouts - pub timeouts: usize, -} - -#[derive(Debug)] -/// `Connect` type represents a message that can be sent to -/// `ClientConnector` with a connection request. -pub struct Connect { - pub(crate) uri: Uri, - pub(crate) wait_timeout: Duration, - pub(crate) conn_timeout: Duration, -} - -impl Connect { - /// Create `Connect` message for specified `Uri` - pub fn new(uri: U) -> Result - where - Uri: HttpTryFrom, - { - Ok(Connect { - uri: Uri::try_from(uri).map_err(|e| e.into())?, - wait_timeout: Duration::from_secs(5), - conn_timeout: Duration::from_secs(1), - }) - } - - /// Connection timeout, i.e. max time to connect to remote host. - /// Set to 1 second by default. - pub fn conn_timeout(mut self, timeout: Duration) -> Self { - self.conn_timeout = timeout; - self - } - - /// If connection pool limits are enabled, wait time indicates - /// max time to wait for a connection to become available. - /// Set to 5 seconds by default. - pub fn wait_timeout(mut self, timeout: Duration) -> Self { - self.wait_timeout = timeout; - self - } -} - -impl Message for Connect { - type Result = Result; -} - -/// Pause connection process for `ClientConnector` -/// -/// All connect requests enter wait state during connector pause. -pub struct Pause { - time: Option, -} - -impl Pause { - /// Create message with pause duration parameter - pub fn new(time: Duration) -> Pause { - Pause { time: Some(time) } - } -} - -impl Default for Pause { - fn default() -> Pause { - Pause { time: None } - } -} - -impl Message for Pause { - type Result = (); -} - -/// Resume connection process for `ClientConnector` -#[derive(Message)] -pub struct Resume; - -/// A set of errors that can occur while connecting to an HTTP host -#[derive(Fail, Debug)] -pub enum ClientConnectorError { - /// Invalid URL - #[fail(display = "Invalid URL")] - InvalidUrl, - - /// SSL feature is not enabled - #[fail(display = "SSL is not supported")] - SslIsNotSupported, - - /// SSL error - #[cfg(any( - feature = "tls", - feature = "alpn", - feature = "ssl", - feature = "rust-tls", - ))] - #[fail(display = "{}", _0)] - SslError(#[cause] SslError), - - /// Resolver error - #[fail(display = "{}", _0)] - Resolver(#[cause] ResolverError), - - /// Connection took too long - #[fail(display = "Timeout while establishing connection")] - Timeout, - - /// Connector has been disconnected - #[fail(display = "Internal error: connector has been disconnected")] - Disconnected, - - /// Connection IO error - #[fail(display = "{}", _0)] - IoError(#[cause] io::Error), -} - -impl From for ClientConnectorError { - fn from(err: ResolverError) -> ClientConnectorError { - match err { - ResolverError::Timeout => ClientConnectorError::Timeout, - _ => ClientConnectorError::Resolver(err), - } - } -} - -struct Waiter { - tx: oneshot::Sender>, - wait: Instant, - conn_timeout: Duration, -} - -enum Paused { - No, - Yes, - Timeout(Instant, Delay), -} - -impl Paused { - fn is_paused(&self) -> bool { - match *self { - Paused::No => false, - _ => true, - } - } -} - -/// `ClientConnector` type is responsible for transport layer of a -/// client connection. -pub struct ClientConnector { - #[allow(dead_code)] - connector: SslConnector, - - stats: ClientConnectorStats, - subscriber: Option>, - - acq_tx: mpsc::UnboundedSender, - acq_rx: Option>, - - resolver: Option>, - conn_lifetime: Duration, - conn_keep_alive: Duration, - limit: usize, - limit_per_host: usize, - acquired: usize, - acquired_per_host: HashMap, - available: HashMap>, - to_close: Vec, - waiters: Option>>, - wait_timeout: Option<(Instant, Delay)>, - paused: Paused, -} - -impl Actor for ClientConnector { - type Context = Context; - - fn started(&mut self, ctx: &mut Self::Context) { - if self.resolver.is_none() { - self.resolver = Some(Resolver::from_registry()) - } - self.collect_periodic(ctx); - ctx.add_stream(self.acq_rx.take().unwrap()); - ctx.spawn(Maintenance); - } -} - -impl Supervised for ClientConnector {} - -impl SystemService for ClientConnector {} - -impl Default for ClientConnector { - fn default() -> ClientConnector { - let connector = { - #[cfg(all(any(feature = "alpn", feature = "ssl")))] - { - SslConnector::builder(SslMethod::tls()).unwrap().build() - } - - #[cfg(all( - feature = "tls", - not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")) - ))] - { - NativeTlsConnector::builder().build().unwrap().into() - } - - #[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls", feature = "ssl")) - ))] - { - let mut config = ClientConfig::new(); - config - .root_store - .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - Arc::new(config) - } - - #[cfg_attr(rustfmt, rustfmt_skip)] - #[cfg(not(any( - feature = "alpn", feature = "ssl", feature = "tls", feature = "rust-tls")))] - { - () - } - }; - - #[cfg_attr(feature = "cargo-clippy", allow(clippy::let_unit_value))] - ClientConnector::with_connector_impl(connector) - } -} - -impl ClientConnector { - #[cfg(any(feature = "alpn", feature = "ssl"))] - /// Create `ClientConnector` actor with custom `SslConnector` instance. - /// - /// By default `ClientConnector` uses very a simple SSL configuration. - /// With `with_connector` method it is possible to use a custom - /// `SslConnector` object. - /// - /// ```rust,ignore - /// # #![cfg(feature="alpn")] - /// # extern crate actix_web; - /// # extern crate futures; - /// # use futures::{future, Future}; - /// # use std::io::Write; - /// # use actix_web::actix::Actor; - /// extern crate openssl; - /// use actix_web::{actix, client::ClientConnector, client::Connect}; - /// - /// use openssl::ssl::{SslConnector, SslMethod}; - /// - /// fn main() { - /// actix::run(|| { - /// // Start `ClientConnector` with custom `SslConnector` - /// let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); - /// let conn = ClientConnector::with_connector(ssl_conn).start(); - /// - /// conn.send( - /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host - /// .map_err(|_| ()) - /// .and_then(|res| { - /// if let Ok(mut stream) = res { - /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); - /// } - /// # actix::System::current().stop(); - /// Ok(()) - /// }) - /// }); - /// } - /// ``` - pub fn with_connector(connector: SslConnector) -> ClientConnector { - // keep level of indirection for docstrings matching featureflags - Self::with_connector_impl(connector) - } - - #[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "ssl", feature = "tls")) - ))] - /// Create `ClientConnector` actor with custom `SslConnector` instance. - /// - /// By default `ClientConnector` uses very a simple SSL configuration. - /// With `with_connector` method it is possible to use a custom - /// `SslConnector` object. - /// - /// ```rust - /// # #![cfg(feature = "rust-tls")] - /// # extern crate actix_web; - /// # extern crate futures; - /// # use futures::{future, Future}; - /// # use std::io::Write; - /// # use actix_web::actix::Actor; - /// extern crate rustls; - /// extern crate webpki_roots; - /// use actix_web::{actix, client::ClientConnector, client::Connect}; - /// - /// use rustls::ClientConfig; - /// use std::sync::Arc; - /// - /// fn main() { - /// actix::run(|| { - /// // Start `ClientConnector` with custom `ClientConfig` - /// let mut config = ClientConfig::new(); - /// config - /// .root_store - /// .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - /// let conn = ClientConnector::with_connector(Arc::new(config)).start(); - /// - /// conn.send( - /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host - /// .map_err(|_| ()) - /// .and_then(|res| { - /// if let Ok(mut stream) = res { - /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); - /// } - /// # actix::System::current().stop(); - /// Ok(()) - /// }) - /// }); - /// } - /// ``` - pub fn with_connector(connector: ClientConfig) -> ClientConnector { - // keep level of indirection for docstrings matching featureflags - Self::with_connector_impl(Arc::new(connector)) - } - - #[cfg(all( - feature = "tls", - not(any(feature = "ssl", feature = "alpn", feature = "rust-tls")) - ))] - /// Create `ClientConnector` actor with custom `SslConnector` instance. - /// - /// By default `ClientConnector` uses very a simple SSL configuration. - /// With `with_connector` method it is possible to use a custom - /// `SslConnector` object. - /// - /// ```rust - /// # #![cfg(feature = "tls")] - /// # extern crate actix_web; - /// # extern crate futures; - /// # use futures::{future, Future}; - /// # use std::io::Write; - /// # use actix_web::actix::Actor; - /// extern crate native_tls; - /// extern crate webpki_roots; - /// use native_tls::TlsConnector; - /// use actix_web::{actix, client::ClientConnector, client::Connect}; - /// - /// fn main() { - /// actix::run(|| { - /// let connector = TlsConnector::new().unwrap(); - /// let conn = ClientConnector::with_connector(connector.into()).start(); - /// - /// conn.send( - /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host - /// .map_err(|_| ()) - /// .and_then(|res| { - /// if let Ok(mut stream) = res { - /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); - /// } - /// # actix::System::current().stop(); - /// Ok(()) - /// }) - /// }); - /// } - /// ``` - pub fn with_connector(connector: SslConnector) -> ClientConnector { - // keep level of indirection for docstrings matching featureflags - Self::with_connector_impl(connector) - } - - #[inline] - fn with_connector_impl(connector: SslConnector) -> ClientConnector { - let (tx, rx) = mpsc::unbounded(); - - ClientConnector { - connector, - stats: ClientConnectorStats::default(), - subscriber: None, - acq_tx: tx, - acq_rx: Some(rx), - resolver: None, - conn_lifetime: Duration::from_secs(75), - conn_keep_alive: Duration::from_secs(15), - limit: 100, - limit_per_host: 0, - acquired: 0, - acquired_per_host: HashMap::new(), - available: HashMap::new(), - to_close: Vec::new(), - waiters: Some(HashMap::new()), - wait_timeout: None, - paused: Paused::No, - } - } - - /// Set total number of simultaneous connections. - /// - /// If limit is 0, the connector has no limit. - /// The default limit size is 100. - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - /// Set total number of simultaneous connections to the same endpoint. - /// - /// Endpoints are the same if they have equal (host, port, ssl) triplets. - /// If limit is 0, the connector has no limit. The default limit size is 0. - pub fn limit_per_host(mut self, limit: usize) -> Self { - self.limit_per_host = limit; - self - } - - /// Set keep-alive period for opened connection. - /// - /// Keep-alive period is the period between connection usage. If - /// the delay between repeated usages of the same connection - /// exceeds this period, the connection is closed. - /// Default keep-alive period is 15 seconds. - pub fn conn_keep_alive(mut self, dur: Duration) -> Self { - self.conn_keep_alive = dur; - self - } - - /// Set max lifetime period for connection. - /// - /// Connection lifetime is max lifetime of any opened connection - /// until it is closed regardless of keep-alive period. - /// Default lifetime period is 75 seconds. - pub fn conn_lifetime(mut self, dur: Duration) -> Self { - self.conn_lifetime = dur; - self - } - - /// Subscribe for connector stats. Only one subscriber is supported. - pub fn stats(mut self, subs: Recipient) -> Self { - self.subscriber = Some(subs); - self - } - - /// Use custom resolver actor - pub fn resolver(mut self, addr: Addr) -> Self { - self.resolver = Some(addr); - self - } - - fn acquire(&mut self, key: &Key) -> Acquire { - // check limits - if self.limit > 0 { - if self.acquired >= self.limit { - return Acquire::NotAvailable; - } - if self.limit_per_host > 0 { - if let Some(per_host) = self.acquired_per_host.get(key) { - if *per_host >= self.limit_per_host { - return Acquire::NotAvailable; - } - } - } - } else if self.limit_per_host > 0 { - if let Some(per_host) = self.acquired_per_host.get(key) { - if *per_host >= self.limit_per_host { - return Acquire::NotAvailable; - } - } - } - - self.reserve(key); - - // check if open connection is available - // cleanup stale connections at the same time - if let Some(ref mut connections) = self.available.get_mut(key) { - let now = Instant::now(); - while let Some(conn) = connections.pop_back() { - // check if it still usable - if (now - conn.0) > self.conn_keep_alive - || (now - conn.1.ts) > self.conn_lifetime - { - self.stats.closed += 1; - self.to_close.push(conn.1); - } else { - let mut conn = conn.1; - let mut buf = [0; 2]; - match conn.stream().read(&mut buf) { - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), - Ok(n) if n > 0 => { - self.stats.closed += 1; - self.to_close.push(conn); - continue; - } - Ok(_) | Err(_) => continue, - } - return Acquire::Acquired(conn); - } - } - } - Acquire::Available - } - - fn reserve(&mut self, key: &Key) { - self.acquired += 1; - let per_host = if let Some(per_host) = self.acquired_per_host.get(key) { - *per_host - } else { - 0 - }; - self.acquired_per_host.insert(key.clone(), per_host + 1); - } - - fn release_key(&mut self, key: &Key) { - if self.acquired > 0 { - self.acquired -= 1; - } - let per_host = if let Some(per_host) = self.acquired_per_host.get(key) { - *per_host - } else { - return; - }; - if per_host > 1 { - self.acquired_per_host.insert(key.clone(), per_host - 1); - } else { - self.acquired_per_host.remove(key); - } - } - - fn collect_periodic(&mut self, ctx: &mut Context) { - // check connections for shutdown - let mut idx = 0; - while idx < self.to_close.len() { - match AsyncWrite::shutdown(&mut self.to_close[idx]) { - Ok(Async::NotReady) => idx += 1, - _ => { - self.to_close.swap_remove(idx); - } - } - } - - // re-schedule next collect period - ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx)); - - // send stats - let mut stats = mem::replace(&mut self.stats, ClientConnectorStats::default()); - if let Some(ref mut subscr) = self.subscriber { - if let Some(ref waiters) = self.waiters { - for w in waiters.values() { - stats.wait_queue += w.len(); - } - } - let _ = subscr.do_send(stats); - } - } - - // TODO: waiters should be sorted by deadline. maybe timewheel? - fn collect_waiters(&mut self) { - let now = Instant::now(); - let mut next = None; - - for waiters in self.waiters.as_mut().unwrap().values_mut() { - let mut idx = 0; - while idx < waiters.len() { - let wait = waiters[idx].wait; - if wait <= now { - self.stats.timeouts += 1; - let waiter = waiters.swap_remove_back(idx).unwrap(); - let _ = waiter.tx.send(Err(ClientConnectorError::Timeout)); - } else { - if let Some(n) = next { - if wait < n { - next = Some(wait); - } - } else { - next = Some(wait); - } - idx += 1; - } - } - } - - if next.is_some() { - self.install_wait_timeout(next.unwrap()); - } - } - - fn install_wait_timeout(&mut self, time: Instant) { - if let Some(ref mut wait) = self.wait_timeout { - if wait.0 < time { - return; - } - } - - let mut timeout = Delay::new(time); - let _ = timeout.poll(); - self.wait_timeout = Some((time, timeout)); - } - - fn wait_for( - &mut self, key: Key, wait: Duration, conn_timeout: Duration, - ) -> oneshot::Receiver> { - // connection is not available, wait - let (tx, rx) = oneshot::channel(); - - let wait = Instant::now() + wait; - self.install_wait_timeout(wait); - - let waiter = Waiter { - tx, - wait, - conn_timeout, - }; - self.waiters - .as_mut() - .unwrap() - .entry(key) - .or_insert_with(VecDeque::new) - .push_back(waiter); - rx - } - - fn check_availibility(&mut self, ctx: &mut Context) { - // check waiters - let mut act_waiters = self.waiters.take().unwrap(); - - for (key, ref mut waiters) in &mut act_waiters { - while let Some(waiter) = waiters.pop_front() { - if waiter.tx.is_canceled() { - continue; - } - - match self.acquire(key) { - Acquire::Acquired(mut conn) => { - // use existing connection - self.stats.reused += 1; - conn.pool = - Some(AcquiredConn(key.clone(), Some(self.acq_tx.clone()))); - let _ = waiter.tx.send(Ok(conn)); - } - Acquire::NotAvailable => { - waiters.push_front(waiter); - break; - } - Acquire::Available => { - // create new connection - self.connect_waiter(&key, waiter, ctx); - } - } - } - } - - self.waiters = Some(act_waiters); - } - - fn connect_waiter(&mut self, key: &Key, waiter: Waiter, ctx: &mut Context) { - let key = key.clone(); - let conn = AcquiredConn(key.clone(), Some(self.acq_tx.clone())); - - let key2 = key.clone(); - fut::WrapFuture::::actfuture( - self.resolver.as_ref().unwrap().send( - ResolveConnect::host_and_port(&conn.0.host, conn.0.port) - .timeout(waiter.conn_timeout), - ), - ).map_err(move |_, act, _| { - act.release_key(&key2); - () - }).and_then(move |res, act, _| { - #[cfg(any(feature = "alpn", feature = "ssl"))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - fut::Either::A( - act.connector - .connect_async(&key.host, stream) - .into_actor(act) - .then(move |res, _, _| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - } - } - fut::ok(()) - }), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } - - #[cfg(all(feature = "tls", not(any(feature = "alpn", feature = "ssl"))))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - fut::Either::A( - act.connector - .connect(&conn.0.host, stream) - .into_actor(act) - .then(move |res, _, _| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - } - } - fut::ok(()) - }), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } - - #[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "ssl", feature = "tls")) - ))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - let host = DNSNameRef::try_from_ascii_str(&key.host).unwrap(); - fut::Either::A( - act.connector - .connect_async(host, stream) - .into_actor(act) - .then(move |res, _, _| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - } - } - fut::ok(()) - }), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } - - #[cfg(not(any( - feature = "alpn", - feature = "ssl", - feature = "tls", - feature = "rust-tls" - )))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::err(()) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - let _ = - waiter.tx.send(Err(ClientConnectorError::SslIsNotSupported)); - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - }; - fut::ok(()) - } - } - }).spawn(ctx); - } -} - -impl Handler for ClientConnector { - type Result = (); - - fn handle(&mut self, msg: Pause, _: &mut Self::Context) { - if let Some(time) = msg.time { - let when = Instant::now() + time; - let mut timeout = Delay::new(when); - let _ = timeout.poll(); - self.paused = Paused::Timeout(when, timeout); - } else { - self.paused = Paused::Yes; - } - } -} - -impl Handler for ClientConnector { - type Result = (); - - fn handle(&mut self, _: Resume, _: &mut Self::Context) { - self.paused = Paused::No; - } -} - -impl Handler for ClientConnector { - type Result = ActorResponse; - - fn handle(&mut self, msg: Connect, ctx: &mut Self::Context) -> Self::Result { - let uri = &msg.uri; - let wait_timeout = msg.wait_timeout; - let conn_timeout = msg.conn_timeout; - - // host name is required - if uri.host().is_none() { - return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)); - } - - // supported protocols - let proto = match uri.scheme_part() { - Some(scheme) => match Protocol::from(scheme.as_str()) { - Some(proto) => proto, - None => { - return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)) - } - }, - None => return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)), - }; - - // check ssl availability - if proto.is_secure() && !HAS_OPENSSL && !HAS_TLS && !HAS_RUSTLS { - return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported)); - } - - let host = uri.host().unwrap().to_owned(); - let port = uri.port().unwrap_or_else(|| proto.port()); - let key = Key { - host, - port, - ssl: proto.is_secure(), - }; - - // check pause state - if self.paused.is_paused() { - let rx = self.wait_for(key.clone(), wait_timeout, conn_timeout); - self.stats.waits += 1; - return ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(move |res, act, ctx| match res { - Ok(conn) => fut::ok(conn), - Err(err) => { - match err { - ClientConnectorError::Timeout => (), - _ => { - act.release_key(&key); - } - } - act.stats.errors += 1; - act.check_availibility(ctx); - fut::err(err) - } - }), - ); - } - - // do not re-use websockets connection - if !proto.is_http() { - let (tx, rx) = oneshot::channel(); - let wait = Instant::now() + wait_timeout; - let waiter = Waiter { - tx, - wait, - conn_timeout, - }; - self.connect_waiter(&key, waiter, ctx); - - return ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(move |res, act, ctx| match res { - Ok(conn) => fut::ok(conn), - Err(err) => { - act.stats.errors += 1; - act.release_key(&key); - act.check_availibility(ctx); - fut::err(err) - } - }), - ); - } - - // acquire connection - match self.acquire(&key) { - Acquire::Acquired(mut conn) => { - // use existing connection - conn.pool = Some(AcquiredConn(key, Some(self.acq_tx.clone()))); - self.stats.reused += 1; - ActorResponse::async(fut::ok(conn)) - } - Acquire::NotAvailable => { - // connection is not available, wait - let rx = self.wait_for(key.clone(), wait_timeout, conn_timeout); - self.stats.waits += 1; - - ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(move |res, act, ctx| match res { - Ok(conn) => fut::ok(conn), - Err(err) => { - match err { - ClientConnectorError::Timeout => (), - _ => { - act.release_key(&key); - } - } - act.stats.errors += 1; - act.check_availibility(ctx); - fut::err(err) - } - }), - ) - } - Acquire::Available => { - let (tx, rx) = oneshot::channel(); - let wait = Instant::now() + wait_timeout; - let waiter = Waiter { - tx, - wait, - conn_timeout, - }; - self.connect_waiter(&key, waiter, ctx); - - ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(move |res, act, ctx| match res { - Ok(conn) => fut::ok(conn), - Err(err) => { - act.stats.errors += 1; - act.release_key(&key); - act.check_availibility(ctx); - fut::err(err) - } - }), - ) - } - } - } -} - -impl StreamHandler for ClientConnector { - fn handle(&mut self, msg: AcquiredConnOperation, ctx: &mut Context) { - match msg { - AcquiredConnOperation::Close(conn) => { - self.release_key(&conn.key); - self.to_close.push(conn); - self.stats.closed += 1; - } - AcquiredConnOperation::Release(conn) => { - self.release_key(&conn.key); - if (Instant::now() - conn.ts) < self.conn_lifetime { - self.available - .entry(conn.key.clone()) - .or_insert_with(VecDeque::new) - .push_back(Conn(Instant::now(), conn)); - } else { - self.to_close.push(conn); - self.stats.closed += 1; - } - } - AcquiredConnOperation::ReleaseKey(key) => { - // closed - self.stats.closed += 1; - self.release_key(&key); - } - } - - self.check_availibility(ctx); - } -} - -struct Maintenance; - -impl fut::ActorFuture for Maintenance { - type Item = (); - type Error = (); - type Actor = ClientConnector; - - fn poll( - &mut self, act: &mut ClientConnector, ctx: &mut Context, - ) -> Poll { - // check pause duration - if let Paused::Timeout(inst, _) = act.paused { - if inst <= Instant::now() { - act.paused = Paused::No; - } - } - - // collect wait timers - act.collect_waiters(); - - // check waiters - act.check_availibility(ctx); - - Ok(Async::NotReady) - } -} - -#[derive(PartialEq, Hash, Debug, Clone, Copy)] -enum Protocol { - Http, - Https, - Ws, - Wss, -} - -impl Protocol { - fn from(s: &str) -> Option { - match s { - "http" => Some(Protocol::Http), - "https" => Some(Protocol::Https), - "ws" => Some(Protocol::Ws), - "wss" => Some(Protocol::Wss), - _ => None, - } - } - - fn is_http(self) -> bool { - match self { - Protocol::Https | Protocol::Http => true, - _ => false, - } - } - - fn is_secure(self) -> bool { - match self { - Protocol::Https | Protocol::Wss => true, - _ => false, - } - } - - fn port(self) -> u16 { - match self { - Protocol::Http | Protocol::Ws => 80, - Protocol::Https | Protocol::Wss => 443, - } - } -} - -#[derive(Hash, Eq, PartialEq, Clone, Debug)] -struct Key { - host: String, - port: u16, - ssl: bool, -} - -impl Key { - fn empty() -> Key { - Key { - host: String::new(), - port: 0, - ssl: false, - } - } -} - -#[derive(Debug)] -struct Conn(Instant, Connection); - -enum Acquire { - Acquired(Connection), - Available, - NotAvailable, -} - -enum AcquiredConnOperation { - Close(Connection), - Release(Connection), - ReleaseKey(Key), -} - -struct AcquiredConn(Key, Option>); - -impl AcquiredConn { - fn close(&mut self, conn: Connection) { - if let Some(tx) = self.1.take() { - let _ = tx.unbounded_send(AcquiredConnOperation::Close(conn)); - } - } - fn release(&mut self, conn: Connection) { - if let Some(tx) = self.1.take() { - let _ = tx.unbounded_send(AcquiredConnOperation::Release(conn)); - } - } -} - -impl Drop for AcquiredConn { - fn drop(&mut self) { - if let Some(tx) = self.1.take() { - let _ = tx.unbounded_send(AcquiredConnOperation::ReleaseKey(self.0.clone())); - } - } -} - -/// HTTP client connection -pub struct Connection { - key: Key, - stream: Box, - pool: Option, - ts: Instant, -} - -impl fmt::Debug for Connection { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Connection {}:{}", self.key.host, self.key.port) - } -} - -impl Connection { - fn new(key: Key, pool: Option, stream: Box) -> Self { - Connection { - key, - stream, - pool, - ts: Instant::now(), - } - } - - /// Raw IO stream - pub fn stream(&mut self) -> &mut IoStream { - &mut *self.stream - } - - /// Create a new connection from an IO Stream - /// - /// The stream can be a `UnixStream` if the Unix-only "uds" feature is enabled. - /// - /// See also `ClientRequestBuilder::with_connection()`. - pub fn from_stream(io: T) -> Connection { - Connection::new(Key::empty(), None, Box::new(io)) - } - - /// Close connection - pub fn close(mut self) { - if let Some(mut pool) = self.pool.take() { - pool.close(self) - } - } - - /// Release this connection to the connection pool - pub fn release(mut self) { - if let Some(mut pool) = self.pool.take() { - pool.release(self) - } - } -} - -impl IoStream for Connection { - fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { - IoStream::shutdown(&mut *self.stream, how) - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - IoStream::set_nodelay(&mut *self.stream, nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - IoStream::set_linger(&mut *self.stream, dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - IoStream::set_keepalive(&mut *self.stream, dur) - } -} - -impl io::Read for Connection { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.stream.read(buf) - } -} - -impl AsyncRead for Connection {} - -impl io::Write for Connection { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.stream.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.stream.flush() - } -} - -impl AsyncWrite for Connection { - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.stream.shutdown() - } -} - -#[cfg(feature = "tls")] -use tokio_tls::TlsStream; - -#[cfg(feature = "tls")] -/// This is temp solution untile actix-net migration -impl IoStream for TlsStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = self.get_mut().shutdown(); - Ok(()) - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - self.get_mut().get_mut().set_nodelay(nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_linger(dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_keepalive(dur) - } -} diff --git a/src/client/mod.rs b/src/client/mod.rs deleted file mode 100644 index a0713fe32..000000000 --- a/src/client/mod.rs +++ /dev/null @@ -1,118 +0,0 @@ -//! Http client api -//! -//! ```rust -//! # extern crate actix_web; -//! # extern crate futures; -//! # extern crate tokio; -//! # use futures::Future; -//! # use std::process; -//! use actix_web::{actix, client}; -//! -//! fn main() { -//! actix::run( -//! || client::get("http://www.rust-lang.org") // <- Create request builder -//! .header("User-Agent", "Actix-web") -//! .finish().unwrap() -//! .send() // <- Send http request -//! .map_err(|_| ()) -//! .and_then(|response| { // <- server http response -//! println!("Response: {:?}", response); -//! # actix::System::current().stop(); -//! Ok(()) -//! }) -//! ); -//! } -//! ``` -mod connector; -mod parser; -mod pipeline; -mod request; -mod response; -mod writer; - -pub use self::connector::{ - ClientConnector, ClientConnectorError, ClientConnectorStats, Connect, Connection, - Pause, Resume, -}; -pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; -pub(crate) use self::pipeline::Pipeline; -pub use self::pipeline::{SendRequest, SendRequestError}; -pub use self::request::{ClientRequest, ClientRequestBuilder}; -pub use self::response::ClientResponse; -pub(crate) use self::writer::HttpClientWriter; - -use error::ResponseError; -use http::Method; -use httpresponse::HttpResponse; - -/// Convert `SendRequestError` to a `HttpResponse` -impl ResponseError for SendRequestError { - fn error_response(&self) -> HttpResponse { - match *self { - SendRequestError::Timeout => HttpResponse::GatewayTimeout(), - SendRequestError::Connector(_) => HttpResponse::BadGateway(), - _ => HttpResponse::InternalServerError(), - }.into() - } -} - -/// Create request builder for `GET` requests -/// -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate futures; -/// # extern crate tokio; -/// # extern crate env_logger; -/// # use futures::Future; -/// # use std::process; -/// use actix_web::{actix, client}; -/// -/// fn main() { -/// actix::run( -/// || client::get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") -/// .finish().unwrap() -/// .send() // <- Send http request -/// .map_err(|_| ()) -/// .and_then(|response| { // <- server http response -/// println!("Response: {:?}", response); -/// # actix::System::current().stop(); -/// Ok(()) -/// }), -/// ); -/// } -/// ``` -pub fn get>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::GET).uri(uri); - builder -} - -/// Create request builder for `HEAD` requests -pub fn head>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::HEAD).uri(uri); - builder -} - -/// Create request builder for `POST` requests -pub fn post>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::POST).uri(uri); - builder -} - -/// Create request builder for `PUT` requests -pub fn put>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::PUT).uri(uri); - builder -} - -/// Create request builder for `DELETE` requests -pub fn delete>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::DELETE).uri(uri); - builder -} diff --git a/src/client/parser.rs b/src/client/parser.rs deleted file mode 100644 index 11252fa52..000000000 --- a/src/client/parser.rs +++ /dev/null @@ -1,238 +0,0 @@ -use std::mem; - -use bytes::{Bytes, BytesMut}; -use futures::{Async, Poll}; -use http::header::{self, HeaderName, HeaderValue}; -use http::{HeaderMap, StatusCode, Version}; -use httparse; - -use error::{ParseError, PayloadError}; - -use server::h1decoder::{EncodingDecoder, HeaderIndex}; -use server::IoStream; - -use super::response::ClientMessage; -use super::ClientResponse; - -const MAX_BUFFER_SIZE: usize = 131_072; -const MAX_HEADERS: usize = 96; - -#[derive(Default)] -pub struct HttpResponseParser { - decoder: Option, - eof: bool, // indicate that we read payload until stream eof -} - -#[derive(Debug, Fail)] -pub enum HttpResponseParserError { - /// Server disconnected - #[fail(display = "Server disconnected")] - Disconnect, - #[fail(display = "{}", _0)] - Error(#[cause] ParseError), -} - -impl HttpResponseParser { - pub fn parse( - &mut self, io: &mut T, buf: &mut BytesMut, - ) -> Poll - where - T: IoStream, - { - loop { - // Don't call parser until we have data to parse. - if !buf.is_empty() { - match HttpResponseParser::parse_message(buf) - .map_err(HttpResponseParserError::Error)? - { - Async::Ready((msg, info)) => { - if let Some((decoder, eof)) = info { - self.eof = eof; - self.decoder = Some(decoder); - } else { - self.eof = false; - self.decoder = None; - } - return Ok(Async::Ready(msg)); - } - Async::NotReady => { - if buf.capacity() >= MAX_BUFFER_SIZE { - return Err(HttpResponseParserError::Error( - ParseError::TooLarge, - )); - } - // Parser needs more data. - } - } - } - // Read some more data into the buffer for the parser. - match io.read_available(buf) { - Ok(Async::Ready((false, true))) => { - return Err(HttpResponseParserError::Disconnect) - } - Ok(Async::Ready(_)) => (), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => return Err(HttpResponseParserError::Error(err.into())), - } - } - } - - pub fn parse_payload( - &mut self, io: &mut T, buf: &mut BytesMut, - ) -> Poll, PayloadError> - where - T: IoStream, - { - if self.decoder.is_some() { - loop { - // read payload - let (not_ready, stream_finished) = match io.read_available(buf) { - Ok(Async::Ready((_, true))) => (false, true), - Ok(Async::Ready((_, false))) => (false, false), - Ok(Async::NotReady) => (true, false), - Err(err) => return Err(err.into()), - }; - - match self.decoder.as_mut().unwrap().decode(buf) { - Ok(Async::Ready(Some(b))) => return Ok(Async::Ready(Some(b))), - Ok(Async::Ready(None)) => { - self.decoder.take(); - return Ok(Async::Ready(None)); - } - Ok(Async::NotReady) => { - if not_ready { - return Ok(Async::NotReady); - } - if stream_finished { - // read untile eof? - if self.eof { - return Ok(Async::Ready(None)); - } else { - return Err(PayloadError::Incomplete); - } - } - } - Err(err) => return Err(err.into()), - } - } - } else { - Ok(Async::Ready(None)) - } - } - - fn parse_message( - buf: &mut BytesMut, - ) -> Poll<(ClientResponse, Option<(EncodingDecoder, bool)>), ParseError> { - // Unsafe: we read only this data only after httparse parses headers into. - // performance bump for pipeline benchmarks. - let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; - - let (len, version, status, headers_len) = { - let mut parsed: [httparse::Header; MAX_HEADERS] = - unsafe { mem::uninitialized() }; - - let mut resp = httparse::Response::new(&mut parsed); - match resp.parse(buf)? { - httparse::Status::Complete(len) => { - let version = if resp.version.unwrap_or(1) == 1 { - Version::HTTP_11 - } else { - Version::HTTP_10 - }; - HeaderIndex::record(buf, resp.headers, &mut headers); - let status = StatusCode::from_u16(resp.code.unwrap()) - .map_err(|_| ParseError::Status)?; - - (len, version, status, resp.headers.len()) - } - httparse::Status::Partial => return Ok(Async::NotReady), - } - }; - - let slice = buf.split_to(len).freeze(); - - // convert headers - let mut hdrs = HeaderMap::new(); - for idx in headers[..headers_len].iter() { - if let Ok(name) = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) { - // Unsafe: httparse check header value for valid utf-8 - let value = unsafe { - HeaderValue::from_shared_unchecked( - slice.slice(idx.value.0, idx.value.1), - ) - }; - hdrs.append(name, value); - } else { - return Err(ParseError::Header); - } - } - - let decoder = if status == StatusCode::SWITCHING_PROTOCOLS { - Some((EncodingDecoder::eof(), true)) - } else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) { - // Content-Length - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - Some((EncodingDecoder::length(len), false)) - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } else if chunked(&hdrs)? { - // Chunked encoding - Some((EncodingDecoder::chunked(), false)) - } else if let Some(value) = hdrs.get(header::CONNECTION) { - let close = if let Ok(s) = value.to_str() { - s == "close" - } else { - false - }; - if close { - Some((EncodingDecoder::eof(), true)) - } else { - None - } - } else { - None - }; - - if let Some(decoder) = decoder { - Ok(Async::Ready(( - ClientResponse::new(ClientMessage { - status, - version, - headers: hdrs, - cookies: None, - }), - Some(decoder), - ))) - } else { - Ok(Async::Ready(( - ClientResponse::new(ClientMessage { - status, - version, - headers: hdrs, - cookies: None, - }), - None, - ))) - } - } -} - -/// Check if request has chunked transfer encoding -pub fn chunked(headers: &HeaderMap) -> Result { - if let Some(encodings) = headers.get(header::TRANSFER_ENCODING) { - if let Ok(s) = encodings.to_str() { - Ok(s.to_lowercase().contains("chunked")) - } else { - Err(ParseError::Header) - } - } else { - Ok(false) - } -} diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs deleted file mode 100644 index 394b7a6cd..000000000 --- a/src/client/pipeline.rs +++ /dev/null @@ -1,552 +0,0 @@ -use bytes::{Bytes, BytesMut}; -use futures::sync::oneshot; -use futures::{Async, Future, Poll, Stream}; -use http::header::CONTENT_ENCODING; -use std::time::{Duration, Instant}; -use std::{io, mem}; -use tokio_timer::Delay; - -use actix::{Addr, Request, SystemService}; - -use super::{ - ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect, - Connection, HttpClientWriter, HttpResponseParser, HttpResponseParserError, -}; -use body::{Body, BodyStream}; -use context::{ActorHttpContext, Frame}; -use error::Error; -use error::PayloadError; -use header::ContentEncoding; -use http::{Method, Uri}; -use httpmessage::HttpMessage; -use server::input::PayloadStream; -use server::WriterState; - -/// A set of errors that can occur during request sending and response reading -#[derive(Fail, Debug)] -pub enum SendRequestError { - /// Response took too long - #[fail(display = "Timeout while waiting for response")] - Timeout, - /// Failed to connect to host - #[fail(display = "Failed to connect to host: {}", _0)] - Connector(#[cause] ClientConnectorError), - /// Error parsing response - #[fail(display = "{}", _0)] - ParseError(#[cause] HttpResponseParserError), - /// Error reading response payload - #[fail(display = "Error reading response payload: {}", _0)] - Io(#[cause] io::Error), -} - -impl From for SendRequestError { - fn from(err: io::Error) -> SendRequestError { - SendRequestError::Io(err) - } -} - -impl From for SendRequestError { - fn from(err: ClientConnectorError) -> SendRequestError { - match err { - ClientConnectorError::Timeout => SendRequestError::Timeout, - _ => SendRequestError::Connector(err), - } - } -} - -enum State { - New, - Connect(Request), - Connection(Connection), - Send(Box), - None, -} - -/// `SendRequest` is a `Future` which represents an asynchronous -/// request sending process. -#[must_use = "SendRequest does nothing unless polled"] -pub struct SendRequest { - req: ClientRequest, - state: State, - conn: Option>, - conn_timeout: Duration, - wait_timeout: Duration, - timeout: Option, -} - -impl SendRequest { - pub(crate) fn new(req: ClientRequest) -> SendRequest { - SendRequest { - req, - conn: None, - state: State::New, - timeout: None, - wait_timeout: Duration::from_secs(5), - conn_timeout: Duration::from_secs(1), - } - } - - pub(crate) fn with_connector( - req: ClientRequest, conn: Addr, - ) -> SendRequest { - SendRequest { - req, - conn: Some(conn), - state: State::New, - timeout: None, - wait_timeout: Duration::from_secs(5), - conn_timeout: Duration::from_secs(1), - } - } - - pub(crate) fn with_connection(req: ClientRequest, conn: Connection) -> SendRequest { - SendRequest { - req, - state: State::Connection(conn), - conn: None, - timeout: None, - wait_timeout: Duration::from_secs(5), - conn_timeout: Duration::from_secs(1), - } - } - - /// Set request timeout - /// - /// Request timeout is the total time before a response must be received. - /// Default value is 5 seconds. - pub fn timeout(mut self, timeout: Duration) -> Self { - self.timeout = Some(timeout); - self - } - - /// Set connection timeout - /// - /// Connection timeout includes resolving hostname and actual connection to - /// the host. - /// Default value is 1 second. - pub fn conn_timeout(mut self, timeout: Duration) -> Self { - self.conn_timeout = timeout; - self - } - - /// Set wait timeout - /// - /// If connections pool limits are enabled, wait time indicates max time - /// to wait for available connection. Default value is 5 seconds. - pub fn wait_timeout(mut self, timeout: Duration) -> Self { - self.wait_timeout = timeout; - self - } -} - -impl Future for SendRequest { - type Item = ClientResponse; - type Error = SendRequestError; - - fn poll(&mut self) -> Poll { - loop { - let state = mem::replace(&mut self.state, State::None); - - match state { - State::New => { - let conn = if let Some(conn) = self.conn.take() { - conn - } else { - ClientConnector::from_registry() - }; - self.state = State::Connect(conn.send(Connect { - uri: self.req.uri().clone(), - wait_timeout: self.wait_timeout, - conn_timeout: self.conn_timeout, - })) - } - State::Connect(mut conn) => match conn.poll() { - Ok(Async::NotReady) => { - self.state = State::Connect(conn); - return Ok(Async::NotReady); - } - Ok(Async::Ready(result)) => match result { - Ok(stream) => self.state = State::Connection(stream), - Err(err) => return Err(err.into()), - }, - Err(_) => { - return Err(SendRequestError::Connector( - ClientConnectorError::Disconnected, - )); - } - }, - State::Connection(conn) => { - let mut writer = HttpClientWriter::new(); - writer.start(&mut self.req)?; - - let body = match self.req.replace_body(Body::Empty) { - Body::Streaming(stream) => IoBody::Payload(stream), - Body::Actor(ctx) => IoBody::Actor(ctx), - _ => IoBody::Done, - }; - - let timeout = self - .timeout - .take() - .unwrap_or_else(|| Duration::from_secs(5)); - - let pl = Box::new(Pipeline { - body, - writer, - conn: Some(conn), - parser: Some(HttpResponseParser::default()), - parser_buf: BytesMut::new(), - disconnected: false, - body_completed: false, - drain: None, - decompress: None, - should_decompress: self.req.response_decompress(), - write_state: RunningState::Running, - timeout: Some(Delay::new(Instant::now() + timeout)), - meth: self.req.method().clone(), - path: self.req.uri().clone(), - }); - self.state = State::Send(pl); - } - State::Send(mut pl) => { - pl.poll_timeout()?; - pl.poll_write().map_err(|e| { - io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str()) - })?; - - match pl.parse() { - Ok(Async::Ready(mut resp)) => { - if self.req.method() == Method::HEAD { - pl.parser.take(); - } - resp.set_pipeline(pl); - return Ok(Async::Ready(resp)); - } - Ok(Async::NotReady) => { - self.state = State::Send(pl); - return Ok(Async::NotReady); - } - Err(err) => { - return Err(SendRequestError::ParseError(err)); - } - } - } - State::None => unreachable!(), - } - } - } -} - -pub struct Pipeline { - body: IoBody, - body_completed: bool, - conn: Option, - writer: HttpClientWriter, - parser: Option, - parser_buf: BytesMut, - disconnected: bool, - drain: Option>, - decompress: Option, - should_decompress: bool, - write_state: RunningState, - timeout: Option, - meth: Method, - path: Uri, -} - -enum IoBody { - Payload(BodyStream), - Actor(Box), - Done, -} - -#[derive(Debug, PartialEq)] -enum RunningState { - Running, - Paused, - Done, -} - -impl RunningState { - #[inline] - fn pause(&mut self) { - if *self != RunningState::Done { - *self = RunningState::Paused - } - } - #[inline] - fn resume(&mut self) { - if *self != RunningState::Done { - *self = RunningState::Running - } - } -} - -impl Pipeline { - fn release_conn(&mut self) { - if let Some(conn) = self.conn.take() { - if self.meth == Method::HEAD { - conn.close() - } else { - conn.release() - } - } - } - - #[inline] - fn parse(&mut self) -> Poll { - if let Some(ref mut conn) = self.conn { - match self - .parser - .as_mut() - .unwrap() - .parse(conn, &mut self.parser_buf) - { - Ok(Async::Ready(resp)) => { - // check content-encoding - if self.should_decompress { - if let Some(enc) = resp.headers().get(CONTENT_ENCODING) { - if let Ok(enc) = enc.to_str() { - match ContentEncoding::from(enc) { - ContentEncoding::Auto - | ContentEncoding::Identity => (), - enc => { - self.decompress = Some(PayloadStream::new(enc)) - } - } - } - } - } - - Ok(Async::Ready(resp)) - } - val => val, - } - } else { - Ok(Async::NotReady) - } - } - - #[inline] - pub(crate) fn poll(&mut self) -> Poll, PayloadError> { - if self.conn.is_none() { - return Ok(Async::Ready(None)); - } - let mut need_run = false; - - // need write? - match self - .poll_write() - .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))? - { - Async::NotReady => need_run = true, - Async::Ready(_) => { - self.poll_timeout().map_err(|e| { - io::Error::new(io::ErrorKind::Other, format!("{}", e)) - })?; - } - } - - // need read? - if self.parser.is_some() { - let conn: &mut Connection = self.conn.as_mut().unwrap(); - - loop { - match self - .parser - .as_mut() - .unwrap() - .parse_payload(conn, &mut self.parser_buf)? - { - Async::Ready(Some(b)) => { - if let Some(ref mut decompress) = self.decompress { - match decompress.feed_data(b) { - Ok(Some(b)) => return Ok(Async::Ready(Some(b))), - Ok(None) => return Ok(Async::NotReady), - Err(ref err) - if err.kind() == io::ErrorKind::WouldBlock => - { - continue - } - Err(err) => return Err(err.into()), - } - } else { - return Ok(Async::Ready(Some(b))); - } - } - Async::Ready(None) => { - let _ = self.parser.take(); - break; - } - Async::NotReady => return Ok(Async::NotReady), - } - } - } - - // eof - if let Some(mut decompress) = self.decompress.take() { - let res = decompress.feed_eof(); - if let Some(b) = res? { - self.release_conn(); - return Ok(Async::Ready(Some(b))); - } - } - - if need_run { - Ok(Async::NotReady) - } else { - self.release_conn(); - Ok(Async::Ready(None)) - } - } - - fn poll_timeout(&mut self) -> Result<(), SendRequestError> { - if self.timeout.is_some() { - match self.timeout.as_mut().unwrap().poll() { - Ok(Async::Ready(())) => return Err(SendRequestError::Timeout), - Ok(Async::NotReady) => (), - Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e).into()), - } - } - Ok(()) - } - - #[inline] - fn poll_write(&mut self) -> Poll<(), Error> { - if self.write_state == RunningState::Done || self.conn.is_none() { - return Ok(Async::Ready(())); - } - - let mut done = false; - if self.drain.is_none() && self.write_state != RunningState::Paused { - 'outter: loop { - let result = match mem::replace(&mut self.body, IoBody::Done) { - IoBody::Payload(mut body) => match body.poll()? { - Async::Ready(None) => { - self.writer.write_eof()?; - self.body_completed = true; - break; - } - Async::Ready(Some(chunk)) => { - self.body = IoBody::Payload(body); - self.writer.write(chunk.as_ref())? - } - Async::NotReady => { - done = true; - self.body = IoBody::Payload(body); - break; - } - }, - IoBody::Actor(mut ctx) => { - if self.disconnected { - ctx.disconnected(); - } - match ctx.poll()? { - Async::Ready(Some(vec)) => { - if vec.is_empty() { - self.body = IoBody::Actor(ctx); - break; - } - let mut res = None; - for frame in vec { - match frame { - Frame::Chunk(None) => { - self.body_completed = true; - self.writer.write_eof()?; - break 'outter; - } - Frame::Chunk(Some(chunk)) => { - res = - Some(self.writer.write(chunk.as_ref())?) - } - Frame::Drain(fut) => self.drain = Some(fut), - } - } - self.body = IoBody::Actor(ctx); - if self.drain.is_some() { - self.write_state.resume(); - break; - } - res.unwrap() - } - Async::Ready(None) => { - done = true; - break; - } - Async::NotReady => { - done = true; - self.body = IoBody::Actor(ctx); - break; - } - } - } - IoBody::Done => { - self.body_completed = true; - done = true; - break; - } - }; - - match result { - WriterState::Pause => { - self.write_state.pause(); - break; - } - WriterState::Done => self.write_state.resume(), - } - } - } - - // flush io but only if we need to - match self - .writer - .poll_completed(self.conn.as_mut().unwrap(), false) - { - Ok(Async::Ready(_)) => { - if self.disconnected - || (self.body_completed && self.writer.is_completed()) - { - self.write_state = RunningState::Done; - } else { - self.write_state.resume(); - } - - // resolve drain futures - if let Some(tx) = self.drain.take() { - let _ = tx.send(()); - } - // restart io processing - if !done || self.write_state == RunningState::Done { - self.poll_write() - } else { - Ok(Async::NotReady) - } - } - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(err) => Err(err.into()), - } - } -} - -impl Drop for Pipeline { - fn drop(&mut self) { - if let Some(conn) = self.conn.take() { - debug!( - "Client http transaction is not completed, dropping connection: {:?} {:?}", - self.meth, - self.path, - ); - conn.close() - } - } -} - -/// Future that resolves to a complete request body. -impl Stream for Box { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll, Self::Error> { - Pipeline::poll(self) - } -} diff --git a/src/client/request.rs b/src/client/request.rs deleted file mode 100644 index 76fb1be59..000000000 --- a/src/client/request.rs +++ /dev/null @@ -1,782 +0,0 @@ -use std::fmt::Write as FmtWrite; -use std::io::Write; -use std::time::Duration; -use std::{fmt, mem}; - -use actix::Addr; -use bytes::{BufMut, Bytes, BytesMut}; -use cookie::{Cookie, CookieJar}; -use futures::Stream; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; -use serde::Serialize; -use serde_json; -use serde_urlencoded; -use url::Url; - -use super::connector::{ClientConnector, Connection}; -use super::pipeline::SendRequest; -use body::Body; -use error::Error; -use header::{ContentEncoding, Header, IntoHeaderValue}; -use http::header::{self, HeaderName, HeaderValue}; -use http::{uri, Error as HttpError, HeaderMap, HttpTryFrom, Method, Uri, Version}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; - -/// An HTTP Client Request -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate futures; -/// # extern crate tokio; -/// # use futures::Future; -/// # use std::process; -/// use actix_web::{actix, client}; -/// -/// fn main() { -/// actix::run( -/// || client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") -/// .finish().unwrap() -/// .send() // <- Send http request -/// .map_err(|_| ()) -/// .and_then(|response| { // <- server http response -/// println!("Response: {:?}", response); -/// # actix::System::current().stop(); -/// Ok(()) -/// }), -/// ); -/// } -/// ``` -pub struct ClientRequest { - uri: Uri, - method: Method, - version: Version, - headers: HeaderMap, - body: Body, - chunked: bool, - upgrade: bool, - timeout: Option, - encoding: ContentEncoding, - response_decompress: bool, - buffer_capacity: usize, - conn: ConnectionType, -} - -enum ConnectionType { - Default, - Connector(Addr), - Connection(Connection), -} - -impl Default for ClientRequest { - fn default() -> ClientRequest { - ClientRequest { - uri: Uri::default(), - method: Method::default(), - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), - body: Body::Empty, - chunked: false, - upgrade: false, - timeout: None, - encoding: ContentEncoding::Auto, - response_decompress: true, - buffer_capacity: 32_768, - conn: ConnectionType::Default, - } - } -} - -impl ClientRequest { - /// Create request builder for `GET` request - pub fn get>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::GET).uri(uri); - builder - } - - /// Create request builder for `HEAD` request - pub fn head>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::HEAD).uri(uri); - builder - } - - /// Create request builder for `POST` request - pub fn post>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::POST).uri(uri); - builder - } - - /// Create request builder for `PUT` request - pub fn put>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::PUT).uri(uri); - builder - } - - /// Create request builder for `DELETE` request - pub fn delete>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::DELETE).uri(uri); - builder - } -} - -impl ClientRequest { - /// Create client request builder - pub fn build() -> ClientRequestBuilder { - ClientRequestBuilder { - request: Some(ClientRequest::default()), - err: None, - cookies: None, - default_headers: true, - } - } - - /// Create client request builder - pub fn build_from>(source: T) -> ClientRequestBuilder { - source.into() - } - - /// Get the request URI - #[inline] - pub fn uri(&self) -> &Uri { - &self.uri - } - - /// Set client request URI - #[inline] - pub fn set_uri(&mut self, uri: Uri) { - self.uri = uri - } - - /// Get the request method - #[inline] - pub fn method(&self) -> &Method { - &self.method - } - - /// Set HTTP `Method` for the request - #[inline] - pub fn set_method(&mut self, method: Method) { - self.method = method - } - - /// Get HTTP version for the request - #[inline] - pub fn version(&self) -> Version { - self.version - } - - /// Set http `Version` for the request - #[inline] - pub fn set_version(&mut self, version: Version) { - self.version = version - } - - /// Get the headers from the request - #[inline] - pub fn headers(&self) -> &HeaderMap { - &self.headers - } - - /// Get a mutable reference to the headers - #[inline] - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.headers - } - - /// is chunked encoding enabled - #[inline] - pub fn chunked(&self) -> bool { - self.chunked - } - - /// is upgrade request - #[inline] - pub fn upgrade(&self) -> bool { - self.upgrade - } - - /// Content encoding - #[inline] - pub fn content_encoding(&self) -> ContentEncoding { - self.encoding - } - - /// Decompress response payload - #[inline] - pub fn response_decompress(&self) -> bool { - self.response_decompress - } - - /// Requested write buffer capacity - pub fn write_buffer_capacity(&self) -> usize { - self.buffer_capacity - } - - /// Get body of this response - #[inline] - pub fn body(&self) -> &Body { - &self.body - } - - /// Set a body - pub fn set_body>(&mut self, body: B) { - self.body = body.into(); - } - - /// Extract body, replace it with `Empty` - pub(crate) fn replace_body(&mut self, body: Body) -> Body { - mem::replace(&mut self.body, body) - } - - /// Send request - /// - /// This method returns a future that resolves to a ClientResponse - pub fn send(mut self) -> SendRequest { - let timeout = self.timeout.take(); - let send = match mem::replace(&mut self.conn, ConnectionType::Default) { - ConnectionType::Default => SendRequest::new(self), - ConnectionType::Connector(conn) => SendRequest::with_connector(self, conn), - ConnectionType::Connection(conn) => SendRequest::with_connection(self, conn), - }; - if let Some(timeout) = timeout { - send.timeout(timeout) - } else { - send - } - } -} - -impl fmt::Debug for ClientRequest { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!( - f, - "\nClientRequest {:?} {}:{}", - self.version, self.method, self.uri - )?; - writeln!(f, " headers:")?; - for (key, val) in self.headers.iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -/// An HTTP Client request builder -/// -/// This type can be used to construct an instance of `ClientRequest` through a -/// builder-like pattern. -pub struct ClientRequestBuilder { - request: Option, - err: Option, - cookies: Option, - default_headers: bool, -} - -impl ClientRequestBuilder { - /// Set HTTP URI of request. - #[inline] - pub fn uri>(&mut self, uri: U) -> &mut Self { - match Url::parse(uri.as_ref()) { - Ok(url) => self._uri(url.as_str()), - Err(_) => self._uri(uri.as_ref()), - } - } - - fn _uri(&mut self, url: &str) -> &mut Self { - match Uri::try_from(url) { - Ok(uri) => { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.uri = uri; - } - } - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Set HTTP method of this request. - #[inline] - pub fn method(&mut self, method: Method) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.method = method; - } - self - } - - /// Set HTTP method of this request. - #[inline] - pub fn get_method(&mut self) -> &Method { - let parts = self.request.as_ref().expect("cannot reuse request builder"); - &parts.method - } - - /// Set HTTP version of this request. - /// - /// By default requests's HTTP version depends on network stream - #[inline] - pub fn version(&mut self, version: Version) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.version = version; - } - self - } - - /// Set a header. - /// - /// ```rust - /// # extern crate mime; - /// # extern crate actix_web; - /// # use actix_web::client::*; - /// # - /// use actix_web::{client, http}; - /// - /// fn main() { - /// let req = client::ClientRequest::build() - /// .set(http::header::Date::now()) - /// .set(http::header::ContentType(mime::TEXT_HTML)) - /// .finish() - /// .unwrap(); - /// } - /// ``` - #[doc(hidden)] - pub fn set(&mut self, hdr: H) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - match hdr.try_into() { - Ok(value) => { - parts.headers.insert(H::name(), value); - } - Err(e) => self.err = Some(e.into()), - } - } - self - } - - /// Append a header. - /// - /// Header gets appended to existing header. - /// To override header use `set_header()` method. - /// - /// ```rust - /// # extern crate http; - /// # extern crate actix_web; - /// # use actix_web::client::*; - /// # - /// use http::header; - /// - /// fn main() { - /// let req = ClientRequest::build() - /// .header("X-TEST", "value") - /// .header(header::CONTENT_TYPE, "application/json") - /// .finish() - /// .unwrap(); - /// } - /// ``` - pub fn header(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.request, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.append(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set a header. - pub fn set_header(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.request, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set a header only if it is not yet set. - pub fn set_header_if_none(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.request, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => if !parts.headers.contains_key(&key) { - match value.try_into() { - Ok(value) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - } - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set content encoding. - /// - /// By default `ContentEncoding::Identity` is used. - #[inline] - pub fn content_encoding(&mut self, enc: ContentEncoding) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.encoding = enc; - } - self - } - - /// Enables automatic chunked transfer encoding - #[inline] - pub fn chunked(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.chunked = true; - } - self - } - - /// Enable connection upgrade - #[inline] - pub fn upgrade(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.upgrade = true; - } - self - } - - /// Set request's content type - #[inline] - pub fn content_type(&mut self, value: V) -> &mut Self - where - HeaderValue: HttpTryFrom, - { - if let Some(parts) = parts(&mut self.request, &self.err) { - match HeaderValue::try_from(value) { - Ok(value) => { - parts.headers.insert(header::CONTENT_TYPE, value); - } - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set content length - #[inline] - pub fn content_length(&mut self, len: u64) -> &mut Self { - let mut wrt = BytesMut::new().writer(); - let _ = write!(wrt, "{}", len); - self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) - } - - /// Set a cookie - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{client, http}; - /// - /// fn main() { - /// let req = client::ClientRequest::build() - /// .cookie( - /// http::Cookie::build("name", "value") - /// .domain("www.rust-lang.org") - /// .path("/") - /// .secure(true) - /// .http_only(true) - /// .finish(), - /// ) - /// .finish() - /// .unwrap(); - /// } - /// ``` - pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { - if self.cookies.is_none() { - let mut jar = CookieJar::new(); - jar.add(cookie.into_owned()); - self.cookies = Some(jar) - } else { - self.cookies.as_mut().unwrap().add(cookie.into_owned()); - } - self - } - - /// Do not add default request headers. - /// By default `Accept-Encoding` and `User-Agent` headers are set. - pub fn no_default_headers(&mut self) -> &mut Self { - self.default_headers = false; - self - } - - /// Disable automatic decompress response body - pub fn disable_decompress(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.response_decompress = false; - } - self - } - - /// Set write buffer capacity - /// - /// Default buffer capacity is 32kb - pub fn write_buffer_capacity(&mut self, cap: usize) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.buffer_capacity = cap; - } - self - } - - /// Set request timeout - /// - /// Request timeout is a total time before response should be received. - /// Default value is 5 seconds. - pub fn timeout(&mut self, timeout: Duration) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.timeout = Some(timeout); - } - self - } - - /// Send request using custom connector - pub fn with_connector(&mut self, conn: Addr) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.conn = ConnectionType::Connector(conn); - } - self - } - - /// Send request using existing `Connection` - pub fn with_connection(&mut self, conn: Connection) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.conn = ConnectionType::Connection(conn); - } - self - } - - /// This method calls provided closure with builder reference if - /// value is `true`. - pub fn if_true(&mut self, value: bool, f: F) -> &mut Self - where - F: FnOnce(&mut ClientRequestBuilder), - { - if value { - f(self); - } - self - } - - /// This method calls provided closure with builder reference if - /// value is `Some`. - pub fn if_some(&mut self, value: Option, f: F) -> &mut Self - where - F: FnOnce(T, &mut ClientRequestBuilder), - { - if let Some(val) = value { - f(val, self); - } - self - } - - /// Set a body and generate `ClientRequest`. - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn body>(&mut self, body: B) -> Result { - if let Some(e) = self.err.take() { - return Err(e.into()); - } - - if self.default_headers { - // enable br only for https - let https = if let Some(parts) = parts(&mut self.request, &self.err) { - parts - .uri - .scheme_part() - .map(|s| s == &uri::Scheme::HTTPS) - .unwrap_or(true) - } else { - true - }; - - if https { - self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate"); - } else { - self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate"); - } - - // set request host header - if let Some(parts) = parts(&mut self.request, &self.err) { - if let Some(host) = parts.uri.host() { - if !parts.headers.contains_key(header::HOST) { - let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - - let _ = match parts.uri.port() { - None | Some(80) | Some(443) => write!(wrt, "{}", host), - Some(port) => write!(wrt, "{}:{}", host, port), - }; - - match wrt.get_mut().take().freeze().try_into() { - Ok(value) => { - parts.headers.insert(header::HOST, value); - } - Err(e) => self.err = Some(e.into()), - } - } - } - } - - // user agent - self.set_header_if_none( - header::USER_AGENT, - concat!("actix-web/", env!("CARGO_PKG_VERSION")), - ); - } - - let mut request = self.request.take().expect("cannot reuse request builder"); - - // set cookies - if let Some(ref mut jar) = self.cookies { - let mut cookie = String::new(); - for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - request.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); - } - request.body = body.into(); - Ok(request) - } - - /// Set a JSON body and generate `ClientRequest` - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn json(&mut self, value: T) -> Result { - let body = serde_json::to_string(&value)?; - - let contains = if let Some(parts) = parts(&mut self.request, &self.err) { - parts.headers.contains_key(header::CONTENT_TYPE) - } else { - true - }; - if !contains { - self.header(header::CONTENT_TYPE, "application/json"); - } - - self.body(body) - } - - /// Set a urlencoded body and generate `ClientRequest` - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn form(&mut self, value: T) -> Result { - let body = serde_urlencoded::to_string(&value)?; - - let contains = if let Some(parts) = parts(&mut self.request, &self.err) { - parts.headers.contains_key(header::CONTENT_TYPE) - } else { - true - }; - if !contains { - self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded"); - } - - self.body(body) - } - - /// Set a streaming body and generate `ClientRequest`. - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn streaming(&mut self, stream: S) -> Result - where - S: Stream + 'static, - E: Into, - { - self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) - } - - /// Set an empty body and generate `ClientRequest` - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn finish(&mut self) -> Result { - self.body(Body::Empty) - } - - /// This method construct new `ClientRequestBuilder` - pub fn take(&mut self) -> ClientRequestBuilder { - ClientRequestBuilder { - request: self.request.take(), - err: self.err.take(), - cookies: self.cookies.take(), - default_headers: self.default_headers, - } - } -} - -#[inline] -fn parts<'a>( - parts: &'a mut Option, err: &Option, -) -> Option<&'a mut ClientRequest> { - if err.is_some() { - return None; - } - parts.as_mut() -} - -impl fmt::Debug for ClientRequestBuilder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(ref parts) = self.request { - writeln!( - f, - "\nClientRequestBuilder {:?} {}:{}", - parts.version, parts.method, parts.uri - )?; - writeln!(f, " headers:")?; - for (key, val) in parts.headers.iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } else { - write!(f, "ClientRequestBuilder(Consumed)") - } - } -} - -/// Create `ClientRequestBuilder` from `HttpRequest` -/// -/// It is useful for proxy requests. This implementation -/// copies all request headers and the method. -impl<'a, S: 'static> From<&'a HttpRequest> for ClientRequestBuilder { - fn from(req: &'a HttpRequest) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - for (key, value) in req.headers() { - builder.header(key.clone(), value.clone()); - } - builder.method(req.method().clone()); - builder - } -} diff --git a/src/client/response.rs b/src/client/response.rs deleted file mode 100644 index 5f1f42649..000000000 --- a/src/client/response.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::cell::RefCell; -use std::{fmt, str}; - -use cookie::Cookie; -use http::header::{self, HeaderValue}; -use http::{HeaderMap, StatusCode, Version}; - -use error::CookieParseError; -use httpmessage::HttpMessage; - -use super::pipeline::Pipeline; - -pub(crate) struct ClientMessage { - pub status: StatusCode, - pub version: Version, - pub headers: HeaderMap, - pub cookies: Option>>, -} - -impl Default for ClientMessage { - fn default() -> ClientMessage { - ClientMessage { - status: StatusCode::OK, - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), - cookies: None, - } - } -} - -/// An HTTP Client response -pub struct ClientResponse(ClientMessage, RefCell>>); - -impl HttpMessage for ClientResponse { - type Stream = Box; - - /// Get the headers from the response. - #[inline] - fn headers(&self) -> &HeaderMap { - &self.0.headers - } - - #[inline] - fn payload(&self) -> Box { - self.1 - .borrow_mut() - .take() - .expect("Payload is already consumed.") - } -} - -impl ClientResponse { - pub(crate) fn new(msg: ClientMessage) -> ClientResponse { - ClientResponse(msg, RefCell::new(None)) - } - - pub(crate) fn set_pipeline(&mut self, pl: Box) { - *self.1.borrow_mut() = Some(pl); - } - - /// Get the HTTP version of this response. - #[inline] - pub fn version(&self) -> Version { - self.0.version - } - - /// Get the status from the server. - #[inline] - pub fn status(&self) -> StatusCode { - self.0.status - } - - /// Load response cookies. - pub fn cookies(&self) -> Result>, CookieParseError> { - let mut cookies = Vec::new(); - for val in self.0.headers.get_all(header::SET_COOKIE).iter() { - let s = str::from_utf8(val.as_bytes()).map_err(CookieParseError::from)?; - cookies.push(Cookie::parse_encoded(s)?.into_owned()); - } - Ok(cookies) - } - - /// Return request cookie. - pub fn cookie(&self, name: &str) -> Option { - if let Ok(cookies) = self.cookies() { - for cookie in cookies { - if cookie.name() == name { - return Some(cookie); - } - } - } - None - } -} - -impl fmt::Debug for ClientResponse { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status())?; - writeln!(f, " headers:")?; - for (key, val) in self.headers().iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_debug() { - let mut resp = ClientResponse::new(ClientMessage::default()); - resp.0 - .headers - .insert(header::COOKIE, HeaderValue::from_static("cookie1=value1")); - resp.0 - .headers - .insert(header::COOKIE, HeaderValue::from_static("cookie2=value2")); - - let dbg = format!("{:?}", resp); - assert!(dbg.contains("ClientResponse")); - } -} diff --git a/src/client/writer.rs b/src/client/writer.rs deleted file mode 100644 index e74f22332..000000000 --- a/src/client/writer.rs +++ /dev/null @@ -1,412 +0,0 @@ -#![cfg_attr( - feature = "cargo-clippy", - allow(clippy::redundant_field_names) -)] - -use std::cell::RefCell; -use std::fmt::Write as FmtWrite; -use std::io::{self, Write}; - -#[cfg(feature = "brotli")] -use brotli2::write::BrotliEncoder; -use bytes::{BufMut, BytesMut}; -#[cfg(feature = "flate2")] -use flate2::write::{GzEncoder, ZlibEncoder}; -#[cfg(feature = "flate2")] -use flate2::Compression; -use futures::{Async, Poll}; -use http::header::{ - HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, -}; -use http::{HttpTryFrom, Version}; -use time::{self, Duration}; -use tokio_io::AsyncWrite; - -use body::{Binary, Body}; -use header::ContentEncoding; -use server::output::{ContentEncoder, Output, TransferEncoding}; -use server::WriterState; - -use client::ClientRequest; - -const AVERAGE_HEADER_SIZE: usize = 30; - -bitflags! { - struct Flags: u8 { - const STARTED = 0b0000_0001; - const UPGRADE = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; - const DISCONNECTED = 0b0000_1000; - } -} - -pub(crate) struct HttpClientWriter { - flags: Flags, - written: u64, - headers_size: u32, - buffer: Output, - buffer_capacity: usize, -} - -impl HttpClientWriter { - pub fn new() -> HttpClientWriter { - HttpClientWriter { - flags: Flags::empty(), - written: 0, - headers_size: 0, - buffer_capacity: 0, - buffer: Output::Buffer(BytesMut::new()), - } - } - - pub fn disconnected(&mut self) { - self.buffer.take(); - } - - pub fn is_completed(&self) -> bool { - self.buffer.is_empty() - } - - // pub fn keepalive(&self) -> bool { - // self.flags.contains(Flags::KEEPALIVE) && - // !self.flags.contains(Flags::UPGRADE) } - - fn write_to_stream( - &mut self, stream: &mut T, - ) -> io::Result { - while !self.buffer.is_empty() { - match stream.write(self.buffer.as_ref().as_ref()) { - Ok(0) => { - self.disconnected(); - return Ok(WriterState::Done); - } - Ok(n) => { - let _ = self.buffer.split_to(n); - } - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - if self.buffer.len() > self.buffer_capacity { - return Ok(WriterState::Pause); - } else { - return Ok(WriterState::Done); - } - } - Err(err) => return Err(err), - } - } - Ok(WriterState::Done) - } -} - -pub struct Writer<'a>(pub &'a mut BytesMut); - -impl<'a> io::Write for Writer<'a> { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl HttpClientWriter { - pub fn start(&mut self, msg: &mut ClientRequest) -> io::Result<()> { - // prepare task - self.buffer = content_encoder(self.buffer.take(), msg); - self.flags.insert(Flags::STARTED); - if msg.upgrade() { - self.flags.insert(Flags::UPGRADE); - } - - // render message - { - // output buffer - let buffer = self.buffer.as_mut(); - - // status line - writeln!( - Writer(buffer), - "{} {} {:?}\r", - msg.method(), - msg.uri() - .path_and_query() - .map(|u| u.as_str()) - .unwrap_or("/"), - msg.version() - ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - - // write headers - if let Body::Binary(ref bytes) = *msg.body() { - buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); - } else { - buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE); - } - - for (key, value) in msg.headers() { - let v = value.as_ref(); - let k = key.as_str().as_bytes(); - buffer.reserve(k.len() + v.len() + 4); - buffer.put_slice(k); - buffer.put_slice(b": "); - buffer.put_slice(v); - buffer.put_slice(b"\r\n"); - } - - // set date header - if !msg.headers().contains_key(DATE) { - buffer.extend_from_slice(b"date: "); - set_date(buffer); - buffer.extend_from_slice(b"\r\n\r\n"); - } else { - buffer.extend_from_slice(b"\r\n"); - } - } - self.headers_size = self.buffer.len() as u32; - - if msg.body().is_binary() { - if let Body::Binary(bytes) = msg.replace_body(Body::Empty) { - self.written += bytes.len() as u64; - self.buffer.write(bytes.as_ref())?; - } - } else { - self.buffer_capacity = msg.write_buffer_capacity(); - } - Ok(()) - } - - pub fn write(&mut self, payload: &[u8]) -> io::Result { - self.written += payload.len() as u64; - if !self.flags.contains(Flags::DISCONNECTED) { - self.buffer.write(payload)?; - } - - if self.buffer.len() > self.buffer_capacity { - Ok(WriterState::Pause) - } else { - Ok(WriterState::Done) - } - } - - pub fn write_eof(&mut self) -> io::Result<()> { - if self.buffer.write_eof()? { - Ok(()) - } else { - Err(io::Error::new( - io::ErrorKind::Other, - "Last payload item, but eof is not reached", - )) - } - } - - #[inline] - pub fn poll_completed( - &mut self, stream: &mut T, shutdown: bool, - ) -> Poll<(), io::Error> { - match self.write_to_stream(stream) { - Ok(WriterState::Done) => { - if shutdown { - stream.shutdown() - } else { - Ok(Async::Ready(())) - } - } - Ok(WriterState::Pause) => Ok(Async::NotReady), - Err(err) => Err(err), - } - } -} - -fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output { - let version = req.version(); - let mut body = req.replace_body(Body::Empty); - let mut encoding = req.content_encoding(); - - let transfer = match body { - Body::Empty => { - req.headers_mut().remove(CONTENT_LENGTH); - return Output::Empty(buf); - } - Body::Binary(ref mut bytes) => { - #[cfg(any(feature = "flate2", feature = "brotli"))] - { - if encoding.is_compression() { - let mut tmp = BytesMut::new(); - let mut transfer = TransferEncoding::eof(tmp); - let mut enc = match encoding { - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => ContentEncoder::Deflate( - ZlibEncoder::new(transfer, Compression::default()), - ), - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( - transfer, - Compression::default(), - )), - #[cfg(feature = "brotli")] - ContentEncoding::Br => { - ContentEncoder::Br(BrotliEncoder::new(transfer, 5)) - } - ContentEncoding::Auto | ContentEncoding::Identity => { - unreachable!() - } - }; - // TODO return error! - let _ = enc.write(bytes.as_ref()); - let _ = enc.write_eof(); - *bytes = Binary::from(enc.buf_mut().take()); - - req.headers_mut().insert( - CONTENT_ENCODING, - HeaderValue::from_static(encoding.as_str()), - ); - encoding = ContentEncoding::Identity; - } - let mut b = BytesMut::new(); - let _ = write!(b, "{}", bytes.len()); - req.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); - TransferEncoding::eof(buf) - } - #[cfg(not(any(feature = "flate2", feature = "brotli")))] - { - let mut b = BytesMut::new(); - let _ = write!(b, "{}", bytes.len()); - req.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); - TransferEncoding::eof(buf) - } - } - Body::Streaming(_) | Body::Actor(_) => { - if req.upgrade() { - if version == Version::HTTP_2 { - error!("Connection upgrade is forbidden for HTTP/2"); - } else { - req.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("upgrade")); - } - if encoding != ContentEncoding::Identity { - encoding = ContentEncoding::Identity; - req.headers_mut().remove(CONTENT_ENCODING); - } - TransferEncoding::eof(buf) - } else { - streaming_encoding(buf, version, req) - } - } - }; - - if encoding.is_compression() { - req.headers_mut().insert( - CONTENT_ENCODING, - HeaderValue::from_static(encoding.as_str()), - ); - } - - req.replace_body(body); - let enc = match encoding { - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => { - ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::default())) - } - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => { - ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default())) - } - #[cfg(feature = "brotli")] - ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 5)), - ContentEncoding::Identity | ContentEncoding::Auto => return Output::TE(transfer), - }; - Output::Encoder(enc) -} - -fn streaming_encoding( - buf: BytesMut, version: Version, req: &mut ClientRequest, -) -> TransferEncoding { - if req.chunked() { - // Enable transfer encoding - req.headers_mut().remove(CONTENT_LENGTH); - if version == Version::HTTP_2 { - req.headers_mut().remove(TRANSFER_ENCODING); - TransferEncoding::eof(buf) - } else { - req.headers_mut() - .insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked")); - TransferEncoding::chunked(buf) - } - } else { - // if Content-Length is specified, then use it as length hint - let (len, chunked) = if let Some(len) = req.headers().get(CONTENT_LENGTH) { - // Content-Length - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - (Some(len), false) - } else { - error!("illegal Content-Length: {:?}", len); - (None, false) - } - } else { - error!("illegal Content-Length: {:?}", len); - (None, false) - } - } else { - (None, true) - }; - - if !chunked { - if let Some(len) = len { - TransferEncoding::length(len, buf) - } else { - TransferEncoding::eof(buf) - } - } else { - // Enable transfer encoding - match version { - Version::HTTP_11 => { - req.headers_mut() - .insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked")); - TransferEncoding::chunked(buf) - } - _ => { - req.headers_mut().remove(TRANSFER_ENCODING); - TransferEncoding::eof(buf) - } - } - } - } -} - -// "Sun, 06 Nov 1994 08:49:37 GMT".len() -pub const DATE_VALUE_LENGTH: usize = 29; - -fn set_date(dst: &mut BytesMut) { - CACHED.with(|cache| { - let mut cache = cache.borrow_mut(); - let now = time::get_time(); - if now > cache.next_update { - cache.update(now); - } - dst.extend_from_slice(cache.buffer()); - }) -} - -struct CachedDate { - bytes: [u8; DATE_VALUE_LENGTH], - next_update: time::Timespec, -} - -thread_local!(static CACHED: RefCell = RefCell::new(CachedDate { - bytes: [0; DATE_VALUE_LENGTH], - next_update: time::Timespec::new(0, 0), -})); - -impl CachedDate { - fn buffer(&self) -> &[u8] { - &self.bytes[..] - } - - fn update(&mut self, now: time::Timespec) { - write!(&mut self.bytes[..], "{}", time::at_utc(now).rfc822()).unwrap(); - self.next_update = now + Duration::seconds(1); - self.next_update.nsec = 0; - } -} diff --git a/src/context.rs b/src/context.rs deleted file mode 100644 index 71a5af2d8..000000000 --- a/src/context.rs +++ /dev/null @@ -1,294 +0,0 @@ -extern crate actix; - -use futures::sync::oneshot; -use futures::sync::oneshot::Sender; -use futures::{Async, Future, Poll}; -use smallvec::SmallVec; -use std::marker::PhantomData; - -use self::actix::dev::{ - AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope, -}; -use self::actix::fut::ActorFuture; -use self::actix::{ - Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle, -}; - -use body::{Binary, Body}; -use error::{Error, ErrorInternalServerError}; -use httprequest::HttpRequest; - -pub trait ActorHttpContext: 'static { - fn disconnected(&mut self); - fn poll(&mut self) -> Poll>, Error>; -} - -#[derive(Debug)] -pub enum Frame { - Chunk(Option), - Drain(oneshot::Sender<()>), -} - -impl Frame { - pub fn len(&self) -> usize { - match *self { - Frame::Chunk(Some(ref bin)) => bin.len(), - _ => 0, - } - } -} - -/// Execution context for http actors -pub struct HttpContext -where - A: Actor>, -{ - inner: ContextParts, - stream: Option>, - request: HttpRequest, - disconnected: bool, -} - -impl ActorContext for HttpContext -where - A: Actor, -{ - fn stop(&mut self) { - self.inner.stop(); - } - fn terminate(&mut self) { - self.inner.terminate() - } - fn state(&self) -> ActorState { - self.inner.state() - } -} - -impl AsyncContext for HttpContext -where - A: Actor, -{ - #[inline] - fn spawn(&mut self, fut: F) -> SpawnHandle - where - F: ActorFuture + 'static, - { - self.inner.spawn(fut) - } - #[inline] - fn wait(&mut self, fut: F) - where - F: ActorFuture + 'static, - { - self.inner.wait(fut) - } - #[doc(hidden)] - #[inline] - fn waiting(&self) -> bool { - self.inner.waiting() - || self.inner.state() == ActorState::Stopping - || self.inner.state() == ActorState::Stopped - } - #[inline] - fn cancel_future(&mut self, handle: SpawnHandle) -> bool { - self.inner.cancel_future(handle) - } - #[inline] - fn address(&self) -> Addr { - self.inner.address() - } -} - -impl HttpContext -where - A: Actor, -{ - #[inline] - /// Create a new HTTP Context from a request and an actor - pub fn create(req: HttpRequest, actor: A) -> Body { - let mb = Mailbox::default(); - let ctx = HttpContext { - inner: ContextParts::new(mb.sender_producer()), - stream: None, - request: req, - disconnected: false, - }; - Body::Actor(Box::new(HttpContextFut::new(ctx, actor, mb))) - } - - /// Create a new HTTP Context - pub fn with_factory(req: HttpRequest, f: F) -> Body - where - F: FnOnce(&mut Self) -> A + 'static, - { - let mb = Mailbox::default(); - let mut ctx = HttpContext { - inner: ContextParts::new(mb.sender_producer()), - stream: None, - request: req, - disconnected: false, - }; - - let act = f(&mut ctx); - Body::Actor(Box::new(HttpContextFut::new(ctx, act, mb))) - } -} - -impl HttpContext -where - A: Actor, -{ - /// Shared application state - #[inline] - pub fn state(&self) -> &S { - self.request.state() - } - - /// Incoming request - #[inline] - pub fn request(&mut self) -> &mut HttpRequest { - &mut self.request - } - - /// Write payload - #[inline] - pub fn write>(&mut self, data: B) { - if !self.disconnected { - self.add_frame(Frame::Chunk(Some(data.into()))); - } else { - warn!("Trying to write to disconnected response"); - } - } - - /// Indicate end of streaming payload. Also this method calls `Self::close`. - #[inline] - pub fn write_eof(&mut self) { - self.add_frame(Frame::Chunk(None)); - } - - /// Returns drain future - pub fn drain(&mut self) -> Drain { - let (tx, rx) = oneshot::channel(); - self.add_frame(Frame::Drain(tx)); - Drain::new(rx) - } - - /// Check if connection still open - #[inline] - pub fn connected(&self) -> bool { - !self.disconnected - } - - #[inline] - fn add_frame(&mut self, frame: Frame) { - if self.stream.is_none() { - self.stream = Some(SmallVec::new()); - } - if let Some(s) = self.stream.as_mut() { - s.push(frame) - } - } - - /// Handle of the running future - /// - /// SpawnHandle is the handle returned by `AsyncContext::spawn()` method. - pub fn handle(&self) -> SpawnHandle { - self.inner.curr_handle() - } -} - -impl AsyncContextParts for HttpContext -where - A: Actor, -{ - fn parts(&mut self) -> &mut ContextParts { - &mut self.inner - } -} - -struct HttpContextFut -where - A: Actor>, -{ - fut: ContextFut>, -} - -impl HttpContextFut -where - A: Actor>, -{ - fn new(ctx: HttpContext, act: A, mailbox: Mailbox) -> Self { - let fut = ContextFut::new(ctx, act, mailbox); - HttpContextFut { fut } - } -} - -impl ActorHttpContext for HttpContextFut -where - A: Actor>, - S: 'static, -{ - #[inline] - fn disconnected(&mut self) { - self.fut.ctx().disconnected = true; - self.fut.ctx().stop(); - } - - fn poll(&mut self) -> Poll>, Error> { - if self.fut.alive() { - match self.fut.poll() { - Ok(Async::NotReady) | Ok(Async::Ready(())) => (), - Err(_) => return Err(ErrorInternalServerError("error")), - } - } - - // frames - if let Some(data) = self.fut.ctx().stream.take() { - Ok(Async::Ready(Some(data))) - } else if self.fut.alive() { - Ok(Async::NotReady) - } else { - Ok(Async::Ready(None)) - } - } -} - -impl ToEnvelope for HttpContext -where - A: Actor> + Handler, - M: Message + Send + 'static, - M::Result: Send, -{ - fn pack(msg: M, tx: Option>) -> Envelope { - Envelope::new(msg, tx) - } -} - -/// Consume a future -pub struct Drain { - fut: oneshot::Receiver<()>, - _a: PhantomData, -} - -impl Drain { - /// Create a drain from a future - pub fn new(fut: oneshot::Receiver<()>) -> Self { - Drain { - fut, - _a: PhantomData, - } - } -} - -impl ActorFuture for Drain { - type Item = (); - type Error = (); - type Actor = A; - - #[inline] - fn poll( - &mut self, _: &mut A, _: &mut ::Context, - ) -> Poll { - self.fut.poll().map_err(|_| ()) - } -} diff --git a/src/de.rs b/src/de.rs deleted file mode 100644 index 59ab79ba9..000000000 --- a/src/de.rs +++ /dev/null @@ -1,443 +0,0 @@ -use serde::de::{self, Deserializer, Error as DeError, Visitor}; - -use httprequest::HttpRequest; -use param::ParamsIter; - -macro_rules! unsupported_type { - ($trait_fn:ident, $name:expr) => { - fn $trait_fn(self, _: V) -> Result - where V: Visitor<'de> - { - Err(de::value::Error::custom(concat!("unsupported type: ", $name))) - } - }; -} - -macro_rules! parse_single_value { - ($trait_fn:ident, $visit_fn:ident, $tp:tt) => { - fn $trait_fn(self, visitor: V) -> Result - where V: Visitor<'de> - { - if self.req.match_info().len() != 1 { - Err(de::value::Error::custom( - format!("wrong number of parameters: {} expected 1", - self.req.match_info().len()).as_str())) - } else { - let v = self.req.match_info()[0].parse().map_err( - |_| de::value::Error::custom( - format!("can not parse {:?} to a {}", - &self.req.match_info()[0], $tp)))?; - visitor.$visit_fn(v) - } - } - } -} - -pub struct PathDeserializer<'de, S: 'de> { - req: &'de HttpRequest, -} - -impl<'de, S: 'de> PathDeserializer<'de, S> { - pub fn new(req: &'de HttpRequest) -> Self { - PathDeserializer { req } - } -} - -impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { - type Error = de::value::Error; - - fn deserialize_map(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_map(ParamsDeserializer { - params: self.req.match_info().iter(), - current: None, - }) - } - - fn deserialize_struct( - self, _: &'static str, _: &'static [&'static str], visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - self.deserialize_map(visitor) - } - - fn deserialize_unit(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_unit() - } - - fn deserialize_unit_struct( - self, _: &'static str, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - self.deserialize_unit(visitor) - } - - fn deserialize_newtype_struct( - self, _: &'static str, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_newtype_struct(self) - } - - fn deserialize_tuple( - self, len: usize, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - if self.req.match_info().len() < len { - Err(de::value::Error::custom( - format!( - "wrong number of parameters: {} expected {}", - self.req.match_info().len(), - len - ).as_str(), - )) - } else { - visitor.visit_seq(ParamsSeq { - params: self.req.match_info().iter(), - }) - } - } - - fn deserialize_tuple_struct( - self, _: &'static str, len: usize, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - if self.req.match_info().len() < len { - Err(de::value::Error::custom( - format!( - "wrong number of parameters: {} expected {}", - self.req.match_info().len(), - len - ).as_str(), - )) - } else { - visitor.visit_seq(ParamsSeq { - params: self.req.match_info().iter(), - }) - } - } - - fn deserialize_enum( - self, _: &'static str, _: &'static [&'static str], _: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("unsupported type: enum")) - } - - fn deserialize_str(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - if self.req.match_info().len() != 1 { - Err(de::value::Error::custom( - format!( - "wrong number of parameters: {} expected 1", - self.req.match_info().len() - ).as_str(), - )) - } else { - visitor.visit_str(&self.req.match_info()[0]) - } - } - - fn deserialize_seq(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_seq(ParamsSeq { - params: self.req.match_info().iter(), - }) - } - - unsupported_type!(deserialize_any, "'any'"); - unsupported_type!(deserialize_bytes, "bytes"); - unsupported_type!(deserialize_option, "Option"); - unsupported_type!(deserialize_identifier, "identifier"); - unsupported_type!(deserialize_ignored_any, "ignored_any"); - - parse_single_value!(deserialize_bool, visit_bool, "bool"); - parse_single_value!(deserialize_i8, visit_i8, "i8"); - parse_single_value!(deserialize_i16, visit_i16, "i16"); - parse_single_value!(deserialize_i32, visit_i32, "i32"); - parse_single_value!(deserialize_i64, visit_i64, "i64"); - parse_single_value!(deserialize_u8, visit_u8, "u8"); - parse_single_value!(deserialize_u16, visit_u16, "u16"); - parse_single_value!(deserialize_u32, visit_u32, "u32"); - parse_single_value!(deserialize_u64, visit_u64, "u64"); - parse_single_value!(deserialize_f32, visit_f32, "f32"); - parse_single_value!(deserialize_f64, visit_f64, "f64"); - parse_single_value!(deserialize_string, visit_string, "String"); - parse_single_value!(deserialize_byte_buf, visit_string, "String"); - parse_single_value!(deserialize_char, visit_char, "char"); -} - -struct ParamsDeserializer<'de> { - params: ParamsIter<'de>, - current: Option<(&'de str, &'de str)>, -} - -impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> { - type Error = de::value::Error; - - fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> - where - K: de::DeserializeSeed<'de>, - { - self.current = self.params.next().map(|ref item| (item.0, item.1)); - match self.current { - Some((key, _)) => Ok(Some(seed.deserialize(Key { key })?)), - None => Ok(None), - } - } - - fn next_value_seed(&mut self, seed: V) -> Result - where - V: de::DeserializeSeed<'de>, - { - if let Some((_, value)) = self.current.take() { - seed.deserialize(Value { value }) - } else { - Err(de::value::Error::custom("unexpected item")) - } - } -} - -struct Key<'de> { - key: &'de str, -} - -impl<'de> Deserializer<'de> for Key<'de> { - type Error = de::value::Error; - - fn deserialize_identifier(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_str(self.key) - } - - fn deserialize_any(self, _visitor: V) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("Unexpected")) - } - - forward_to_deserialize_any! { - bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes - byte_buf option unit unit_struct newtype_struct seq tuple - tuple_struct map struct enum ignored_any - } -} - -macro_rules! parse_value { - ($trait_fn:ident, $visit_fn:ident, $tp:tt) => { - fn $trait_fn(self, visitor: V) -> Result - where V: Visitor<'de> - { - let v = self.value.parse().map_err( - |_| de::value::Error::custom( - format!("can not parse {:?} to a {}", self.value, $tp)))?; - visitor.$visit_fn(v) - } - } -} - -struct Value<'de> { - value: &'de str, -} - -impl<'de> Deserializer<'de> for Value<'de> { - type Error = de::value::Error; - - parse_value!(deserialize_bool, visit_bool, "bool"); - parse_value!(deserialize_i8, visit_i8, "i8"); - parse_value!(deserialize_i16, visit_i16, "i16"); - parse_value!(deserialize_i32, visit_i32, "i16"); - parse_value!(deserialize_i64, visit_i64, "i64"); - parse_value!(deserialize_u8, visit_u8, "u8"); - parse_value!(deserialize_u16, visit_u16, "u16"); - parse_value!(deserialize_u32, visit_u32, "u32"); - parse_value!(deserialize_u64, visit_u64, "u64"); - parse_value!(deserialize_f32, visit_f32, "f32"); - parse_value!(deserialize_f64, visit_f64, "f64"); - parse_value!(deserialize_string, visit_string, "String"); - parse_value!(deserialize_byte_buf, visit_string, "String"); - parse_value!(deserialize_char, visit_char, "char"); - - fn deserialize_ignored_any(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_unit() - } - - fn deserialize_unit(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_unit() - } - - fn deserialize_unit_struct( - self, _: &'static str, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_unit() - } - - fn deserialize_bytes(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_borrowed_bytes(self.value.as_bytes()) - } - - fn deserialize_str(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_borrowed_str(self.value) - } - - fn deserialize_option(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_some(self) - } - - fn deserialize_enum( - self, _: &'static str, _: &'static [&'static str], visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_enum(ValueEnum { value: self.value }) - } - - fn deserialize_newtype_struct( - self, _: &'static str, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_newtype_struct(self) - } - - fn deserialize_tuple(self, _: usize, _: V) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("unsupported type: tuple")) - } - - fn deserialize_struct( - self, _: &'static str, _: &'static [&'static str], _: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("unsupported type: struct")) - } - - fn deserialize_tuple_struct( - self, _: &'static str, _: usize, _: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("unsupported type: tuple struct")) - } - - unsupported_type!(deserialize_any, "any"); - unsupported_type!(deserialize_seq, "seq"); - unsupported_type!(deserialize_map, "map"); - unsupported_type!(deserialize_identifier, "identifier"); -} - -struct ParamsSeq<'de> { - params: ParamsIter<'de>, -} - -impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> { - type Error = de::value::Error; - - fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> - where - T: de::DeserializeSeed<'de>, - { - match self.params.next() { - Some(item) => Ok(Some(seed.deserialize(Value { value: item.1 })?)), - None => Ok(None), - } - } -} - -struct ValueEnum<'de> { - value: &'de str, -} - -impl<'de> de::EnumAccess<'de> for ValueEnum<'de> { - type Error = de::value::Error; - type Variant = UnitVariant; - - fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> - where - V: de::DeserializeSeed<'de>, - { - Ok((seed.deserialize(Key { key: self.value })?, UnitVariant)) - } -} - -struct UnitVariant; - -impl<'de> de::VariantAccess<'de> for UnitVariant { - type Error = de::value::Error; - - fn unit_variant(self) -> Result<(), Self::Error> { - Ok(()) - } - - fn newtype_variant_seed(self, _seed: T) -> Result - where - T: de::DeserializeSeed<'de>, - { - Err(de::value::Error::custom("not supported")) - } - - fn tuple_variant(self, _len: usize, _visitor: V) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("not supported")) - } - - fn struct_variant( - self, _: &'static [&'static str], _: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("not supported")) - } -} diff --git a/src/error.rs b/src/error.rs index 76c8e79ec..724803805 100644 --- a/src/error.rs +++ b/src/error.rs @@ -22,8 +22,7 @@ pub use url::ParseError as UrlParseError; // re-exports pub use cookie::ParseError as CookieParseError; -use handler::Responder; -use httprequest::HttpRequest; +// use httprequest::HttpRequest; use httpresponse::{HttpResponse, HttpResponseParts}; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) @@ -727,18 +726,6 @@ where } } -impl Responder for InternalError -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, _: &HttpRequest) -> Result { - Err(self.into()) - } -} - /// Helper function that creates wrapper of any error and generate *BAD /// REQUEST* response. #[allow(non_snake_case)] diff --git a/src/extractor.rs b/src/extractor.rs deleted file mode 100644 index 7b0b4b003..000000000 --- a/src/extractor.rs +++ /dev/null @@ -1,1024 +0,0 @@ -use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; -use std::rc::Rc; -use std::{fmt, str}; - -use bytes::Bytes; -use encoding::all::UTF_8; -use encoding::types::{DecoderTrap, Encoding}; -use futures::{future, Async, Future, Poll}; -use mime::Mime; -use serde::de::{self, DeserializeOwned}; -use serde_urlencoded; - -use de::PathDeserializer; -use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError}; -use handler::{AsyncResult, FromRequest}; -use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; -use httprequest::HttpRequest; - -#[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from the request's path. -/// -/// ## Example -/// -/// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// use actix_web::{http, App, Path, Result}; -/// -/// /// extract path info from "/{username}/{count}/index.html" url -/// /// {username} - deserializes to a String -/// /// {count} - - deserializes to a u32 -/// fn index(info: Path<(String, u32)>) -> Result { -/// Ok(format!("Welcome {}! {}", info.0, info.1)) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/{username}/{count}/index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with(index), -/// ); // <- use `with` extractor -/// } -/// ``` -/// -/// It is possible to extract path information to a specific type that -/// implements `Deserialize` trait from *serde*. -/// -/// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Path, Result}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// extract path info using serde -/// fn index(info: Path) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/{username}/index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with(index), -/// ); // <- use `with` extractor -/// } -/// ``` -pub struct Path { - inner: T, -} - -impl AsRef for Path { - fn as_ref(&self) -> &T { - &self.inner - } -} - -impl Deref for Path { - type Target = T; - - fn deref(&self) -> &T { - &self.inner - } -} - -impl DerefMut for Path { - fn deref_mut(&mut self) -> &mut T { - &mut self.inner - } -} - -impl Path { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.inner - } -} - -impl From for Path { - fn from(inner: T) -> Path { - Path { inner } - } -} - -impl FromRequest for Path -where - T: DeserializeOwned, -{ - type Config = (); - type Result = Result; - - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - let req = req.clone(); - de::Deserialize::deserialize(PathDeserializer::new(&req)) - .map_err(ErrorNotFound) - .map(|inner| Path { inner }) - } -} - -impl fmt::Debug for Path { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } -} - -impl fmt::Display for Path { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } -} - -#[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from from the request's query. -/// -/// ## Example -/// -/// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Query, http}; -/// -/// -///#[derive(Debug, Deserialize)] -///pub enum ResponseType { -/// Token, -/// Code -///} -/// -///#[derive(Deserialize)] -///pub struct AuthRequest { -/// id: u64, -/// response_type: ResponseType, -///} -/// -/// // use `with` extractor for query info -/// // this handler get called only if request's query contains `username` field -/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` -/// fn index(info: Query) -> String { -/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor -/// } -/// ``` -pub struct Query(T); - -impl Deref for Query { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Query { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl Query { - /// Deconstruct to a inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl FromRequest for Query -where - T: de::DeserializeOwned, -{ - type Config = (); - type Result = Result; - - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - serde_urlencoded::from_str::(req.query_string()) - .map_err(|e| e.into()) - .map(Query) - } -} - -impl fmt::Debug for Query { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Display for Query { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -#[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from the request's body. -/// -/// To extract typed information from request's body, the type `T` must -/// implement the `Deserialize` trait from *serde*. -/// -/// [**FormConfig**](dev/struct.FormConfig.html) allows to configure extraction -/// process. -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Form, Result}; -/// -/// #[derive(Deserialize)] -/// struct FormData { -/// username: String, -/// } -/// -/// /// extract form data using serde -/// /// this handler get called only if content type is *x-www-form-urlencoded* -/// /// and content of the request could be deserialized to a `FormData` struct -/// fn index(form: Form) -> Result { -/// Ok(format!("Welcome {}!", form.username)) -/// } -/// # fn main() {} -/// ``` -pub struct Form(pub T); - -impl Form { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl Deref for Form { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Form { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl FromRequest for Form -where - T: DeserializeOwned + 'static, - S: 'static, -{ - type Config = FormConfig; - type Result = Box>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - let req2 = req.clone(); - let err = Rc::clone(&cfg.ehandler); - Box::new( - UrlEncoded::new(req) - .limit(cfg.limit) - .map_err(move |e| (*err)(e, &req2)) - .map(Form), - ) - } -} - -impl fmt::Debug for Form { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Display for Form { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -/// Form extractor configuration -/// -/// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Form, Result}; -/// -/// #[derive(Deserialize)] -/// struct FormData { -/// username: String, -/// } -/// -/// /// extract form data using serde. -/// /// custom configuration is used for this handler, max payload size is 4k -/// fn index(form: Form) -> Result { -/// Ok(format!("Welcome {}!", form.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| { -/// r.method(http::Method::GET) -/// // register form handler and change form extractor configuration -/// .with_config(index, |cfg| {cfg.0.limit(4096);}) -/// }, -/// ); -/// } -/// ``` -pub struct FormConfig { - limit: usize, - ehandler: Rc) -> Error>, -} - -impl FormConfig { - /// Change max size of payload. By default max size is 256Kb - pub fn limit(&mut self, limit: usize) -> &mut Self { - self.limit = limit; - self - } - - /// Set custom error handler - pub fn error_handler(&mut self, f: F) -> &mut Self - where - F: Fn(UrlencodedError, &HttpRequest) -> Error + 'static, - { - self.ehandler = Rc::new(f); - self - } -} - -impl Default for FormConfig { - fn default() -> Self { - FormConfig { - limit: 262_144, - ehandler: Rc::new(|e, _| e.into()), - } - } -} - -/// Request payload extractor. -/// -/// Loads request's payload and construct Bytes instance. -/// -/// [**PayloadConfig**](dev/struct.PayloadConfig.html) allows to configure -/// extraction process. -/// -/// ## Example -/// -/// ```rust -/// extern crate bytes; -/// # extern crate actix_web; -/// use actix_web::{http, App, Result}; -/// -/// /// extract text data from request -/// fn index(body: bytes::Bytes) -> Result { -/// Ok(format!("Body {:?}!", body)) -/// } -/// -/// fn main() { -/// let app = App::new() -/// .resource("/index.html", |r| r.method(http::Method::GET).with(index)); -/// } -/// ``` -impl FromRequest for Bytes { - type Config = PayloadConfig; - type Result = Result>, Error>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - // check content-type - cfg.check_mimetype(req)?; - - Ok(Box::new(MessageBody::new(req).limit(cfg.limit).from_err())) - } -} - -/// Extract text information from the request's body. -/// -/// Text extractor automatically decode body according to the request's charset. -/// -/// [**PayloadConfig**](dev/struct.PayloadConfig.html) allows to configure -/// extraction process. -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{http, App, Result}; -/// -/// /// extract text data from request -/// fn index(body: String) -> Result { -/// Ok(format!("Body {}!", body)) -/// } -/// -/// fn main() { -/// let app = App::new().resource("/index.html", |r| { -/// r.method(http::Method::GET) -/// .with_config(index, |cfg| { // <- register handler with extractor params -/// cfg.0.limit(4096); // <- limit size of the payload -/// }) -/// }); -/// } -/// ``` -impl FromRequest for String { - type Config = PayloadConfig; - type Result = Result>, Error>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - // check content-type - cfg.check_mimetype(req)?; - - // check charset - let encoding = req.encoding()?; - - Ok(Box::new( - MessageBody::new(req) - .limit(cfg.limit) - .from_err() - .and_then(move |body| { - let enc: *const Encoding = encoding as *const Encoding; - if enc == UTF_8 { - Ok(str::from_utf8(body.as_ref()) - .map_err(|_| ErrorBadRequest("Can not decode body"))? - .to_owned()) - } else { - Ok(encoding - .decode(&body, DecoderTrap::Strict) - .map_err(|_| ErrorBadRequest("Can not decode body"))?) - } - }), - )) - } -} - -/// Optionally extract a field from the request -/// -/// If the FromRequest for T fails, return None rather than returning an error response -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// extern crate rand; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest}; -/// use actix_web::error::ErrorBadRequest; -/// -/// #[derive(Debug, Deserialize)] -/// struct Thing { name: String } -/// -/// impl FromRequest for Thing { -/// type Config = (); -/// type Result = Result; -/// -/// #[inline] -/// fn from_request(req: &HttpRequest, _cfg: &Self::Config) -> Self::Result { -/// if rand::random() { -/// Ok(Thing { name: "thingy".into() }) -/// } else { -/// Err(ErrorBadRequest("no luck")) -/// } -/// -/// } -/// } -/// -/// /// extract text data from request -/// fn index(supplied_thing: Option) -> Result { -/// match supplied_thing { -/// // Puns not intended -/// Some(thing) => Ok(format!("Got something: {:?}", thing)), -/// None => Ok(format!("No thing!")) -/// } -/// } -/// -/// fn main() { -/// let app = App::new().resource("/users/:first", |r| { -/// r.method(http::Method::POST).with(index) -/// }); -/// } -/// ``` -impl FromRequest for Option -where - T: FromRequest, -{ - type Config = T::Config; - type Result = Box, Error = Error>>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new(T::from_request(req, cfg).into().then(|r| match r { - Ok(v) => future::ok(Some(v)), - Err(_) => future::ok(None), - })) - } -} - -/// Optionally extract a field from the request or extract the Error if unsuccessful -/// -/// If the FromRequest for T fails, inject Err into handler rather than returning an error response -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// extern crate rand; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest}; -/// use actix_web::error::ErrorBadRequest; -/// -/// #[derive(Debug, Deserialize)] -/// struct Thing { name: String } -/// -/// impl FromRequest for Thing { -/// type Config = (); -/// type Result = Result; -/// -/// #[inline] -/// fn from_request(req: &HttpRequest, _cfg: &Self::Config) -> Self::Result { -/// if rand::random() { -/// Ok(Thing { name: "thingy".into() }) -/// } else { -/// Err(ErrorBadRequest("no luck")) -/// } -/// -/// } -/// } -/// -/// /// extract text data from request -/// fn index(supplied_thing: Result) -> Result { -/// match supplied_thing { -/// Ok(thing) => Ok(format!("Got thing: {:?}", thing)), -/// Err(e) => Ok(format!("Error extracting thing: {}", e)) -/// } -/// } -/// -/// fn main() { -/// let app = App::new().resource("/users/:first", |r| { -/// r.method(http::Method::POST).with(index) -/// }); -/// } -/// ``` -impl FromRequest for Result -where - T: FromRequest, -{ - type Config = T::Config; - type Result = Box, Error = Error>>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new(T::from_request(req, cfg).into().then(future::ok)) - } -} - -/// Payload configuration for request's payload. -pub struct PayloadConfig { - limit: usize, - mimetype: Option, -} - -impl PayloadConfig { - /// Change max size of payload. By default max size is 256Kb - pub fn limit(&mut self, limit: usize) -> &mut Self { - self.limit = limit; - self - } - - /// Set required mime-type of the request. By default mime type is not - /// enforced. - pub fn mimetype(&mut self, mt: Mime) -> &mut Self { - self.mimetype = Some(mt); - self - } - - fn check_mimetype(&self, req: &HttpRequest) -> Result<(), Error> { - // check content-type - if let Some(ref mt) = self.mimetype { - match req.mime_type() { - Ok(Some(ref req_mt)) => { - if mt != req_mt { - return Err(ErrorBadRequest("Unexpected Content-Type")); - } - } - Ok(None) => { - return Err(ErrorBadRequest("Content-Type is expected")); - } - Err(err) => { - return Err(err.into()); - } - } - } - Ok(()) - } -} - -impl Default for PayloadConfig { - fn default() -> Self { - PayloadConfig { - limit: 262_144, - mimetype: None, - } - } -} - -macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { - - /// FromRequest implementation for tuple - impl + 'static),+> FromRequest for ($($T,)+) - where - S: 'static, - { - type Config = ($($T::Config,)+); - type Result = Box>; - - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new($fut_type { - s: PhantomData, - items: <($(Option<$T>,)+)>::default(), - futs: ($(Some($T::from_request(req, &cfg.$n).into()),)+), - }) - } - } - - struct $fut_type),+> - where - S: 'static, - { - s: PhantomData, - items: ($(Option<$T>,)+), - futs: ($(Option>,)+), - } - - impl),+> Future for $fut_type - where - S: 'static, - { - type Item = ($($T,)+); - type Error = Error; - - fn poll(&mut self) -> Poll { - let mut ready = true; - - $( - if self.futs.$n.is_some() { - match self.futs.$n.as_mut().unwrap().poll() { - Ok(Async::Ready(item)) => { - self.items.$n = Some(item); - self.futs.$n.take(); - } - Ok(Async::NotReady) => ready = false, - Err(e) => return Err(e), - } - } - )+ - - if ready { - Ok(Async::Ready( - ($(self.items.$n.take().unwrap(),)+) - )) - } else { - Ok(Async::NotReady) - } - } - } -}); - -impl FromRequest for () { - type Config = (); - type Result = Self; - fn from_request(_req: &HttpRequest, _cfg: &Self::Config) -> Self::Result {} -} - -tuple_from_req!(TupleFromRequest1, (0, A)); -tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); -tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); -tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D)); -tuple_from_req!(TupleFromRequest5, (0, A), (1, B), (2, C), (3, D), (4, E)); -tuple_from_req!( - TupleFromRequest6, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F) -); -tuple_from_req!( - TupleFromRequest7, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G) -); -tuple_from_req!( - TupleFromRequest8, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H) -); -tuple_from_req!( - TupleFromRequest9, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H), - (8, I) -); - -#[cfg(test)] -mod tests { - use super::*; - use bytes::Bytes; - use futures::{Async, Future}; - use http::header; - use mime; - use resource::Resource; - use router::{ResourceDef, Router}; - use test::TestRequest; - - #[derive(Deserialize, Debug, PartialEq)] - struct Info { - hello: String, - } - - #[test] - fn test_bytes() { - let cfg = PayloadConfig::default(); - let req = TestRequest::with_header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - match Bytes::from_request(&req, &cfg).unwrap().poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s, Bytes::from_static(b"hello=world")); - } - _ => unreachable!(), - } - } - - #[test] - fn test_string() { - let cfg = PayloadConfig::default(); - let req = TestRequest::with_header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - match String::from_request(&req, &cfg).unwrap().poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s, "hello=world"); - } - _ => unreachable!(), - } - } - - #[test] - fn test_form() { - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - let mut cfg = FormConfig::default(); - cfg.limit(4096); - match Form::::from_request(&req, &cfg).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.hello, "world"); - } - _ => unreachable!(), - } - } - - #[test] - fn test_option() { - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).finish(); - - let mut cfg = FormConfig::default(); - cfg.limit(4096); - - match Option::>::from_request(&req, &cfg) - .poll() - .unwrap() - { - Async::Ready(r) => assert_eq!(r, None), - _ => unreachable!(), - } - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - match Option::>::from_request(&req, &cfg) - .poll() - .unwrap() - { - Async::Ready(r) => assert_eq!( - r, - Some(Form(Info { - hello: "world".into() - })) - ), - _ => unreachable!(), - } - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"bye=world")) - .finish(); - - match Option::>::from_request(&req, &cfg) - .poll() - .unwrap() - { - Async::Ready(r) => assert_eq!(r, None), - _ => unreachable!(), - } - } - - #[test] - fn test_result() { - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - match Result::, Error>::from_request(&req, &FormConfig::default()) - .poll() - .unwrap() - { - Async::Ready(Ok(r)) => assert_eq!( - r, - Form(Info { - hello: "world".into() - }) - ), - _ => unreachable!(), - } - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"bye=world")) - .finish(); - - match Result::, Error>::from_request(&req, &FormConfig::default()) - .poll() - .unwrap() - { - Async::Ready(r) => assert!(r.is_err()), - _ => unreachable!(), - } - } - - #[test] - fn test_payload_config() { - let req = TestRequest::default().finish(); - let mut cfg = PayloadConfig::default(); - cfg.mimetype(mime::APPLICATION_JSON); - assert!(cfg.check_mimetype(&req).is_err()); - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).finish(); - assert!(cfg.check_mimetype(&req).is_err()); - - let req = - TestRequest::with_header(header::CONTENT_TYPE, "application/json").finish(); - assert!(cfg.check_mimetype(&req).is_ok()); - } - - #[derive(Deserialize)] - struct MyStruct { - key: String, - value: String, - } - - #[derive(Deserialize)] - struct Id { - id: String, - } - - #[derive(Deserialize)] - struct Test2 { - key: String, - value: u32, - } - - #[test] - fn test_request_extract() { - let req = TestRequest::with_uri("/name/user1/?id=test").finish(); - - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - - let s = Path::::from_request(&req, &()).unwrap(); - assert_eq!(s.key, "name"); - assert_eq!(s.value, "user1"); - - let s = Path::<(String, String)>::from_request(&req, &()).unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, "user1"); - - let s = Query::::from_request(&req, &()).unwrap(); - assert_eq!(s.id, "test"); - - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - let req = TestRequest::with_uri("/name/32/").finish(); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - - let s = Path::::from_request(&req, &()).unwrap(); - assert_eq!(s.as_ref().key, "name"); - assert_eq!(s.value, 32); - - let s = Path::<(String, u8)>::from_request(&req, &()).unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, 32); - - let res = Path::>::extract(&req).unwrap(); - assert_eq!(res[0], "name".to_owned()); - assert_eq!(res[1], "32".to_owned()); - } - - #[test] - fn test_extract_path_single() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); - - let req = TestRequest::with_uri("/32/").finish(); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - assert_eq!(*Path::::from_request(&req, &()).unwrap(), 32); - } - - #[test] - fn test_tuple_extract() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - - let req = TestRequest::with_uri("/name/user1/?id=test").finish(); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - - let res = match <(Path<(String, String)>,)>::extract(&req).poll() { - Ok(Async::Ready(res)) => res, - _ => panic!("error"), - }; - assert_eq!((res.0).0, "name"); - assert_eq!((res.0).1, "user1"); - - let res = match <(Path<(String, String)>, Path<(String, String)>)>::extract(&req) - .poll() - { - Ok(Async::Ready(res)) => res, - _ => panic!("error"), - }; - assert_eq!((res.0).0, "name"); - assert_eq!((res.0).1, "user1"); - assert_eq!((res.1).0, "name"); - assert_eq!((res.1).1, "user1"); - - let () = <()>::extract(&req); - } -} diff --git a/src/fs.rs b/src/fs.rs deleted file mode 100644 index 10cdaff7b..000000000 --- a/src/fs.rs +++ /dev/null @@ -1,1786 +0,0 @@ -//! Static files support -use std::fmt::Write; -use std::fs::{DirEntry, File, Metadata}; -use std::io::{Read, Seek}; -use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; -use std::path::{Path, PathBuf}; -use std::time::{SystemTime, UNIX_EPOCH}; -use std::{cmp, io}; - -#[cfg(unix)] -use std::os::unix::fs::MetadataExt; - -use bytes::Bytes; -use futures::{Async, Future, Poll, Stream}; -use futures_cpupool::{CpuFuture, CpuPool}; -use htmlescape::encode_minimal as escape_html_entity; -use mime; -use mime_guess::{get_mime_type, guess_mime_type}; -use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; - -use error::{Error, StaticFileError}; -use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; -use header; -use header::{ContentDisposition, DispositionParam, DispositionType}; -use http::{ContentEncoding, Method, StatusCode}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use param::FromParam; -use server::settings::DEFAULT_CPUPOOL; - -///Describes `StaticFiles` configiration -/// -///To configure actix's static resources you need -///to define own configiration type and implement any method -///you wish to customize. -///As trait implements reasonable defaults for Actix. -/// -///## Example -/// -///```rust -/// extern crate mime; -/// extern crate actix_web; -/// use actix_web::http::header::DispositionType; -/// use actix_web::fs::{StaticFileConfig, NamedFile}; -/// -/// #[derive(Default)] -/// struct MyConfig; -/// -/// impl StaticFileConfig for MyConfig { -/// fn content_disposition_map(typ: mime::Name) -> DispositionType { -/// DispositionType::Attachment -/// } -/// } -/// -/// let file = NamedFile::open_with_config("foo.txt", MyConfig); -///``` -pub trait StaticFileConfig: Default { - ///Describes mapping for mime type to content disposition header - /// - ///By default `IMAGE`, `TEXT` and `VIDEO` are mapped to Inline. - ///Others are mapped to Attachment - fn content_disposition_map(typ: mime::Name) -> DispositionType { - match typ { - mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, - _ => DispositionType::Attachment, - } - } - - ///Describes whether Actix should attempt to calculate `ETag` - /// - ///Defaults to `true` - fn is_use_etag() -> bool { - true - } - - ///Describes whether Actix should use last modified date of file. - /// - ///Defaults to `true` - fn is_use_last_modifier() -> bool { - true - } - - ///Describes allowed methods to access static resources. - /// - ///By default all methods are allowed - fn is_method_allowed(_method: &Method) -> bool { - true - } -} - -///Default content disposition as described in -///[StaticFileConfig](trait.StaticFileConfig.html) -#[derive(Default)] -pub struct DefaultConfig; -impl StaticFileConfig for DefaultConfig {} - -/// Return the MIME type associated with a filename extension (case-insensitive). -/// If `ext` is empty or no associated type for the extension was found, returns -/// the type `application/octet-stream`. -#[inline] -pub fn file_extension_to_mime(ext: &str) -> mime::Mime { - get_mime_type(ext) -} - -/// A file with an associated name. -#[derive(Debug)] -pub struct NamedFile { - path: PathBuf, - file: File, - content_type: mime::Mime, - content_disposition: header::ContentDisposition, - md: Metadata, - modified: Option, - cpu_pool: Option, - encoding: Option, - status_code: StatusCode, - _cd_map: PhantomData, -} - -impl NamedFile { - /// Attempts to open a file in read-only mode. - /// - /// # Examples - /// - /// ```rust - /// use actix_web::fs::NamedFile; - /// - /// let file = NamedFile::open("foo.txt"); - /// ``` - pub fn open>(path: P) -> io::Result { - Self::open_with_config(path, DefaultConfig) - } -} - -impl NamedFile { - /// Attempts to open a file in read-only mode using provided configiration. - /// - /// # Examples - /// - /// ```rust - /// use actix_web::fs::{DefaultConfig, NamedFile}; - /// - /// let file = NamedFile::open_with_config("foo.txt", DefaultConfig); - /// ``` - pub fn open_with_config>(path: P, _: C) -> io::Result> { - let path = path.as_ref().to_path_buf(); - - // Get the name of the file and use it to construct default Content-Type - // and Content-Disposition values - let (content_type, content_disposition) = { - let filename = match path.file_name() { - Some(name) => name.to_string_lossy(), - None => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Provided path has no filename", - )) - } - }; - - let ct = guess_mime_type(&path); - let disposition_type = C::content_disposition_map(ct.type_()); - let cd = ContentDisposition { - disposition: disposition_type, - parameters: vec![DispositionParam::Filename(filename.into_owned())], - }; - (ct, cd) - }; - - let file = File::open(&path)?; - let md = file.metadata()?; - let modified = md.modified().ok(); - let cpu_pool = None; - let encoding = None; - Ok(NamedFile { - path, - file, - content_type, - content_disposition, - md, - modified, - cpu_pool, - encoding, - status_code: StatusCode::OK, - _cd_map: PhantomData, - }) - } - - /// Returns reference to the underlying `File` object. - #[inline] - pub fn file(&self) -> &File { - &self.file - } - - /// Retrieve the path of this file. - /// - /// # Examples - /// - /// ```rust - /// # use std::io; - /// use actix_web::fs::NamedFile; - /// - /// # fn path() -> io::Result<()> { - /// let file = NamedFile::open("test.txt")?; - /// assert_eq!(file.path().as_os_str(), "foo.txt"); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn path(&self) -> &Path { - self.path.as_path() - } - - /// Set `CpuPool` to use - #[inline] - pub fn set_cpu_pool(mut self, cpu_pool: CpuPool) -> Self { - self.cpu_pool = Some(cpu_pool); - self - } - - /// Set response **Status Code** - pub fn set_status_code(mut self, status: StatusCode) -> Self { - self.status_code = status; - self - } - - /// Set the MIME Content-Type for serving this file. By default - /// the Content-Type is inferred from the filename extension. - #[inline] - pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self { - self.content_type = mime_type; - self - } - - /// Set the Content-Disposition for serving this file. This allows - /// changing the inline/attachment disposition as well as the filename - /// sent to the peer. By default the disposition is `inline` for text, - /// image, and video content types, and `attachment` otherwise, and - /// the filename is taken from the path provided in the `open` method - /// after converting it to UTF-8 using - /// [to_string_lossy](https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_string_lossy). - #[inline] - pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { - self.content_disposition = cd; - self - } - - /// Set content encoding for serving this file - #[inline] - pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self { - self.encoding = Some(enc); - self - } - - fn etag(&self) -> Option { - // This etag format is similar to Apache's. - self.modified.as_ref().map(|mtime| { - let ino = { - #[cfg(unix)] - { - self.md.ino() - } - #[cfg(not(unix))] - { - 0 - } - }; - - let dur = mtime - .duration_since(UNIX_EPOCH) - .expect("modification time must be after epoch"); - header::EntityTag::strong(format!( - "{:x}:{:x}:{:x}:{:x}", - ino, - self.md.len(), - dur.as_secs(), - dur.subsec_nanos() - )) - }) - } - - fn last_modified(&self) -> Option { - self.modified.map(|mtime| mtime.into()) - } -} - -impl Deref for NamedFile { - type Target = File; - - fn deref(&self) -> &File { - &self.file - } -} - -impl DerefMut for NamedFile { - fn deref_mut(&mut self) -> &mut File { - &mut self.file - } -} - -/// Returns true if `req` has no `If-Match` header or one which matches `etag`. -fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { - match req.get_header::() { - None | Some(header::IfMatch::Any) => true, - Some(header::IfMatch::Items(ref items)) => { - if let Some(some_etag) = etag { - for item in items { - if item.strong_eq(some_etag) { - return true; - } - } - } - false - } - } -} - -/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`. -fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { - match req.get_header::() { - Some(header::IfNoneMatch::Any) => false, - Some(header::IfNoneMatch::Items(ref items)) => { - if let Some(some_etag) = etag { - for item in items { - if item.weak_eq(some_etag) { - return false; - } - } - } - true - } - None => true, - } -} - -impl Responder for NamedFile { - type Item = HttpResponse; - type Error = io::Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - if self.status_code != StatusCode::OK { - let mut resp = HttpResponse::build(self.status_code); - resp.set(header::ContentType(self.content_type.clone())) - .header( - header::CONTENT_DISPOSITION, - self.content_disposition.to_string(), - ); - - if let Some(current_encoding) = self.encoding { - resp.content_encoding(current_encoding); - } - let reader = ChunkedReadFile { - size: self.md.len(), - offset: 0, - cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), - file: Some(self.file), - fut: None, - counter: 0, - }; - return Ok(resp.streaming(reader)); - } - - if !C::is_method_allowed(req.method()) { - return Ok(HttpResponse::MethodNotAllowed() - .header(header::CONTENT_TYPE, "text/plain") - .header(header::ALLOW, "GET, HEAD") - .body("This resource only supports GET and HEAD.")); - } - - let etag = if C::is_use_etag() { self.etag() } else { None }; - let last_modified = if C::is_use_last_modifier() { - self.last_modified() - } else { - None - }; - - // check preconditions - let precondition_failed = if !any_match(etag.as_ref(), req) { - true - } else if let (Some(ref m), Some(header::IfUnmodifiedSince(ref since))) = - (last_modified, req.get_header()) - { - m > since - } else { - false - }; - - // check last modified - let not_modified = if !none_match(etag.as_ref(), req) { - true - } else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) = - (last_modified, req.get_header()) - { - m <= since - } else { - false - }; - - let mut resp = HttpResponse::build(self.status_code); - resp.set(header::ContentType(self.content_type.clone())) - .header( - header::CONTENT_DISPOSITION, - self.content_disposition.to_string(), - ); - - if let Some(current_encoding) = self.encoding { - resp.content_encoding(current_encoding); - } - - resp.if_some(last_modified, |lm, resp| { - resp.set(header::LastModified(lm)); - }).if_some(etag, |etag, resp| { - resp.set(header::ETag(etag)); - }); - - resp.header(header::ACCEPT_RANGES, "bytes"); - - let mut length = self.md.len(); - let mut offset = 0; - - // check for range header - if let Some(ranges) = req.headers().get(header::RANGE) { - if let Ok(rangesheader) = ranges.to_str() { - if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) { - length = rangesvec[0].length; - offset = rangesvec[0].start; - resp.content_encoding(ContentEncoding::Identity); - resp.header( - header::CONTENT_RANGE, - format!( - "bytes {}-{}/{}", - offset, - offset + length - 1, - self.md.len() - ), - ); - } else { - resp.header(header::CONTENT_RANGE, format!("bytes */{}", length)); - return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish()); - }; - } else { - return Ok(resp.status(StatusCode::BAD_REQUEST).finish()); - }; - }; - - resp.header(header::CONTENT_LENGTH, format!("{}", length)); - - if precondition_failed { - return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); - } else if not_modified { - return Ok(resp.status(StatusCode::NOT_MODIFIED).finish()); - } - - if *req.method() == Method::HEAD { - Ok(resp.finish()) - } else { - let reader = ChunkedReadFile { - offset, - size: length, - cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), - file: Some(self.file), - fut: None, - counter: 0, - }; - if offset != 0 || length != self.md.len() { - return Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader)); - }; - Ok(resp.streaming(reader)) - } - } -} - -/// A helper created from a `std::fs::File` which reads the file -/// chunk-by-chunk on a `CpuPool`. -pub struct ChunkedReadFile { - size: u64, - offset: u64, - cpu_pool: CpuPool, - file: Option, - fut: Option>, - counter: u64, -} - -impl Stream for ChunkedReadFile { - type Item = Bytes; - type Error = Error; - - fn poll(&mut self) -> Poll, Error> { - if self.fut.is_some() { - return match self.fut.as_mut().unwrap().poll()? { - Async::Ready((file, bytes)) => { - self.fut.take(); - self.file = Some(file); - self.offset += bytes.len() as u64; - self.counter += bytes.len() as u64; - Ok(Async::Ready(Some(bytes))) - } - Async::NotReady => Ok(Async::NotReady), - }; - } - - let size = self.size; - let offset = self.offset; - let counter = self.counter; - - if size == counter { - Ok(Async::Ready(None)) - } else { - let mut file = self.file.take().expect("Use after completion"); - self.fut = Some(self.cpu_pool.spawn_fn(move || { - let max_bytes: usize; - max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; - let mut buf = Vec::with_capacity(max_bytes); - file.seek(io::SeekFrom::Start(offset))?; - let nbytes = - file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; - if nbytes == 0 { - return Err(io::ErrorKind::UnexpectedEof.into()); - } - Ok((file, Bytes::from(buf))) - })); - self.poll() - } - } -} - -type DirectoryRenderer = - Fn(&Directory, &HttpRequest) -> Result; - -/// A directory; responds with the generated directory listing. -#[derive(Debug)] -pub struct Directory { - /// Base directory - pub base: PathBuf, - /// Path of subdirectory to generate listing for - pub path: PathBuf, -} - -impl Directory { - /// Create a new directory - pub fn new(base: PathBuf, path: PathBuf) -> Directory { - Directory { base, path } - } - - /// Is this entry visible from this directory? - pub fn is_visible(&self, entry: &io::Result) -> bool { - if let Ok(ref entry) = *entry { - if let Some(name) = entry.file_name().to_str() { - if name.starts_with('.') { - return false; - } - } - if let Ok(ref md) = entry.metadata() { - let ft = md.file_type(); - return ft.is_dir() || ft.is_file() || ft.is_symlink(); - } - } - false - } -} - -fn directory_listing( - dir: &Directory, req: &HttpRequest, -) -> Result { - let index_of = format!("Index of {}", req.path()); - let mut body = String::new(); - let base = Path::new(req.path()); - - for entry in dir.path.read_dir()? { - if dir.is_visible(&entry) { - let entry = entry.unwrap(); - let p = match entry.path().strip_prefix(&dir.path) { - Ok(p) => base.join(p), - Err(_) => continue, - }; - // show file url as relative to static path - let file_url = utf8_percent_encode(&p.to_string_lossy(), DEFAULT_ENCODE_SET) - .to_string(); - // " -- " & -- & ' -- ' < -- < > -- > - let file_name = escape_html_entity(&entry.file_name().to_string_lossy()); - - // if file is a directory, add '/' to the end of the name - if let Ok(metadata) = entry.metadata() { - if metadata.is_dir() { - let _ = write!( - body, - "
  • {}/
  • ", - file_url, file_name - ); - } else { - let _ = write!( - body, - "
  • {}
  • ", - file_url, file_name - ); - } - } else { - continue; - } - } - } - - let html = format!( - "\ - {}\ -

    {}

    \ -
      \ - {}\ -
    \n", - index_of, index_of, body - ); - Ok(HttpResponse::Ok() - .content_type("text/html; charset=utf-8") - .body(html)) -} - -/// Static files handling -/// -/// `StaticFile` handler must be registered with `App::handler()` method, -/// because `StaticFile` handler requires access sub-path information. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{fs, App}; -/// -/// fn main() { -/// let app = App::new() -/// .handler("/static", fs::StaticFiles::new(".").unwrap()) -/// .finish(); -/// } -/// ``` -pub struct StaticFiles { - directory: PathBuf, - index: Option, - show_index: bool, - cpu_pool: CpuPool, - default: Box>, - renderer: Box>, - _chunk_size: usize, - _follow_symlinks: bool, - _cd_map: PhantomData, -} - -impl StaticFiles { - /// Create new `StaticFiles` instance for specified base directory. - /// - /// `StaticFile` uses `CpuPool` for blocking filesystem operations. - /// By default pool with 20 threads is used. - /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable. - pub fn new>(dir: T) -> Result, Error> { - Self::with_config(dir, DefaultConfig) - } - - /// Create new `StaticFiles` instance for specified base directory and - /// `CpuPool`. - pub fn with_pool>( - dir: T, pool: CpuPool, - ) -> Result, Error> { - Self::with_config_pool(dir, pool, DefaultConfig) - } -} - -impl StaticFiles { - /// Create new `StaticFiles` instance for specified base directory. - /// - /// Identical with `new` but allows to specify configiration to use. - pub fn with_config>( - dir: T, config: C, - ) -> Result, Error> { - // use default CpuPool - let pool = { DEFAULT_CPUPOOL.lock().clone() }; - - StaticFiles::with_config_pool(dir, pool, config) - } - - /// Create new `StaticFiles` instance for specified base directory with config and - /// `CpuPool`. - pub fn with_config_pool>( - dir: T, pool: CpuPool, _: C, - ) -> Result, Error> { - let dir = dir.into().canonicalize()?; - - if !dir.is_dir() { - return Err(StaticFileError::IsNotDirectory.into()); - } - - Ok(StaticFiles { - directory: dir, - index: None, - show_index: false, - cpu_pool: pool, - default: Box::new(WrapHandler::new(|_: &_| { - HttpResponse::new(StatusCode::NOT_FOUND) - })), - renderer: Box::new(directory_listing), - _chunk_size: 0, - _follow_symlinks: false, - _cd_map: PhantomData, - }) - } - - /// Show files listing for directories. - /// - /// By default show files listing is disabled. - pub fn show_files_listing(mut self) -> Self { - self.show_index = true; - self - } - - /// Set custom directory renderer - pub fn files_listing_renderer(mut self, f: F) -> Self - where - for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) - -> Result - + 'static, - { - self.renderer = Box::new(f); - self - } - - /// Set index file - /// - /// Redirects to specific index file for directory "/" instead of - /// showing files listing. - pub fn index_file>(mut self, index: T) -> StaticFiles { - self.index = Some(index.into()); - self - } - - /// Sets default handler which is used when no matched file could be found. - pub fn default_handler>(mut self, handler: H) -> StaticFiles { - self.default = Box::new(WrapHandler::new(handler)); - self - } - - fn try_handle( - &self, req: &HttpRequest, - ) -> Result, Error> { - let tail: String = req.match_info().query("tail")?; - let relpath = PathBuf::from_param(tail.trim_left_matches('/'))?; - - // full filepath - let path = self.directory.join(&relpath).canonicalize()?; - - if path.is_dir() { - if let Some(ref redir_index) = self.index { - // TODO: Don't redirect, just return the index content. - // TODO: It'd be nice if there were a good usable URL manipulation - // library - let mut new_path: String = req.path().to_owned(); - if !new_path.ends_with('/') { - new_path.push('/'); - } - new_path.push_str(redir_index); - HttpResponse::Found() - .header(header::LOCATION, new_path.as_str()) - .finish() - .respond_to(&req) - } else if self.show_index { - let dir = Directory::new(self.directory.clone(), path); - Ok((*self.renderer)(&dir, &req)?.into()) - } else { - Err(StaticFileError::IsDirectory.into()) - } - } else { - NamedFile::open_with_config(path, C::default())? - .set_cpu_pool(self.cpu_pool.clone()) - .respond_to(&req)? - .respond_to(&req) - } - } -} - -impl Handler for StaticFiles { - type Result = Result, Error>; - - fn handle(&self, req: &HttpRequest) -> Self::Result { - self.try_handle(req).or_else(|e| { - debug!("StaticFiles: Failed to handle {}: {}", req.path(), e); - Ok(self.default.handle(req)) - }) - } -} - -/// HTTP Range header representation. -#[derive(Debug, Clone, Copy)] -struct HttpRange { - pub start: u64, - pub length: u64, -} - -static PREFIX: &'static str = "bytes="; -const PREFIX_LEN: usize = 6; - -impl HttpRange { - /// Parses Range HTTP header string as per RFC 2616. - /// - /// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`). - /// `size` is full size of response (file). - fn parse(header: &str, size: u64) -> Result, ()> { - if header.is_empty() { - return Ok(Vec::new()); - } - if !header.starts_with(PREFIX) { - return Err(()); - } - - let size_sig = size as i64; - let mut no_overlap = false; - - let all_ranges: Vec> = header[PREFIX_LEN..] - .split(',') - .map(|x| x.trim()) - .filter(|x| !x.is_empty()) - .map(|ra| { - let mut start_end_iter = ra.split('-'); - - let start_str = start_end_iter.next().ok_or(())?.trim(); - let end_str = start_end_iter.next().ok_or(())?.trim(); - - if start_str.is_empty() { - // If no start is specified, end specifies the - // range start relative to the end of the file. - let mut length: i64 = try!(end_str.parse().map_err(|_| ())); - - if length > size_sig { - length = size_sig; - } - - Ok(Some(HttpRange { - start: (size_sig - length) as u64, - length: length as u64, - })) - } else { - let start: i64 = start_str.parse().map_err(|_| ())?; - - if start < 0 { - return Err(()); - } - if start >= size_sig { - no_overlap = true; - return Ok(None); - } - - let length = if end_str.is_empty() { - // If no end is specified, range extends to end of the file. - size_sig - start - } else { - let mut end: i64 = end_str.parse().map_err(|_| ())?; - - if start > end { - return Err(()); - } - - if end >= size_sig { - end = size_sig - 1; - } - - end - start + 1 - }; - - Ok(Some(HttpRange { - start: start as u64, - length: length as u64, - })) - } - }).collect::>()?; - - let ranges: Vec = all_ranges.into_iter().filter_map(|x| x).collect(); - - if no_overlap && ranges.is_empty() { - return Err(()); - } - - Ok(ranges) - } -} - -#[cfg(test)] -mod tests { - use std::fs; - - use super::*; - use application::App; - use body::{Binary, Body}; - use http::{header, Method, StatusCode}; - use test::{self, TestRequest}; - - #[test] - fn test_file_extension_to_mime() { - let m = file_extension_to_mime("jpg"); - assert_eq!(m, mime::IMAGE_JPEG); - - let m = file_extension_to_mime("invalid extension!!"); - assert_eq!(m, mime::APPLICATION_OCTET_STREAM); - - let m = file_extension_to_mime(""); - assert_eq!(m, mime::APPLICATION_OCTET_STREAM); - } - - #[test] - fn test_named_file_text() { - assert!(NamedFile::open("test--").is_err()); - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - } - - #[test] - fn test_named_file_set_content_type() { - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_content_type(mime::TEXT_XML) - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/xml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - } - - #[test] - fn test_named_file_image() { - let mut file = NamedFile::open("tests/test.png") - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"test.png\"" - ); - } - - #[test] - fn test_named_file_image_attachment() { - use header::{ContentDisposition, DispositionParam, DispositionType}; - let cd = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Filename(String::from("test.png"))], - }; - let mut file = NamedFile::open("tests/test.png") - .unwrap() - .set_content_disposition(cd) - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.png\"" - ); - } - - #[derive(Default)] - pub struct AllAttachmentConfig; - impl StaticFileConfig for AllAttachmentConfig { - fn content_disposition_map(_typ: mime::Name) -> DispositionType { - DispositionType::Attachment - } - } - - #[derive(Default)] - pub struct AllInlineConfig; - impl StaticFileConfig for AllInlineConfig { - fn content_disposition_map(_typ: mime::Name) -> DispositionType { - DispositionType::Inline - } - } - - #[test] - fn test_named_file_image_attachment_and_custom_config() { - let file = NamedFile::open_with_config("tests/test.png", AllAttachmentConfig) - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.png\"" - ); - - let file = NamedFile::open_with_config("tests/test.png", AllInlineConfig) - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"test.png\"" - ); - } - - #[test] - fn test_named_file_binary() { - let mut file = NamedFile::open("tests/test.binary") - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "application/octet-stream" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.binary\"" - ); - } - - #[test] - fn test_named_file_status_code_text() { - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_status_code(StatusCode::NOT_FOUND) - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_named_file_ranges_status_code() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".").unwrap().index_file("Cargo.toml"), - ) - }); - - // Valid range header - let request = srv - .get() - .uri(srv.url("/t%65st/Cargo.toml")) - .header(header::RANGE, "bytes=10-20") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); - - // Invalid range header - let request = srv - .get() - .uri(srv.url("/t%65st/Cargo.toml")) - .header(header::RANGE, "bytes=1-0") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - - assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); - } - - #[test] - fn test_named_file_content_range_headers() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".") - .unwrap() - .index_file("tests/test.binary"), - ) - }); - - // Valid range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-20") - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentrange = response - .headers() - .get(header::CONTENT_RANGE) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentrange, "bytes 10-20/100"); - - // Invalid range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-5") - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentrange = response - .headers() - .get(header::CONTENT_RANGE) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentrange, "bytes */100"); - } - - #[test] - fn test_named_file_content_length_headers() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".") - .unwrap() - .index_file("tests/test.binary"), - ) - }); - - // Valid range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-20") - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentlength = response - .headers() - .get(header::CONTENT_LENGTH) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentlength, "11"); - - // Invalid range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-8") - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentlength = response - .headers() - .get(header::CONTENT_LENGTH) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentlength, "0"); - - // Without range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .no_default_headers() - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentlength = response - .headers() - .get(header::CONTENT_LENGTH) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentlength, "100"); - - // chunked - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - { - let te = response - .headers() - .get(header::TRANSFER_ENCODING) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(te, "chunked"); - } - let bytes = srv.execute(response.body()).unwrap(); - let data = Bytes::from(fs::read("tests/test.binary").unwrap()); - assert_eq!(bytes, data); - } - - #[derive(Default)] - pub struct OnlyMethodHeadConfig; - impl StaticFileConfig for OnlyMethodHeadConfig { - fn is_method_allowed(method: &Method) -> bool { - match *method { - Method::HEAD => true, - _ => false, - } - } - } - - #[test] - fn test_named_file_not_allowed() { - let file = - NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); - let req = TestRequest::default().method(Method::POST).finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let file = - NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); - let req = TestRequest::default().method(Method::PUT).finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let file = - NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); - let req = TestRequest::default().method(Method::GET).finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - } - - #[test] - fn test_named_file_content_encoding() { - let req = TestRequest::default().method(Method::GET).finish(); - let file = NamedFile::open("Cargo.toml").unwrap(); - - assert!(file.encoding.is_none()); - let resp = file - .set_content_encoding(ContentEncoding::Identity) - .respond_to(&req) - .unwrap(); - - assert!(resp.content_encoding().is_some()); - assert_eq!(resp.content_encoding().unwrap().as_str(), "identity"); - } - - #[test] - fn test_named_file_any_method() { - let req = TestRequest::default().method(Method::POST).finish(); - let file = NamedFile::open("Cargo.toml").unwrap(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[test] - fn test_static_files() { - let mut st = StaticFiles::new(".").unwrap().show_files_listing(); - let req = TestRequest::with_uri("/missing") - .param("tail", "missing") - .finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - st.show_index = false; - let req = TestRequest::default().finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let req = TestRequest::default().param("tail", "").finish(); - - st.show_index = true; - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/html; charset=utf-8" - ); - assert!(resp.body().is_binary()); - assert!(format!("{:?}", resp.body()).contains("README.md")); - } - - #[test] - fn test_static_files_bad_directory() { - let st: Result, Error> = StaticFiles::new("missing"); - assert!(st.is_err()); - - let st: Result, Error> = StaticFiles::new("Cargo.toml"); - assert!(st.is_err()); - } - - #[test] - fn test_default_handler_file_missing() { - let st = StaticFiles::new(".") - .unwrap() - .default_handler(|_: &_| "default content"); - let req = TestRequest::with_uri("/missing") - .param("tail", "missing") - .finish(); - - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body(), - &Body::Binary(Binary::Slice(b"default content")) - ); - } - - #[test] - fn test_redirect_to_index() { - let st = StaticFiles::new(".").unwrap().index_file("index.html"); - let req = TestRequest::default().uri("/tests").finish(); - - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!( - resp.headers().get(header::LOCATION).unwrap(), - "/tests/index.html" - ); - - let req = TestRequest::default().uri("/tests/").finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!( - resp.headers().get(header::LOCATION).unwrap(), - "/tests/index.html" - ); - } - - #[test] - fn test_redirect_to_index_nested() { - let st = StaticFiles::new(".").unwrap().index_file("mod.rs"); - let req = TestRequest::default().uri("/src/client").finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!( - resp.headers().get(header::LOCATION).unwrap(), - "/src/client/mod.rs" - ); - } - - #[test] - fn integration_redirect_to_index_with_prefix() { - let mut srv = test::TestServer::with_factory(|| { - App::new() - .prefix("public") - .handler("/", StaticFiles::new(".").unwrap().index_file("Cargo.toml")) - }); - - let request = srv.get().uri(srv.url("/public")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(loc, "/public/Cargo.toml"); - - let request = srv.get().uri(srv.url("/public/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(loc, "/public/Cargo.toml"); - } - - #[test] - fn integration_redirect_to_index() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".").unwrap().index_file("Cargo.toml"), - ) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(loc, "/test/Cargo.toml"); - - let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(loc, "/test/Cargo.toml"); - } - - #[test] - fn integration_percent_encoded() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".").unwrap().index_file("Cargo.toml"), - ) - }); - - let request = srv - .get() - .uri(srv.url("/test/%43argo.toml")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - } - - struct T(&'static str, u64, Vec); - - #[test] - fn test_parse() { - let tests = vec![ - T("", 0, vec![]), - T("", 1000, vec![]), - T("foo", 0, vec![]), - T("bytes=", 0, vec![]), - T("bytes=7", 10, vec![]), - T("bytes= 7 ", 10, vec![]), - T("bytes=1-", 0, vec![]), - T("bytes=5-4", 10, vec![]), - T("bytes=0-2,5-4", 10, vec![]), - T("bytes=2-5,4-3", 10, vec![]), - T("bytes=--5,4--3", 10, vec![]), - T("bytes=A-", 10, vec![]), - T("bytes=A- ", 10, vec![]), - T("bytes=A-Z", 10, vec![]), - T("bytes= -Z", 10, vec![]), - T("bytes=5-Z", 10, vec![]), - T("bytes=Ran-dom, garbage", 10, vec![]), - T("bytes=0x01-0x02", 10, vec![]), - T("bytes= ", 10, vec![]), - T("bytes= , , , ", 10, vec![]), - T( - "bytes=0-9", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=0-", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=5-", - 10, - vec![HttpRange { - start: 5, - length: 5, - }], - ), - T( - "bytes=0-20", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=15-,0-5", - 10, - vec![HttpRange { - start: 0, - length: 6, - }], - ), - T( - "bytes=1-2,5-", - 10, - vec![ - HttpRange { - start: 1, - length: 2, - }, - HttpRange { - start: 5, - length: 5, - }, - ], - ), - T( - "bytes=-2 , 7-", - 11, - vec![ - HttpRange { - start: 9, - length: 2, - }, - HttpRange { - start: 7, - length: 4, - }, - ], - ), - T( - "bytes=0-0 ,2-2, 7-", - 11, - vec![ - HttpRange { - start: 0, - length: 1, - }, - HttpRange { - start: 2, - length: 1, - }, - HttpRange { - start: 7, - length: 4, - }, - ], - ), - T( - "bytes=-5", - 10, - vec![HttpRange { - start: 5, - length: 5, - }], - ), - T( - "bytes=-15", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=0-499", - 10000, - vec![HttpRange { - start: 0, - length: 500, - }], - ), - T( - "bytes=500-999", - 10000, - vec![HttpRange { - start: 500, - length: 500, - }], - ), - T( - "bytes=-500", - 10000, - vec![HttpRange { - start: 9500, - length: 500, - }], - ), - T( - "bytes=9500-", - 10000, - vec![HttpRange { - start: 9500, - length: 500, - }], - ), - T( - "bytes=0-0,-1", - 10000, - vec![ - HttpRange { - start: 0, - length: 1, - }, - HttpRange { - start: 9999, - length: 1, - }, - ], - ), - T( - "bytes=500-600,601-999", - 10000, - vec![ - HttpRange { - start: 500, - length: 101, - }, - HttpRange { - start: 601, - length: 399, - }, - ], - ), - T( - "bytes=500-700,601-999", - 10000, - vec![ - HttpRange { - start: 500, - length: 201, - }, - HttpRange { - start: 601, - length: 399, - }, - ], - ), - // Match Apache laxity: - T( - "bytes= 1 -2 , 4- 5, 7 - 8 , ,,", - 11, - vec![ - HttpRange { - start: 1, - length: 2, - }, - HttpRange { - start: 4, - length: 2, - }, - HttpRange { - start: 7, - length: 2, - }, - ], - ), - ]; - - for t in tests { - let header = t.0; - let size = t.1; - let expected = t.2; - - let res = HttpRange::parse(header, size); - - if res.is_err() { - if expected.is_empty() { - continue; - } else { - assert!( - false, - "parse({}, {}) returned error {:?}", - header, - size, - res.unwrap_err() - ); - } - } - - let got = res.unwrap(); - - if got.len() != expected.len() { - assert!( - false, - "len(parseRange({}, {})) = {}, want {}", - header, - size, - got.len(), - expected.len() - ); - continue; - } - - for i in 0..expected.len() { - if got[i].start != expected[i].start { - assert!( - false, - "parseRange({}, {})[{}].start = {}, want {}", - header, size, i, got[i].start, expected[i].start - ) - } - if got[i].length != expected[i].length { - assert!( - false, - "parseRange({}, {})[{}].length = {}, want {}", - header, size, i, got[i].length, expected[i].length - ) - } - } - } - } -} diff --git a/src/handler.rs b/src/handler.rs deleted file mode 100644 index 88210fbc0..000000000 --- a/src/handler.rs +++ /dev/null @@ -1,562 +0,0 @@ -use std::marker::PhantomData; -use std::ops::Deref; - -use futures::future::{err, ok, Future}; -use futures::{Async, Poll}; - -use error::Error; -use http::StatusCode; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use resource::DefaultResource; - -/// Trait defines object that could be registered as route handler -#[allow(unused_variables)] -pub trait Handler: 'static { - /// The type of value that handler will return. - type Result: Responder; - - /// Handle request - fn handle(&self, req: &HttpRequest) -> Self::Result; -} - -/// Trait implemented by types that generate responses for clients. -/// -/// Types that implement this trait can be used as the return type of a handler. -pub trait Responder { - /// The associated item which can be returned. - type Item: Into>; - - /// The associated error which can be returned. - type Error: Into; - - /// Convert itself to `AsyncResult` or `Error`. - fn respond_to( - self, req: &HttpRequest, - ) -> Result; -} - -/// Trait implemented by types that can be extracted from request. -/// -/// Types that implement this trait can be used with `Route::with()` method. -pub trait FromRequest: Sized { - /// Configuration for conversion process - type Config: Default; - - /// Future that resolves to a Self - type Result: Into>; - - /// Convert request to a Self - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result; - - /// Convert request to a Self - /// - /// This method uses default extractor configuration - fn extract(req: &HttpRequest) -> Self::Result { - Self::from_request(req, &Self::Config::default()) - } -} - -/// Combines two different responder types into a single type -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate futures; -/// # use futures::future::Future; -/// use actix_web::{AsyncResponder, Either, Error, HttpRequest, HttpResponse}; -/// use futures::future::result; -/// -/// type RegisterResult = -/// Either>>; -/// -/// fn index(req: HttpRequest) -> RegisterResult { -/// if is_a_variant() { -/// // <- choose variant A -/// Either::A(HttpResponse::BadRequest().body("Bad data")) -/// } else { -/// Either::B( -/// // <- variant B -/// result(Ok(HttpResponse::Ok() -/// .content_type("text/html") -/// .body("Hello!"))) -/// .responder(), -/// ) -/// } -/// } -/// # fn is_a_variant() -> bool { true } -/// # fn main() {} -/// ``` -#[derive(Debug)] -pub enum Either { - /// First branch of the type - A(A), - /// Second branch of the type - B(B), -} - -impl Responder for Either -where - A: Responder, - B: Responder, -{ - type Item = AsyncResult; - type Error = Error; - - fn respond_to( - self, req: &HttpRequest, - ) -> Result, Error> { - match self { - Either::A(a) => match a.respond_to(req) { - Ok(val) => Ok(val.into()), - Err(err) => Err(err.into()), - }, - Either::B(b) => match b.respond_to(req) { - Ok(val) => Ok(val.into()), - Err(err) => Err(err.into()), - }, - } - } -} - -impl Future for Either -where - A: Future, - B: Future, -{ - type Item = I; - type Error = E; - - fn poll(&mut self) -> Poll { - match *self { - Either::A(ref mut fut) => fut.poll(), - Either::B(ref mut fut) => fut.poll(), - } - } -} - -impl Responder for Option -where - T: Responder, -{ - type Item = AsyncResult; - type Error = Error; - - fn respond_to( - self, req: &HttpRequest, - ) -> Result, Error> { - match self { - Some(t) => match t.respond_to(req) { - Ok(val) => Ok(val.into()), - Err(err) => Err(err.into()), - }, - None => Ok(req.build_response(StatusCode::NOT_FOUND).finish().into()), - } - } -} - -/// Convenience trait that converts `Future` object to a `Boxed` future -/// -/// For example loading json from request's body is async operation. -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate futures; -/// # #[macro_use] extern crate serde_derive; -/// use actix_web::{ -/// App, AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse, -/// }; -/// use futures::future::Future; -/// -/// #[derive(Deserialize, Debug)] -/// struct MyObj { -/// name: String, -/// } -/// -/// fn index(mut req: HttpRequest) -> Box> { -/// req.json() // <- get JsonBody future -/// .from_err() -/// .and_then(|val: MyObj| { // <- deserialized value -/// Ok(HttpResponse::Ok().into()) -/// }) -/// // Construct boxed future by using `AsyncResponder::responder()` method -/// .responder() -/// } -/// # fn main() {} -/// ``` -pub trait AsyncResponder: Sized { - /// Convert to a boxed future - fn responder(self) -> Box>; -} - -impl AsyncResponder for F -where - F: Future + 'static, - I: Responder + 'static, - E: Into + 'static, -{ - fn responder(self) -> Box> { - Box::new(self) - } -} - -/// Handler for Fn() -impl Handler for F -where - F: Fn(&HttpRequest) -> R + 'static, - R: Responder + 'static, -{ - type Result = R; - - fn handle(&self, req: &HttpRequest) -> R { - (self)(req) - } -} - -/// Represents async result -/// -/// Result could be in tree different forms. -/// * Ok(T) - ready item -/// * Err(E) - error happen during reply process -/// * Future - reply process completes in the future -pub struct AsyncResult(Option>); - -impl Future for AsyncResult { - type Item = I; - type Error = E; - - fn poll(&mut self) -> Poll { - let res = self.0.take().expect("use after resolve"); - match res { - AsyncResultItem::Ok(msg) => Ok(Async::Ready(msg)), - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Future(mut fut) => match fut.poll() { - Ok(Async::NotReady) => { - self.0 = Some(AsyncResultItem::Future(fut)); - Ok(Async::NotReady) - } - Ok(Async::Ready(msg)) => Ok(Async::Ready(msg)), - Err(err) => Err(err), - }, - } - } -} - -pub(crate) enum AsyncResultItem { - Ok(I), - Err(E), - Future(Box>), -} - -impl AsyncResult { - /// Create async response - #[inline] - pub fn async(fut: Box>) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Future(fut))) - } - - /// Send response - #[inline] - pub fn ok>(ok: R) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Ok(ok.into()))) - } - - /// Send error - #[inline] - pub fn err>(err: R) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Err(err.into()))) - } - - #[inline] - pub(crate) fn into(self) -> AsyncResultItem { - self.0.expect("use after resolve") - } - - #[cfg(test)] - pub(crate) fn as_msg(&self) -> &I { - match self.0.as_ref().unwrap() { - &AsyncResultItem::Ok(ref resp) => resp, - _ => panic!(), - } - } - - #[cfg(test)] - pub(crate) fn as_err(&self) -> Option<&E> { - match self.0.as_ref().unwrap() { - &AsyncResultItem::Err(ref err) => Some(err), - _ => None, - } - } -} - -impl Responder for AsyncResult { - type Item = AsyncResult; - type Error = Error; - - fn respond_to( - self, _: &HttpRequest, - ) -> Result, Error> { - Ok(self) - } -} - -impl Responder for HttpResponse { - type Item = AsyncResult; - type Error = Error; - - #[inline] - fn respond_to( - self, _: &HttpRequest, - ) -> Result, Error> { - Ok(AsyncResult(Some(AsyncResultItem::Ok(self)))) - } -} - -impl From for AsyncResult { - #[inline] - fn from(resp: T) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Ok(resp))) - } -} - -impl> Responder for Result { - type Item = ::Item; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - match self { - Ok(val) => match val.respond_to(req) { - Ok(val) => Ok(val), - Err(err) => Err(err.into()), - }, - Err(err) => Err(err.into()), - } - } -} - -impl> From, E>> for AsyncResult { - #[inline] - fn from(res: Result, E>) -> Self { - match res { - Ok(val) => val, - Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), - } - } -} - -impl> From> for AsyncResult { - #[inline] - fn from(res: Result) -> Self { - match res { - Ok(val) => AsyncResult(Some(AsyncResultItem::Ok(val))), - Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), - } - } -} - -impl From>, E>> for AsyncResult -where - T: 'static, - E: Into + 'static, -{ - #[inline] - fn from(res: Result>, E>) -> Self { - match res { - Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(Box::new( - fut.map_err(|e| e.into()), - )))), - Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), - } - } -} - -impl From>> for AsyncResult { - #[inline] - fn from(fut: Box>) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Future(fut))) - } -} - -/// Convenience type alias -pub type FutureResponse = Box>; - -impl Responder for Box> -where - I: Responder + 'static, - E: Into + 'static, -{ - type Item = AsyncResult; - type Error = Error; - - #[inline] - fn respond_to( - self, req: &HttpRequest, - ) -> Result, Error> { - let req = req.clone(); - let fut = self - .map_err(|e| e.into()) - .then(move |r| match r.respond_to(&req) { - Ok(reply) => match reply.into().into() { - AsyncResultItem::Ok(resp) => ok(resp), - _ => panic!("Nested async replies are not supported"), - }, - Err(e) => err(e), - }); - Ok(AsyncResult::async(Box::new(fut))) - } -} - -pub(crate) trait RouteHandler: 'static { - fn handle(&self, &HttpRequest) -> AsyncResult; - - fn has_default_resource(&self) -> bool { - false - } - - fn default_resource(&mut self, _: DefaultResource) {} - - fn finish(&mut self) {} -} - -/// Route handler wrapper for Handler -pub(crate) struct WrapHandler -where - H: Handler, - R: Responder, - S: 'static, -{ - h: H, - s: PhantomData, -} - -impl WrapHandler -where - H: Handler, - R: Responder, - S: 'static, -{ - pub fn new(h: H) -> Self { - WrapHandler { h, s: PhantomData } - } -} - -impl RouteHandler for WrapHandler -where - H: Handler, - R: Responder + 'static, - S: 'static, -{ - fn handle(&self, req: &HttpRequest) -> AsyncResult { - match self.h.handle(req).respond_to(req) { - Ok(reply) => reply.into(), - Err(err) => AsyncResult::err(err.into()), - } - } -} - -/// Async route handler -pub(crate) struct AsyncHandler -where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - S: 'static, -{ - h: Box, - s: PhantomData, -} - -impl AsyncHandler -where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - S: 'static, -{ - pub fn new(h: H) -> Self { - AsyncHandler { - h: Box::new(h), - s: PhantomData, - } - } -} - -impl RouteHandler for AsyncHandler -where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - S: 'static, -{ - fn handle(&self, req: &HttpRequest) -> AsyncResult { - let req = req.clone(); - let fut = (self.h)(&req).map_err(|e| e.into()).then(move |r| { - match r.respond_to(&req) { - Ok(reply) => match reply.into().into() { - AsyncResultItem::Ok(resp) => Either::A(ok(resp)), - AsyncResultItem::Err(e) => Either::A(err(e)), - AsyncResultItem::Future(fut) => Either::B(fut), - }, - Err(e) => Either::A(err(e)), - } - }); - AsyncResult::async(Box::new(fut)) - } -} - -/// Access an application state -/// -/// `S` - application state type -/// -/// ## Example -/// -/// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Path, State}; -/// -/// /// Application state -/// struct MyApp { -/// msg: &'static str, -/// } -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// extract path info using serde -/// fn index(state: State, path: Path) -> String { -/// format!("{} {}!", state.msg, path.username) -/// } -/// -/// fn main() { -/// let app = App::with_state(MyApp { msg: "Welcome" }).resource( -/// "/{username}/index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with(index), -/// ); // <- use `with` extractor -/// } -/// ``` -pub struct State(HttpRequest); - -impl Deref for State { - type Target = S; - - fn deref(&self) -> &S { - self.0.state() - } -} - -impl FromRequest for State { - type Config = (); - type Result = State; - - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - State(req.clone()) - } -} diff --git a/src/helpers.rs b/src/helpers.rs deleted file mode 100644 index e82d61616..000000000 --- a/src/helpers.rs +++ /dev/null @@ -1,571 +0,0 @@ -//! Various helpers - -use http::{header, StatusCode}; -use regex::Regex; - -use handler::Handler; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; - -/// Path normalization helper -/// -/// By normalizing it means: -/// -/// - Add a trailing slash to the path. -/// - Remove a trailing slash from the path. -/// - Double slashes are replaced by one. -/// -/// The handler returns as soon as it finds a path that resolves -/// correctly. The order if all enable is 1) merge, 3) both merge and append -/// and 3) append. If the path resolves with -/// at least one of those conditions, it will redirect to the new path. -/// -/// If *append* is *true* append slash when needed. If a resource is -/// defined with trailing slash and the request comes without it, it will -/// append it automatically. -/// -/// If *merge* is *true*, merge multiple consecutive slashes in the path into -/// one. -/// -/// This handler designed to be use as a handler for application's *default -/// resource*. -/// -/// ```rust -/// # extern crate actix_web; -/// # #[macro_use] extern crate serde_derive; -/// # use actix_web::*; -/// use actix_web::http::NormalizePath; -/// -/// # fn index(req: &HttpRequest) -> HttpResponse { -/// # HttpResponse::Ok().into() -/// # } -/// fn main() { -/// let app = App::new() -/// .resource("/test/", |r| r.f(index)) -/// .default_resource(|r| r.h(NormalizePath::default())) -/// .finish(); -/// } -/// ``` -/// In this example `/test`, `/test///` will be redirected to `/test/` url. -pub struct NormalizePath { - append: bool, - merge: bool, - re_merge: Regex, - redirect: StatusCode, - not_found: StatusCode, -} - -impl Default for NormalizePath { - /// Create default `NormalizePath` instance, *append* is set to *true*, - /// *merge* is set to *true* and *redirect* is set to - /// `StatusCode::MOVED_PERMANENTLY` - fn default() -> NormalizePath { - NormalizePath { - append: true, - merge: true, - re_merge: Regex::new("//+").unwrap(), - redirect: StatusCode::MOVED_PERMANENTLY, - not_found: StatusCode::NOT_FOUND, - } - } -} - -impl NormalizePath { - /// Create new `NormalizePath` instance - pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath { - NormalizePath { - append, - merge, - redirect, - re_merge: Regex::new("//+").unwrap(), - not_found: StatusCode::NOT_FOUND, - } - } -} - -impl Handler for NormalizePath { - type Result = HttpResponse; - - fn handle(&self, req: &HttpRequest) -> Self::Result { - let query = req.query_string(); - if self.merge { - // merge slashes - let p = self.re_merge.replace_all(req.path(), "/"); - if p.len() != req.path().len() { - if req.resource().has_prefixed_resource(p.as_ref()) { - let p = if !query.is_empty() { - p + "?" + query - } else { - p - }; - return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_ref()) - .finish(); - } - // merge slashes and append trailing slash - if self.append && !p.ends_with('/') { - let p = p.as_ref().to_owned() + "/"; - if req.resource().has_prefixed_resource(&p) { - let p = if !query.is_empty() { - p + "?" + query - } else { - p - }; - return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_str()) - .finish(); - } - } - - // try to remove trailing slash - if p.ends_with('/') { - let p = p.as_ref().trim_right_matches('/'); - if req.resource().has_prefixed_resource(p) { - let mut req = HttpResponse::build(self.redirect); - return if !query.is_empty() { - req.header( - header::LOCATION, - (p.to_owned() + "?" + query).as_str(), - ) - } else { - req.header(header::LOCATION, p) - }.finish(); - } - } - } else if p.ends_with('/') { - // try to remove trailing slash - let p = p.as_ref().trim_right_matches('/'); - if req.resource().has_prefixed_resource(p) { - let mut req = HttpResponse::build(self.redirect); - return if !query.is_empty() { - req.header( - header::LOCATION, - (p.to_owned() + "?" + query).as_str(), - ) - } else { - req.header(header::LOCATION, p) - }.finish(); - } - } - } - // append trailing slash - if self.append && !req.path().ends_with('/') { - let p = req.path().to_owned() + "/"; - if req.resource().has_prefixed_resource(&p) { - let p = if !query.is_empty() { - p + "?" + query - } else { - p - }; - return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_str()) - .finish(); - } - } - HttpResponse::new(self.not_found) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use application::App; - use http::{header, Method}; - use test::TestRequest; - - fn index(_req: &HttpRequest) -> HttpResponse { - HttpResponse::new(StatusCode::OK) - } - - #[test] - fn test_normalize_path_trailing_slashes() { - let app = App::new() - .resource("/resource1", |r| r.method(Method::GET).f(index)) - .resource("/resource2/", |r| r.method(Method::GET).f(index)) - .default_resource(|r| r.h(NormalizePath::default())) - .finish(); - - // trailing slashes - let params = vec![ - ("/resource1", "", StatusCode::OK), - ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY), - ("/resource2/", "", StatusCode::OK), - ("/resource1?p1=1&p2=2", "", StatusCode::OK), - ( - "/resource1/?p1=1&p2=2", - "/resource1?p1=1&p2=2", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/resource2?p1=1&p2=2", - "/resource2/?p1=1&p2=2", - StatusCode::MOVED_PERMANENTLY, - ), - ("/resource2/?p1=1&p2=2", "", StatusCode::OK), - ]; - for (path, target, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - assert_eq!(r.status(), code); - if !target.is_empty() { - assert_eq!( - target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap() - ); - } - } - } - - #[test] - fn test_prefixed_normalize_path_trailing_slashes() { - let app = App::new() - .prefix("/test") - .resource("/resource1", |r| r.method(Method::GET).f(index)) - .resource("/resource2/", |r| r.method(Method::GET).f(index)) - .default_resource(|r| r.h(NormalizePath::default())) - .finish(); - - // trailing slashes - let params = vec![ - ("/test/resource1", "", StatusCode::OK), - ( - "/test/resource1/", - "/test/resource1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/test/resource2", - "/test/resource2/", - StatusCode::MOVED_PERMANENTLY, - ), - ("/test/resource2/", "", StatusCode::OK), - ("/test/resource1?p1=1&p2=2", "", StatusCode::OK), - ( - "/test/resource1/?p1=1&p2=2", - "/test/resource1?p1=1&p2=2", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/test/resource2?p1=1&p2=2", - "/test/resource2/?p1=1&p2=2", - StatusCode::MOVED_PERMANENTLY, - ), - ("/test/resource2/?p1=1&p2=2", "", StatusCode::OK), - ]; - for (path, target, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - assert_eq!(r.status(), code); - if !target.is_empty() { - assert_eq!( - target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap() - ); - } - } - } - - #[test] - fn test_normalize_path_trailing_slashes_disabled() { - let app = App::new() - .resource("/resource1", |r| r.method(Method::GET).f(index)) - .resource("/resource2/", |r| r.method(Method::GET).f(index)) - .default_resource(|r| { - r.h(NormalizePath::new( - false, - true, - StatusCode::MOVED_PERMANENTLY, - )) - }).finish(); - - // trailing slashes - let params = vec![ - ("/resource1", StatusCode::OK), - ("/resource1/", StatusCode::MOVED_PERMANENTLY), - ("/resource2", StatusCode::NOT_FOUND), - ("/resource2/", StatusCode::OK), - ("/resource1?p1=1&p2=2", StatusCode::OK), - ("/resource1/?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY), - ("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND), - ("/resource2/?p1=1&p2=2", StatusCode::OK), - ]; - for (path, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - assert_eq!(r.status(), code); - } - } - - #[test] - fn test_normalize_path_merge_slashes() { - let app = App::new() - .resource("/resource1", |r| r.method(Method::GET).f(index)) - .resource("/resource1/a/b", |r| r.method(Method::GET).f(index)) - .default_resource(|r| r.h(NormalizePath::default())) - .finish(); - - // trailing slashes - let params = vec![ - ("/resource1/a/b", "", StatusCode::OK), - ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY), - ( - "//resource1//a//b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource1//a//b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource1//a//b//", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a//b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ("/resource1/a/b?p=1", "", StatusCode::OK), - ( - "//resource1//a//b?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource1//a//b/?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a//b/?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a//b//?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ]; - for (path, target, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - assert_eq!(r.status(), code); - if !target.is_empty() { - assert_eq!( - target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap() - ); - } - } - } - - #[test] - fn test_normalize_path_merge_and_append_slashes() { - let app = App::new() - .resource("/resource1", |r| r.method(Method::GET).f(index)) - .resource("/resource2/", |r| r.method(Method::GET).f(index)) - .resource("/resource1/a/b", |r| r.method(Method::GET).f(index)) - .resource("/resource2/a/b/", |r| r.method(Method::GET).f(index)) - .default_resource(|r| r.h(NormalizePath::default())) - .finish(); - - // trailing slashes - let params = vec![ - ("/resource1/a/b", "", StatusCode::OK), - ( - "/resource1/a/b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource2//a//b", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource2//a//b/", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource2//a//b//", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/resource2/a/b", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ("/resource2/a/b/", "", StatusCode::OK), - ( - "//resource2//a//b", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource2//a//b/", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource2//a//b", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource2//a//b/", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource2/a///b", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource2/a///b/", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ("/resource1/a/b?p=1", "", StatusCode::OK), - ( - "/resource1/a/b/?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource2//a//b?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource2//a//b/?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b/?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b/?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b//?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/resource2/a/b?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource2//a//b?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource2//a//b/?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource2//a//b?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource2//a//b/?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource2/a///b?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource2/a///b/?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ]; - for (path, target, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - assert_eq!(r.status(), code); - if !target.is_empty() { - assert_eq!( - target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap() - ); - } - } - } -} diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 60f77b07e..8c972bd13 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -15,7 +15,6 @@ use error::{ }; use header::Header; use json::JsonBody; -use multipart::Multipart; /// Trait that implements general purpose operations on http messages pub trait HttpMessage: Sized { @@ -203,46 +202,6 @@ pub trait HttpMessage: Sized { JsonBody::new(self) } - /// Return stream to http payload processes as multipart. - /// - /// Content-type: multipart/form-data; - /// - /// ## Server example - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate env_logger; - /// # extern crate futures; - /// # use std::str; - /// # use actix_web::*; - /// # use actix_web::actix::fut::FinishStream; - /// # use futures::{Future, Stream}; - /// # use futures::future::{ok, result, Either}; - /// fn index(mut req: HttpRequest) -> Box> { - /// req.multipart().from_err() // <- get multipart stream for current request - /// .and_then(|item| match item { // <- iterate over multipart items - /// multipart::MultipartItem::Field(field) => { - /// // Field in turn is stream of *Bytes* object - /// Either::A(field.from_err() - /// .map(|c| println!("-- CHUNK: \n{:?}", str::from_utf8(&c))) - /// .finish()) - /// }, - /// multipart::MultipartItem::Nested(mp) => { - /// // Or item could be nested Multipart stream - /// Either::B(ok(())) - /// } - /// }) - /// .finish() // <- Stream::finish() combinator from actix - /// .map(|_| HttpResponse::Ok().into()) - /// .responder() - /// } - /// # fn main() {} - /// ``` - fn multipart(&self) -> Multipart { - let boundary = Multipart::boundary(self.headers()); - Multipart::new(boundary, self.payload()) - } - /// Return stream of lines. fn readlines(&self) -> Readlines { Readlines::new(self) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 59815c58c..73de380ad 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -13,12 +13,10 @@ use serde::Serialize; use serde_json; use body::Body; -use client::ClientResponse; use error::Error; -use handler::Responder; use header::{ContentEncoding, Header, IntoHeaderValue}; use httpmessage::HttpMessage; -use httprequest::HttpRequest; +// use httprequest::HttpRequest; /// max write buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; @@ -720,16 +718,6 @@ impl From for HttpResponse { } } -impl Responder for HttpResponseBuilder { - type Item = HttpResponse; - type Error = Error; - - #[inline] - fn respond_to(mut self, _: &HttpRequest) -> Result { - Ok(self.finish()) - } -} - impl From<&'static str> for HttpResponse { fn from(val: &'static str) -> Self { HttpResponse::Ok() @@ -738,18 +726,6 @@ impl From<&'static str> for HttpResponse { } } -impl Responder for &'static str { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) - } -} - impl From<&'static [u8]> for HttpResponse { fn from(val: &'static [u8]) -> Self { HttpResponse::Ok() @@ -758,18 +734,6 @@ impl From<&'static [u8]> for HttpResponse { } } -impl Responder for &'static [u8] { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) - } -} - impl From for HttpResponse { fn from(val: String) -> Self { HttpResponse::Ok() @@ -778,18 +742,6 @@ impl From for HttpResponse { } } -impl Responder for String { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) - } -} - impl<'a> From<&'a String> for HttpResponse { fn from(val: &'a String) -> Self { HttpResponse::build(StatusCode::OK) @@ -798,18 +750,6 @@ impl<'a> From<&'a String> for HttpResponse { } } -impl<'a> Responder for &'a String { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) - } -} - impl From for HttpResponse { fn from(val: Bytes) -> Self { HttpResponse::Ok() @@ -818,18 +758,6 @@ impl From for HttpResponse { } } -impl Responder for Bytes { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) - } -} - impl From for HttpResponse { fn from(val: BytesMut) -> Self { HttpResponse::Ok() @@ -838,40 +766,6 @@ impl From for HttpResponse { } } -impl Responder for BytesMut { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) - } -} - -/// Create `HttpResponseBuilder` from `ClientResponse` -/// -/// It is useful for proxy response. This implementation -/// copies all responses's headers and status. -impl<'a> From<&'a ClientResponse> for HttpResponseBuilder { - fn from(resp: &'a ClientResponse) -> HttpResponseBuilder { - let mut builder = HttpResponse::build(resp.status()); - for (key, value) in resp.headers() { - builder.header(key.clone(), value.clone()); - } - builder - } -} - -impl<'a, S> From<&'a HttpRequest> for HttpResponseBuilder { - fn from(req: &'a HttpRequest) -> HttpResponseBuilder { - req.request() - .server_settings() - .get_response_builder(StatusCode::OK) - } -} - #[derive(Debug)] struct InnerHttpResponse { version: Option, @@ -921,7 +815,7 @@ impl InnerHttpResponse { let body = match mem::replace(&mut self.body, Body::Empty) { Body::Empty => None, Body::Binary(mut bin) => Some(bin.take()), - Body::Streaming(_) | Body::Actor(_) => { + Body::Streaming(_) => { error!("Streaming or Actor body is not support by error response"); None } diff --git a/src/json.rs b/src/json.rs index 178143f11..04dd369eb 100644 --- a/src/json.rs +++ b/src/json.rs @@ -11,10 +11,9 @@ use serde::Serialize; use serde_json; use error::{Error, JsonPayloadError}; -use handler::{FromRequest, Responder}; use http::StatusCode; use httpmessage::HttpMessage; -use httprequest::HttpRequest; +// use httprequest::HttpRequest; use httpresponse::HttpResponse; /// Json helper @@ -116,102 +115,6 @@ where } } -impl Responder for Json { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - let body = serde_json::to_string(&self.0)?; - - Ok(req - .build_response(StatusCode::OK) - .content_type("application/json") - .body(body)) - } -} - -impl FromRequest for Json -where - T: DeserializeOwned + 'static, - S: 'static, -{ - type Config = JsonConfig; - type Result = Box>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - let req2 = req.clone(); - let err = Rc::clone(&cfg.ehandler); - Box::new( - JsonBody::new(req) - .limit(cfg.limit) - .map_err(move |e| (*err)(e, &req2)) - .map(Json), - ) - } -} - -/// Json extractor configuration -/// -/// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{error, http, App, HttpResponse, Json, Result}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// deserialize `Info` from request's body, max payload size is 4kb -/// fn index(info: Json) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource("/index.html", |r| { -/// r.method(http::Method::POST) -/// .with_config(index, |cfg| { -/// cfg.0.limit(4096) // <- change json extractor configuration -/// .error_handler(|err, req| { // <- create custom error response -/// error::InternalError::from_response( -/// err, HttpResponse::Conflict().finish()).into() -/// }); -/// }) -/// }); -/// } -/// ``` -pub struct JsonConfig { - limit: usize, - ehandler: Rc) -> Error>, -} - -impl JsonConfig { - /// Change max size of payload. By default max size is 256Kb - pub fn limit(&mut self, limit: usize) -> &mut Self { - self.limit = limit; - self - } - - /// Set custom error handler - pub fn error_handler(&mut self, f: F) -> &mut Self - where - F: Fn(JsonPayloadError, &HttpRequest) -> Error + 'static, - { - self.ehandler = Rc::new(f); - self - } -} - -impl Default for JsonConfig { - fn default() -> Self { - JsonConfig { - limit: 262_144, - ehandler: Rc::new(|e, _| e.into()), - } - } -} - /// Request payload json parser that resolves to a deserialized `T` value. /// /// Returns error: diff --git a/src/lib.rs b/src/lib.rs index f494c05de..6df1a770e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,13 +77,11 @@ //! * `flate2-rust` - experimental rust based implementation for //! `gzip`, `deflate` compression. //! -#![cfg_attr(actix_nightly, feature( - specialization, // for impl ErrorResponse for std::error::Error - extern_prelude, - tool_lints, -))] +#![cfg_attr(actix_nightly, feature(tool_lints))] #![warn(missing_docs)] +#![allow(unused_imports, unused_variables, dead_code)] +extern crate actix; #[macro_use] extern crate log; extern crate base64; @@ -140,109 +138,36 @@ extern crate serde_json; extern crate smallvec; extern crate actix_net; -#[macro_use] -extern crate actix as actix_inner; #[cfg(test)] #[macro_use] extern crate serde_derive; -#[cfg(feature = "tls")] -extern crate native_tls; -#[cfg(feature = "tls")] -extern crate tokio_tls; - -#[cfg(feature = "openssl")] -extern crate openssl; -#[cfg(feature = "openssl")] -extern crate tokio_openssl; - -#[cfg(feature = "rust-tls")] -extern crate rustls; -#[cfg(feature = "rust-tls")] -extern crate tokio_rustls; -#[cfg(feature = "rust-tls")] -extern crate webpki; -#[cfg(feature = "rust-tls")] -extern crate webpki_roots; - -mod application; mod body; -mod context; -mod de; mod extensions; -mod extractor; -mod handler; mod header; -mod helpers; mod httpcodes; mod httpmessage; -mod httprequest; +//mod httprequest; mod httpresponse; mod info; mod json; -mod param; mod payload; -mod pipeline; -mod resource; -mod route; -mod router; -mod scope; mod uri; -mod with; -pub mod client; pub mod error; -pub mod fs; -pub mod middleware; -pub mod multipart; -pub mod pred; pub mod server; -pub mod test; -pub mod ws; -pub use application::App; +//pub mod test; +//pub mod ws; pub use body::{Binary, Body}; -pub use context::HttpContext; pub use error::{Error, ResponseError, Result}; pub use extensions::Extensions; -pub use extractor::{Form, Path, Query}; -pub use handler::{ - AsyncResponder, Either, FromRequest, FutureResponse, Responder, State, -}; pub use httpmessage::HttpMessage; -pub use httprequest::HttpRequest; +//pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use json::Json; -pub use scope::Scope; pub use server::Request; -pub mod actix { - //! Re-exports [actix's](https://docs.rs/actix/) prelude - - extern crate actix; - pub use self::actix::actors::resolver; - pub use self::actix::actors::signal; - pub use self::actix::fut; - pub use self::actix::msgs; - pub use self::actix::prelude::*; - pub use self::actix::{run, spawn}; -} - -#[cfg(feature = "openssl")] -pub(crate) const HAS_OPENSSL: bool = true; -#[cfg(not(feature = "openssl"))] -pub(crate) const HAS_OPENSSL: bool = false; - -#[cfg(feature = "tls")] -pub(crate) const HAS_TLS: bool = true; -#[cfg(not(feature = "tls"))] -pub(crate) const HAS_TLS: bool = false; - -#[cfg(feature = "rust-tls")] -pub(crate) const HAS_RUSTLS: bool = true; -#[cfg(not(feature = "rust-tls"))] -pub(crate) const HAS_RUSTLS: bool = false; - pub mod dev { //! The `actix-web` prelude for library developers //! @@ -255,19 +180,11 @@ pub mod dev { //! ``` pub use body::BodyStream; - pub use context::Drain; - pub use extractor::{FormConfig, PayloadConfig}; - pub use handler::{AsyncResult, Handler}; pub use httpmessage::{MessageBody, Readlines, UrlEncoded}; pub use httpresponse::HttpResponseBuilder; pub use info::ConnectionInfo; - pub use json::{JsonBody, JsonConfig}; - pub use param::{FromParam, Params}; + pub use json::JsonBody; pub use payload::{Payload, PayloadBuffer}; - pub use pipeline::Pipeline; - pub use resource::Resource; - pub use route::Route; - pub use router::{ResourceDef, ResourceInfo, ResourceType, Router}; } pub mod http { @@ -281,8 +198,6 @@ pub mod http { pub use cookie::{Cookie, CookieBuilder}; - pub use helpers::NormalizePath; - /// Various http headers pub mod header { pub use header::*; diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs deleted file mode 100644 index 953f2911c..000000000 --- a/src/middleware/cors.rs +++ /dev/null @@ -1,1183 +0,0 @@ -//! Cross-origin resource sharing (CORS) for Actix applications -//! -//! CORS middleware could be used with application and with resource. -//! First you need to construct CORS middleware instance. -//! -//! To construct a cors: -//! -//! 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building. -//! 2. Use any of the builder methods to set fields in the backend. -//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the -//! constructed backend. -//! -//! Cors middleware could be used as parameter for `App::middleware()` or -//! `Resource::middleware()` methods. But you have to use -//! `Cors::for_app()` method to support *preflight* OPTIONS request. -//! -//! -//! # Example -//! -//! ```rust -//! # extern crate actix_web; -//! use actix_web::middleware::cors::Cors; -//! use actix_web::{http, App, HttpRequest, HttpResponse}; -//! -//! fn index(mut req: HttpRequest) -> &'static str { -//! "Hello world" -//! } -//! -//! fn main() { -//! let app = App::new().configure(|app| { -//! Cors::for_app(app) // <- Construct CORS middleware builder -//! .allowed_origin("https://www.rust-lang.org/") -//! .allowed_methods(vec!["GET", "POST"]) -//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) -//! .allowed_header(http::header::CONTENT_TYPE) -//! .max_age(3600) -//! .resource("/index.html", |r| { -//! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -//! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); -//! }) -//! .register() -//! }); -//! } -//! ``` -//! In this example custom *CORS* middleware get registered for "/index.html" -//! endpoint. -//! -//! Cors middleware automatically handle *OPTIONS* preflight request. -use std::collections::HashSet; -use std::iter::FromIterator; -use std::rc::Rc; - -use http::header::{self, HeaderName, HeaderValue}; -use http::{self, HttpTryFrom, Method, StatusCode, Uri}; - -use application::App; -use error::{ResponseError, Result}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response, Started}; -use resource::Resource; -use router::ResourceDef; -use server::Request; - -/// A set of errors that can occur during processing CORS -#[derive(Debug, Fail)] -pub enum CorsError { - /// The HTTP request header `Origin` is required but was not provided - #[fail( - display = "The HTTP request header `Origin` is required but was not provided" - )] - MissingOrigin, - /// The HTTP request header `Origin` could not be parsed correctly. - #[fail(display = "The HTTP request header `Origin` could not be parsed correctly.")] - BadOrigin, - /// The request header `Access-Control-Request-Method` is required but is - /// missing - #[fail( - display = "The request header `Access-Control-Request-Method` is required but is missing" - )] - MissingRequestMethod, - /// The request header `Access-Control-Request-Method` has an invalid value - #[fail( - display = "The request header `Access-Control-Request-Method` has an invalid value" - )] - BadRequestMethod, - /// The request header `Access-Control-Request-Headers` has an invalid - /// value - #[fail( - display = "The request header `Access-Control-Request-Headers` has an invalid value" - )] - BadRequestHeaders, - /// The request header `Access-Control-Request-Headers` is required but is - /// missing. - #[fail( - display = "The request header `Access-Control-Request-Headers` is required but is - missing" - )] - MissingRequestHeaders, - /// Origin is not allowed to make this request - #[fail(display = "Origin is not allowed to make this request")] - OriginNotAllowed, - /// Requested method is not allowed - #[fail(display = "Requested method is not allowed")] - MethodNotAllowed, - /// One or more headers requested are not allowed - #[fail(display = "One or more headers requested are not allowed")] - HeadersNotAllowed, -} - -impl ResponseError for CorsError { - fn error_response(&self) -> HttpResponse { - HttpResponse::with_body(StatusCode::BAD_REQUEST, format!("{}", self)) - } -} - -/// An enum signifying that some of type T is allowed, or `All` (everything is -/// allowed). -/// -/// `Default` is implemented for this enum and is `All`. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum AllOrSome { - /// Everything is allowed. Usually equivalent to the "*" value. - All, - /// Only some of `T` is allowed - Some(T), -} - -impl Default for AllOrSome { - fn default() -> Self { - AllOrSome::All - } -} - -impl AllOrSome { - /// Returns whether this is an `All` variant - pub fn is_all(&self) -> bool { - match *self { - AllOrSome::All => true, - AllOrSome::Some(_) => false, - } - } - - /// Returns whether this is a `Some` variant - pub fn is_some(&self) -> bool { - !self.is_all() - } - - /// Returns &T - pub fn as_ref(&self) -> Option<&T> { - match *self { - AllOrSome::All => None, - AllOrSome::Some(ref t) => Some(t), - } - } -} - -/// `Middleware` for Cross-origin resource sharing support -/// -/// The Cors struct contains the settings for CORS requests to be validated and -/// for responses to be generated. -#[derive(Clone)] -pub struct Cors { - inner: Rc, -} - -struct Inner { - methods: HashSet, - origins: AllOrSome>, - origins_str: Option, - headers: AllOrSome>, - expose_hdrs: Option, - max_age: Option, - preflight: bool, - send_wildcard: bool, - supports_credentials: bool, - vary_header: bool, -} - -impl Default for Cors { - fn default() -> Cors { - let inner = Inner { - origins: AllOrSome::default(), - origins_str: None, - methods: HashSet::from_iter( - vec![ - Method::GET, - Method::HEAD, - Method::POST, - Method::OPTIONS, - Method::PUT, - Method::PATCH, - Method::DELETE, - ].into_iter(), - ), - headers: AllOrSome::All, - expose_hdrs: None, - max_age: None, - preflight: true, - send_wildcard: false, - supports_credentials: false, - vary_header: true, - }; - Cors { - inner: Rc::new(inner), - } - } -} - -impl Cors { - /// Build a new CORS middleware instance - pub fn build() -> CorsBuilder<()> { - CorsBuilder { - cors: Some(Inner { - origins: AllOrSome::All, - origins_str: None, - methods: HashSet::new(), - headers: AllOrSome::All, - expose_hdrs: None, - max_age: None, - preflight: true, - send_wildcard: false, - supports_credentials: false, - vary_header: true, - }), - methods: false, - error: None, - expose_hdrs: HashSet::new(), - resources: Vec::new(), - app: None, - } - } - - /// Create CorsBuilder for a specified application. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::middleware::cors::Cors; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().configure( - /// |app| { - /// Cors::for_app(app) // <- Construct CORS builder - /// .allowed_origin("https://www.rust-lang.org/") - /// .resource("/resource", |r| { // register resource - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// }) - /// .register() - /// }, // construct CORS and return application instance - /// ); - /// } - /// ``` - pub fn for_app(app: App) -> CorsBuilder { - CorsBuilder { - cors: Some(Inner { - origins: AllOrSome::All, - origins_str: None, - methods: HashSet::new(), - headers: AllOrSome::All, - expose_hdrs: None, - max_age: None, - preflight: true, - send_wildcard: false, - supports_credentials: false, - vary_header: true, - }), - methods: false, - error: None, - expose_hdrs: HashSet::new(), - resources: Vec::new(), - app: Some(app), - } - } - - /// This method register cors middleware with resource and - /// adds route for *OPTIONS* preflight requests. - /// - /// It is possible to register *Cors* middleware with - /// `Resource::middleware()` method, but in that case *Cors* - /// middleware wont be able to handle *OPTIONS* requests. - pub fn register(self, resource: &mut Resource) { - resource - .method(Method::OPTIONS) - .h(|_: &_| HttpResponse::Ok()); - resource.middleware(self); - } - - fn validate_origin(&self, req: &Request) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ORIGIN) { - if let Ok(origin) = hdr.to_str() { - return match self.inner.origins { - AllOrSome::All => Ok(()), - AllOrSome::Some(ref allowed_origins) => allowed_origins - .get(origin) - .and_then(|_| Some(())) - .ok_or_else(|| CorsError::OriginNotAllowed), - }; - } - Err(CorsError::BadOrigin) - } else { - return match self.inner.origins { - AllOrSome::All => Ok(()), - _ => Err(CorsError::MissingOrigin), - }; - } - } - - fn validate_allowed_method(&self, req: &Request) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { - if let Ok(meth) = hdr.to_str() { - if let Ok(method) = Method::try_from(meth) { - return self - .inner - .methods - .get(&method) - .and_then(|_| Some(())) - .ok_or_else(|| CorsError::MethodNotAllowed); - } - } - Err(CorsError::BadRequestMethod) - } else { - Err(CorsError::MissingRequestMethod) - } - } - - fn validate_allowed_headers(&self, req: &Request) -> Result<(), CorsError> { - match self.inner.headers { - AllOrSome::All => Ok(()), - AllOrSome::Some(ref allowed_headers) => { - if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) - { - if let Ok(headers) = hdr.to_str() { - let mut hdrs = HashSet::new(); - for hdr in headers.split(',') { - match HeaderName::try_from(hdr.trim()) { - Ok(hdr) => hdrs.insert(hdr), - Err(_) => return Err(CorsError::BadRequestHeaders), - }; - } - - if !hdrs.is_empty() && !hdrs.is_subset(allowed_headers) { - return Err(CorsError::HeadersNotAllowed); - } - return Ok(()); - } - Err(CorsError::BadRequestHeaders) - } else { - Err(CorsError::MissingRequestHeaders) - } - } - } - } -} - -impl Middleware for Cors { - fn start(&self, req: &HttpRequest) -> Result { - if self.inner.preflight && Method::OPTIONS == *req.method() { - self.validate_origin(req)?; - self.validate_allowed_method(&req)?; - self.validate_allowed_headers(&req)?; - - // allowed headers - let headers = if let Some(headers) = self.inner.headers.as_ref() { - Some( - HeaderValue::try_from( - &headers - .iter() - .fold(String::new(), |s, v| s + "," + v.as_str()) - .as_str()[1..], - ).unwrap(), - ) - } else if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) - { - Some(hdr.clone()) - } else { - None - }; - - Ok(Started::Response( - HttpResponse::Ok() - .if_some(self.inner.max_age.as_ref(), |max_age, resp| { - let _ = resp.header( - header::ACCESS_CONTROL_MAX_AGE, - format!("{}", max_age).as_str(), - ); - }).if_some(headers, |headers, resp| { - let _ = - resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); - }).if_true(self.inner.origins.is_all(), |resp| { - if self.inner.send_wildcard { - resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); - } else { - let origin = req.headers().get(header::ORIGIN).unwrap(); - resp.header( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - origin.clone(), - ); - } - }).if_true(self.inner.origins.is_some(), |resp| { - resp.header( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - self.inner.origins_str.as_ref().unwrap().clone(), - ); - }).if_true(self.inner.supports_credentials, |resp| { - resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); - }).header( - header::ACCESS_CONTROL_ALLOW_METHODS, - &self - .inner - .methods - .iter() - .fold(String::new(), |s, v| s + "," + v.as_str()) - .as_str()[1..], - ).finish(), - )) - } else { - // Only check requests with a origin header. - if req.headers().contains_key(header::ORIGIN) { - self.validate_origin(req)?; - } - - Ok(Started::Done) - } - } - - fn response( - &self, req: &HttpRequest, mut resp: HttpResponse, - ) -> Result { - match self.inner.origins { - AllOrSome::All => { - if self.inner.send_wildcard { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - HeaderValue::from_static("*"), - ); - } else if let Some(origin) = req.headers().get(header::ORIGIN) { - resp.headers_mut() - .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); - } - } - AllOrSome::Some(_) => { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - self.inner.origins_str.as_ref().unwrap().clone(), - ); - } - } - - if let Some(ref expose) = self.inner.expose_hdrs { - resp.headers_mut().insert( - header::ACCESS_CONTROL_EXPOSE_HEADERS, - HeaderValue::try_from(expose.as_str()).unwrap(), - ); - } - if self.inner.supports_credentials { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_CREDENTIALS, - HeaderValue::from_static("true"), - ); - } - if self.inner.vary_header { - let value = if let Some(hdr) = resp.headers_mut().get(header::VARY) { - let mut val: Vec = Vec::with_capacity(hdr.as_bytes().len() + 8); - val.extend(hdr.as_bytes()); - val.extend(b", Origin"); - HeaderValue::try_from(&val[..]).unwrap() - } else { - HeaderValue::from_static("Origin") - }; - resp.headers_mut().insert(header::VARY, value); - } - Ok(Response::Done(resp)) - } -} - -/// Structure that follows the builder pattern for building `Cors` middleware -/// structs. -/// -/// To construct a cors: -/// -/// 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building. -/// 2. Use any of the builder methods to set fields in the backend. -/// 3. Call [finish](struct.Cors.html#method.finish) to retrieve the -/// constructed backend. -/// -/// # Example -/// -/// ```rust -/// # extern crate http; -/// # extern crate actix_web; -/// use actix_web::middleware::cors; -/// use http::header; -/// -/// # fn main() { -/// let cors = cors::Cors::build() -/// .allowed_origin("https://www.rust-lang.org/") -/// .allowed_methods(vec!["GET", "POST"]) -/// .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) -/// .allowed_header(header::CONTENT_TYPE) -/// .max_age(3600) -/// .finish(); -/// # } -/// ``` -pub struct CorsBuilder { - cors: Option, - methods: bool, - error: Option, - expose_hdrs: HashSet, - resources: Vec>, - app: Option>, -} - -fn cors<'a>( - parts: &'a mut Option, err: &Option, -) -> Option<&'a mut Inner> { - if err.is_some() { - return None; - } - parts.as_mut() -} - -impl CorsBuilder { - /// Add an origin that are allowed to make requests. - /// Will be verified against the `Origin` request header. - /// - /// When `All` is set, and `send_wildcard` is set, "*" will be sent in - /// the `Access-Control-Allow-Origin` response header. Otherwise, the - /// client's `Origin` request header will be echoed back in the - /// `Access-Control-Allow-Origin` response header. - /// - /// When `Some` is set, the client's `Origin` request header will be - /// checked in a case-sensitive manner. - /// - /// This is the `list of origins` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// Defaults to `All`. - /// - /// Builder panics if supplied origin is not valid uri. - pub fn allowed_origin(&mut self, origin: &str) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - match Uri::try_from(origin) { - Ok(_) => { - if cors.origins.is_all() { - cors.origins = AllOrSome::Some(HashSet::new()); - } - if let AllOrSome::Some(ref mut origins) = cors.origins { - origins.insert(origin.to_owned()); - } - } - Err(e) => { - self.error = Some(e.into()); - } - } - } - self - } - - /// Set a list of methods which the allowed origins are allowed to access - /// for requests. - /// - /// This is the `list of methods` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// Defaults to `[GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]` - pub fn allowed_methods(&mut self, methods: U) -> &mut CorsBuilder - where - U: IntoIterator, - Method: HttpTryFrom, - { - self.methods = true; - if let Some(cors) = cors(&mut self.cors, &self.error) { - for m in methods { - match Method::try_from(m) { - Ok(method) => { - cors.methods.insert(method); - } - Err(e) => { - self.error = Some(e.into()); - break; - } - } - } - } - self - } - - /// Set an allowed header - pub fn allowed_header(&mut self, header: H) -> &mut CorsBuilder - where - HeaderName: HttpTryFrom, - { - if let Some(cors) = cors(&mut self.cors, &self.error) { - match HeaderName::try_from(header) { - Ok(method) => { - if cors.headers.is_all() { - cors.headers = AllOrSome::Some(HashSet::new()); - } - if let AllOrSome::Some(ref mut headers) = cors.headers { - headers.insert(method); - } - } - Err(e) => self.error = Some(e.into()), - } - } - self - } - - /// Set a list of header field names which can be used when - /// this resource is accessed by allowed origins. - /// - /// If `All` is set, whatever is requested by the client in - /// `Access-Control-Request-Headers` will be echoed back in the - /// `Access-Control-Allow-Headers` header. - /// - /// This is the `list of headers` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// Defaults to `All`. - pub fn allowed_headers(&mut self, headers: U) -> &mut CorsBuilder - where - U: IntoIterator, - HeaderName: HttpTryFrom, - { - if let Some(cors) = cors(&mut self.cors, &self.error) { - for h in headers { - match HeaderName::try_from(h) { - Ok(method) => { - if cors.headers.is_all() { - cors.headers = AllOrSome::Some(HashSet::new()); - } - if let AllOrSome::Some(ref mut headers) = cors.headers { - headers.insert(method); - } - } - Err(e) => { - self.error = Some(e.into()); - break; - } - } - } - } - self - } - - /// Set a list of headers which are safe to expose to the API of a CORS API - /// specification. This corresponds to the - /// `Access-Control-Expose-Headers` response header. - /// - /// This is the `list of exposed headers` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// This defaults to an empty set. - pub fn expose_headers(&mut self, headers: U) -> &mut CorsBuilder - where - U: IntoIterator, - HeaderName: HttpTryFrom, - { - for h in headers { - match HeaderName::try_from(h) { - Ok(method) => { - self.expose_hdrs.insert(method); - } - Err(e) => { - self.error = Some(e.into()); - break; - } - } - } - self - } - - /// Set a maximum time for which this CORS request maybe cached. - /// This value is set as the `Access-Control-Max-Age` header. - /// - /// This defaults to `None` (unset). - pub fn max_age(&mut self, max_age: usize) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.max_age = Some(max_age) - } - self - } - - /// Set a wildcard origins - /// - /// If send wildcard is set and the `allowed_origins` parameter is `All`, a - /// wildcard `Access-Control-Allow-Origin` response header is sent, - /// rather than the request’s `Origin` header. - /// - /// This is the `supports credentials flag` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// This **CANNOT** be used in conjunction with `allowed_origins` set to - /// `All` and `allow_credentials` set to `true`. Depending on the mode - /// of usage, this will either result in an `Error:: - /// CredentialsWithWildcardOrigin` error during actix launch or runtime. - /// - /// Defaults to `false`. - pub fn send_wildcard(&mut self) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.send_wildcard = true - } - self - } - - /// Allows users to make authenticated requests - /// - /// If true, injects the `Access-Control-Allow-Credentials` header in - /// responses. This allows cookies and credentials to be submitted - /// across domains. - /// - /// This option cannot be used in conjunction with an `allowed_origin` set - /// to `All` and `send_wildcards` set to `true`. - /// - /// Defaults to `false`. - /// - /// Builder panics if credentials are allowed, but the Origin is set to "*". - /// This is not allowed by W3C - pub fn supports_credentials(&mut self) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.supports_credentials = true - } - self - } - - /// Disable `Vary` header support. - /// - /// When enabled the header `Vary: Origin` will be returned as per the W3 - /// implementation guidelines. - /// - /// Setting this header when the `Access-Control-Allow-Origin` is - /// dynamically generated (e.g. when there is more than one allowed - /// origin, and an Origin than '*' is returned) informs CDNs and other - /// caches that the CORS headers are dynamic, and cannot be cached. - /// - /// By default `vary` header support is enabled. - pub fn disable_vary_header(&mut self) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.vary_header = false - } - self - } - - /// Disable *preflight* request support. - /// - /// When enabled cors middleware automatically handles *OPTIONS* request. - /// This is useful application level middleware. - /// - /// By default *preflight* support is enabled. - pub fn disable_preflight(&mut self) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.preflight = false - } - self - } - - /// Configure resource for a specific path. - /// - /// This is similar to a `App::resource()` method. Except, cors middleware - /// get registered for the resource. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::middleware::cors::Cors; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().configure( - /// |app| { - /// Cors::for_app(app) // <- Construct CORS builder - /// .allowed_origin("https://www.rust-lang.org/") - /// .allowed_methods(vec!["GET", "POST"]) - /// .allowed_header(http::header::CONTENT_TYPE) - /// .max_age(3600) - /// .resource("/resource1", |r| { // register resource - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// }) - /// .resource("/resource2", |r| { // register another resource - /// r.method(http::Method::HEAD) - /// .f(|_| HttpResponse::MethodNotAllowed()); - /// }) - /// .register() - /// }, // construct CORS and return application instance - /// ); - /// } - /// ``` - pub fn resource(&mut self, path: &str, f: F) -> &mut CorsBuilder - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // add resource handler - let mut resource = Resource::new(ResourceDef::new(path)); - f(&mut resource); - - self.resources.push(resource); - self - } - - fn construct(&mut self) -> Cors { - if !self.methods { - self.allowed_methods(vec![ - Method::GET, - Method::HEAD, - Method::POST, - Method::OPTIONS, - Method::PUT, - Method::PATCH, - Method::DELETE, - ]); - } - - if let Some(e) = self.error.take() { - panic!("{}", e); - } - - let mut cors = self.cors.take().expect("cannot reuse CorsBuilder"); - - if cors.supports_credentials && cors.send_wildcard && cors.origins.is_all() { - panic!("Credentials are allowed, but the Origin is set to \"*\""); - } - - if let AllOrSome::Some(ref origins) = cors.origins { - let s = origins - .iter() - .fold(String::new(), |s, v| format!("{}, {}", s, v)); - cors.origins_str = Some(HeaderValue::try_from(&s[2..]).unwrap()); - } - - if !self.expose_hdrs.is_empty() { - cors.expose_hdrs = Some( - self.expose_hdrs - .iter() - .fold(String::new(), |s, v| format!("{}, {}", s, v.as_str()))[2..] - .to_owned(), - ); - } - Cors { - inner: Rc::new(cors), - } - } - - /// Finishes building and returns the built `Cors` instance. - /// - /// This method panics in case of any configuration error. - pub fn finish(&mut self) -> Cors { - if !self.resources.is_empty() { - panic!( - "CorsBuilder::resource() was used, - to construct CORS `.register(app)` method should be used" - ); - } - self.construct() - } - - /// Finishes building Cors middleware and register middleware for - /// application - /// - /// This method panics in case of any configuration error or if non of - /// resources are registered. - pub fn register(&mut self) -> App { - if self.resources.is_empty() { - panic!("No resources are registered."); - } - - let cors = self.construct(); - let mut app = self - .app - .take() - .expect("CorsBuilder has to be constructed with Cors::for_app(app)"); - - // register resources - for mut resource in self.resources.drain(..) { - cors.clone().register(&mut resource); - app.register_resource(resource); - } - - app - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test::{self, TestRequest}; - - impl Started { - fn is_done(&self) -> bool { - match *self { - Started::Done => true, - _ => false, - } - } - fn response(self) -> HttpResponse { - match self { - Started::Response(resp) => resp, - _ => panic!(), - } - } - } - impl Response { - fn response(self) -> HttpResponse { - match self { - Response::Done(resp) => resp, - _ => panic!(), - } - } - } - - #[test] - #[should_panic(expected = "Credentials are allowed, but the Origin is set to")] - fn cors_validates_illegal_allow_credentials() { - Cors::build() - .supports_credentials() - .send_wildcard() - .finish(); - } - - #[test] - #[should_panic(expected = "No resources are registered")] - fn no_resource() { - Cors::build() - .supports_credentials() - .send_wildcard() - .register(); - } - - #[test] - #[should_panic(expected = "Cors::for_app(app)")] - fn no_resource2() { - Cors::build() - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .register(); - } - - #[test] - fn validate_origin_allows_all_origins() { - let cors = Cors::default(); - let req = TestRequest::with_header("Origin", "https://www.example.com").finish(); - - assert!(cors.start(&req).ok().unwrap().is_done()) - } - - #[test] - fn test_preflight() { - let mut cors = Cors::build() - .send_wildcard() - .max_age(3600) - .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) - .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) - .allowed_header(header::CONTENT_TYPE) - .finish(); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .finish(); - - assert!(cors.start(&req).is_err()); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "put") - .method(Method::OPTIONS) - .finish(); - - assert!(cors.start(&req).is_err()); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") - .header( - header::ACCESS_CONTROL_REQUEST_HEADERS, - "AUTHORIZATION,ACCEPT", - ).method(Method::OPTIONS) - .finish(); - - let resp = cors.start(&req).unwrap().response(); - assert_eq!( - &b"*"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - assert_eq!( - &b"3600"[..], - resp.headers() - .get(header::ACCESS_CONTROL_MAX_AGE) - .unwrap() - .as_bytes() - ); - //assert_eq!( - // &b"authorization,accept,content-type"[..], - // resp.headers().get(header::ACCESS_CONTROL_ALLOW_HEADERS).unwrap(). - // as_bytes()); assert_eq!( - // &b"POST,GET,OPTIONS"[..], - // resp.headers().get(header::ACCESS_CONTROL_ALLOW_METHODS).unwrap(). - // as_bytes()); - - Rc::get_mut(&mut cors.inner).unwrap().preflight = false; - assert!(cors.start(&req).unwrap().is_done()); - } - - // #[test] - // #[should_panic(expected = "MissingOrigin")] - // fn test_validate_missing_origin() { - // let cors = Cors::build() - // .allowed_origin("https://www.example.com") - // .finish(); - // let mut req = HttpRequest::default(); - // cors.start(&req).unwrap(); - // } - - #[test] - #[should_panic(expected = "OriginNotAllowed")] - fn test_validate_not_allowed_origin() { - let cors = Cors::build() - .allowed_origin("https://www.example.com") - .finish(); - - let req = TestRequest::with_header("Origin", "https://www.unknown.com") - .method(Method::GET) - .finish(); - cors.start(&req).unwrap(); - } - - #[test] - fn test_validate_origin() { - let cors = Cors::build() - .allowed_origin("https://www.example.com") - .finish(); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::GET) - .finish(); - - assert!(cors.start(&req).unwrap().is_done()); - } - - #[test] - fn test_no_origin_response() { - let cors = Cors::build().finish(); - - let req = TestRequest::default().method(Method::GET).finish(); - let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&req, resp).unwrap().response(); - assert!( - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .is_none() - ); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .finish(); - let resp = cors.response(&req, resp).unwrap().response(); - assert_eq!( - &b"https://www.example.com"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - } - - #[test] - fn test_response() { - let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT]; - let cors = Cors::build() - .send_wildcard() - .disable_preflight() - .max_age(3600) - .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) - .allowed_headers(exposed_headers.clone()) - .expose_headers(exposed_headers.clone()) - .allowed_header(header::CONTENT_TYPE) - .finish(); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .finish(); - - let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&req, resp).unwrap().response(); - assert_eq!( - &b"*"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - assert_eq!( - &b"Origin"[..], - resp.headers().get(header::VARY).unwrap().as_bytes() - ); - - { - let headers = resp - .headers() - .get(header::ACCESS_CONTROL_EXPOSE_HEADERS) - .unwrap() - .to_str() - .unwrap() - .split(',') - .map(|s| s.trim()) - .collect::>(); - - for h in exposed_headers { - assert!(headers.contains(&h.as_str())); - } - } - - let resp: HttpResponse = - HttpResponse::Ok().header(header::VARY, "Accept").finish(); - let resp = cors.response(&req, resp).unwrap().response(); - assert_eq!( - &b"Accept, Origin"[..], - resp.headers().get(header::VARY).unwrap().as_bytes() - ); - - let cors = Cors::build() - .disable_vary_header() - .allowed_origin("https://www.example.com") - .allowed_origin("https://www.google.com") - .finish(); - let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&req, resp).unwrap().response(); - - let origins_str = resp - .headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .to_str() - .unwrap(); - - if origins_str.starts_with("https://www.example.com") { - assert_eq!( - "https://www.example.com, https://www.google.com", - origins_str - ); - } else { - assert_eq!( - "https://www.google.com, https://www.example.com", - origins_str - ); - } - } - - #[test] - fn cors_resource() { - let mut srv = test::TestServer::with_factory(|| { - App::new().configure(|app| { - Cors::for_app(app) - .allowed_origin("https://www.example.com") - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .register() - }) - }); - - let request = srv - .get() - .uri(srv.url("/test")) - .header("ORIGIN", "https://www.example2.com") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let request = srv - .get() - .uri(srv.url("/test")) - .header("ORIGIN", "https://www.example.com") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - } -} diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs deleted file mode 100644 index 02cd150d5..000000000 --- a/src/middleware/csrf.rs +++ /dev/null @@ -1,275 +0,0 @@ -//! A filter for cross-site request forgery (CSRF). -//! -//! This middleware is stateless and [based on request -//! headers](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Verifying_Same_Origin_with_Standard_Headers). -//! -//! By default requests are allowed only if one of these is true: -//! -//! * The request method is safe (`GET`, `HEAD`, `OPTIONS`). It is the -//! applications responsibility to ensure these methods cannot be used to -//! execute unwanted actions. Note that upgrade requests for websockets are -//! also considered safe. -//! * The `Origin` header (added automatically by the browser) matches one -//! of the allowed origins. -//! * There is no `Origin` header but the `Referer` header matches one of -//! the allowed origins. -//! -//! Use [`CsrfFilter::allow_xhr()`](struct.CsrfFilter.html#method.allow_xhr) -//! if you want to allow requests with unprotected methods via -//! [CORS](../cors/struct.Cors.html). -//! -//! # Example -//! -//! ``` -//! # extern crate actix_web; -//! use actix_web::middleware::csrf; -//! use actix_web::{http, App, HttpRequest, HttpResponse}; -//! -//! fn handle_post(_: &HttpRequest) -> &'static str { -//! "This action should only be triggered with requests from the same site" -//! } -//! -//! fn main() { -//! let app = App::new() -//! .middleware( -//! csrf::CsrfFilter::new().allowed_origin("https://www.example.com"), -//! ) -//! .resource("/", |r| { -//! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -//! r.method(http::Method::POST).f(handle_post); -//! }) -//! .finish(); -//! } -//! ``` -//! -//! In this example the entire application is protected from CSRF. - -use std::borrow::Cow; -use std::collections::HashSet; - -use bytes::Bytes; -use error::{ResponseError, Result}; -use http::{header, HeaderMap, HttpTryFrom, Uri}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Started}; -use server::Request; - -/// Potential cross-site request forgery detected. -#[derive(Debug, Fail)] -pub enum CsrfError { - /// The HTTP request header `Origin` was required but not provided. - #[fail(display = "Origin header required")] - MissingOrigin, - /// The HTTP request header `Origin` could not be parsed correctly. - #[fail(display = "Could not parse Origin header")] - BadOrigin, - /// The cross-site request was denied. - #[fail(display = "Cross-site request denied")] - CsrDenied, -} - -impl ResponseError for CsrfError { - fn error_response(&self) -> HttpResponse { - HttpResponse::Forbidden().body(self.to_string()) - } -} - -fn uri_origin(uri: &Uri) -> Option { - match (uri.scheme_part(), uri.host(), uri.port()) { - (Some(scheme), Some(host), Some(port)) => { - Some(format!("{}://{}:{}", scheme, host, port)) - } - (Some(scheme), Some(host), None) => Some(format!("{}://{}", scheme, host)), - _ => None, - } -} - -fn origin(headers: &HeaderMap) -> Option, CsrfError>> { - headers - .get(header::ORIGIN) - .map(|origin| { - origin - .to_str() - .map_err(|_| CsrfError::BadOrigin) - .map(|o| o.into()) - }).or_else(|| { - headers.get(header::REFERER).map(|referer| { - Uri::try_from(Bytes::from(referer.as_bytes())) - .ok() - .as_ref() - .and_then(uri_origin) - .ok_or(CsrfError::BadOrigin) - .map(|o| o.into()) - }) - }) -} - -/// A middleware that filters cross-site requests. -/// -/// To construct a CSRF filter: -/// -/// 1. Call [`CsrfFilter::build`](struct.CsrfFilter.html#method.build) to -/// start building. -/// 2. [Add](struct.CsrfFilterBuilder.html#method.allowed_origin) allowed -/// origins. -/// 3. Call [finish](struct.CsrfFilterBuilder.html#method.finish) to retrieve -/// the constructed filter. -/// -/// # Example -/// -/// ``` -/// use actix_web::middleware::csrf; -/// use actix_web::App; -/// -/// # fn main() { -/// let app = App::new() -/// .middleware(csrf::CsrfFilter::new().allowed_origin("https://www.example.com")); -/// # } -/// ``` -#[derive(Default)] -pub struct CsrfFilter { - origins: HashSet, - allow_xhr: bool, - allow_missing_origin: bool, - allow_upgrade: bool, -} - -impl CsrfFilter { - /// Start building a `CsrfFilter`. - pub fn new() -> CsrfFilter { - CsrfFilter { - origins: HashSet::new(), - allow_xhr: false, - allow_missing_origin: false, - allow_upgrade: false, - } - } - - /// Add an origin that is allowed to make requests. Will be verified - /// against the `Origin` request header. - pub fn allowed_origin>(mut self, origin: T) -> CsrfFilter { - self.origins.insert(origin.into()); - self - } - - /// Allow all requests with an `X-Requested-With` header. - /// - /// A cross-site attacker should not be able to send requests with custom - /// headers unless a CORS policy whitelists them. Therefore it should be - /// safe to allow requests with an `X-Requested-With` header (added - /// automatically by many JavaScript libraries). - /// - /// This is disabled by default, because in Safari it is possible to - /// circumvent this using redirects and Flash. - /// - /// Use this method to enable more lax filtering. - pub fn allow_xhr(mut self) -> CsrfFilter { - self.allow_xhr = true; - self - } - - /// Allow requests if the expected `Origin` header is missing (and - /// there is no `Referer` to fall back on). - /// - /// The filter is conservative by default, but it should be safe to allow - /// missing `Origin` headers because a cross-site attacker cannot prevent - /// the browser from sending `Origin` on unprotected requests. - pub fn allow_missing_origin(mut self) -> CsrfFilter { - self.allow_missing_origin = true; - self - } - - /// Allow cross-site upgrade requests (for example to open a WebSocket). - pub fn allow_upgrade(mut self) -> CsrfFilter { - self.allow_upgrade = true; - self - } - - fn validate(&self, req: &Request) -> Result<(), CsrfError> { - let is_upgrade = req.headers().contains_key(header::UPGRADE); - let is_safe = req.method().is_safe() && (self.allow_upgrade || !is_upgrade); - - if is_safe || (self.allow_xhr && req.headers().contains_key("x-requested-with")) - { - Ok(()) - } else if let Some(header) = origin(req.headers()) { - match header { - Ok(ref origin) if self.origins.contains(origin.as_ref()) => Ok(()), - Ok(_) => Err(CsrfError::CsrDenied), - Err(err) => Err(err), - } - } else if self.allow_missing_origin { - Ok(()) - } else { - Err(CsrfError::MissingOrigin) - } - } -} - -impl Middleware for CsrfFilter { - fn start(&self, req: &HttpRequest) -> Result { - self.validate(req)?; - Ok(Started::Done) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use http::Method; - use test::TestRequest; - - #[test] - fn test_safe() { - let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - - let req = TestRequest::with_header("Origin", "https://www.w3.org") - .method(Method::HEAD) - .finish(); - - assert!(csrf.start(&req).is_ok()); - } - - #[test] - fn test_csrf() { - let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - - let req = TestRequest::with_header("Origin", "https://www.w3.org") - .method(Method::POST) - .finish(); - - assert!(csrf.start(&req).is_err()); - } - - #[test] - fn test_referer() { - let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - - let req = TestRequest::with_header( - "Referer", - "https://www.example.com/some/path?query=param", - ).method(Method::POST) - .finish(); - - assert!(csrf.start(&req).is_ok()); - } - - #[test] - fn test_upgrade() { - let strict_csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - - let lax_csrf = CsrfFilter::new() - .allowed_origin("https://www.example.com") - .allow_upgrade(); - - let req = TestRequest::with_header("Origin", "https://cswsh.com") - .header("Connection", "Upgrade") - .header("Upgrade", "websocket") - .method(Method::GET) - .finish(); - - assert!(strict_csrf.start(&req).is_err()); - assert!(lax_csrf.start(&req).is_ok()); - } -} diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs deleted file mode 100644 index d980a2503..000000000 --- a/src/middleware/defaultheaders.rs +++ /dev/null @@ -1,120 +0,0 @@ -//! Default response headers -use http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; -use http::{HeaderMap, HttpTryFrom}; - -use error::Result; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response}; - -/// `Middleware` for setting default response headers. -/// -/// This middleware does not set header if response headers already contains it. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{http, middleware, App, HttpResponse}; -/// -/// fn main() { -/// let app = App::new() -/// .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2")) -/// .resource("/test", |r| { -/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -/// r.method(http::Method::HEAD) -/// .f(|_| HttpResponse::MethodNotAllowed()); -/// }) -/// .finish(); -/// } -/// ``` -pub struct DefaultHeaders { - ct: bool, - headers: HeaderMap, -} - -impl Default for DefaultHeaders { - fn default() -> Self { - DefaultHeaders { - ct: false, - headers: HeaderMap::new(), - } - } -} - -impl DefaultHeaders { - /// Construct `DefaultHeaders` middleware. - pub fn new() -> DefaultHeaders { - DefaultHeaders::default() - } - - /// Set a header. - #[inline] - #[cfg_attr(feature = "cargo-clippy", allow(clippy::match_wild_err_arm))] - pub fn header(mut self, key: K, value: V) -> Self - where - HeaderName: HttpTryFrom, - HeaderValue: HttpTryFrom, - { - match HeaderName::try_from(key) { - Ok(key) => match HeaderValue::try_from(value) { - Ok(value) => { - self.headers.append(key, value); - } - Err(_) => panic!("Can not create header value"), - }, - Err(_) => panic!("Can not create header name"), - } - self - } - - /// Set *CONTENT-TYPE* header if response does not contain this header. - pub fn content_type(mut self) -> Self { - self.ct = true; - self - } -} - -impl Middleware for DefaultHeaders { - fn response(&self, _: &HttpRequest, mut resp: HttpResponse) -> Result { - for (key, value) in self.headers.iter() { - if !resp.headers().contains_key(key) { - resp.headers_mut().insert(key, value.clone()); - } - } - // default content-type - if self.ct && !resp.headers().contains_key(CONTENT_TYPE) { - resp.headers_mut().insert( - CONTENT_TYPE, - HeaderValue::from_static("application/octet-stream"), - ); - } - Ok(Response::Done(resp)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use http::header::CONTENT_TYPE; - use test::TestRequest; - - #[test] - fn test_default_headers() { - let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001"); - - let req = TestRequest::default().finish(); - - let resp = HttpResponse::Ok().finish(); - let resp = match mw.response(&req, resp) { - Ok(Response::Done(resp)) => resp, - _ => panic!(), - }; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - - let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish(); - let resp = match mw.response(&req, resp) { - Ok(Response::Done(resp)) => resp, - _ => panic!(), - }; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); - } -} diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs deleted file mode 100644 index c7d19d334..000000000 --- a/src/middleware/errhandlers.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::collections::HashMap; - -use error::Result; -use http::StatusCode; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response}; - -type ErrorHandler = Fn(&HttpRequest, HttpResponse) -> Result; - -/// `Middleware` for allowing custom handlers for responses. -/// -/// You can use `ErrorHandlers::handler()` method to register a custom error -/// handler for specific status code. You can modify existing response or -/// create completely new one. -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::{ErrorHandlers, Response}; -/// use actix_web::{http, App, HttpRequest, HttpResponse, Result}; -/// -/// fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { -/// let mut builder = resp.into_builder(); -/// builder.header(http::header::CONTENT_TYPE, "application/json"); -/// Ok(Response::Done(builder.into())) -/// } -/// -/// fn main() { -/// let app = App::new() -/// .middleware( -/// ErrorHandlers::new() -/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500), -/// ) -/// .resource("/test", |r| { -/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -/// r.method(http::Method::HEAD) -/// .f(|_| HttpResponse::MethodNotAllowed()); -/// }) -/// .finish(); -/// } -/// ``` -pub struct ErrorHandlers { - handlers: HashMap>>, -} - -impl Default for ErrorHandlers { - fn default() -> Self { - ErrorHandlers { - handlers: HashMap::new(), - } - } -} - -impl ErrorHandlers { - /// Construct new `ErrorHandlers` instance - pub fn new() -> Self { - ErrorHandlers::default() - } - - /// Register error handler for specified status code - pub fn handler(mut self, status: StatusCode, handler: F) -> Self - where - F: Fn(&HttpRequest, HttpResponse) -> Result + 'static, - { - self.handlers.insert(status, Box::new(handler)); - self - } -} - -impl Middleware for ErrorHandlers { - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - if let Some(handler) = self.handlers.get(&resp.status()) { - handler(req, resp) - } else { - Ok(Response::Done(resp)) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use error::{Error, ErrorInternalServerError}; - use http::header::CONTENT_TYPE; - use http::StatusCode; - use httpmessage::HttpMessage; - use middleware::Started; - use test::{self, TestRequest}; - - fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { - let mut builder = resp.into_builder(); - builder.header(CONTENT_TYPE, "0001"); - Ok(Response::Done(builder.into())) - } - - #[test] - fn test_handler() { - let mw = - ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - - let mut req = TestRequest::default().finish(); - let resp = HttpResponse::InternalServerError().finish(); - let resp = match mw.response(&mut req, resp) { - Ok(Response::Done(resp)) => resp, - _ => panic!(), - }; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - - let resp = HttpResponse::Ok().finish(); - let resp = match mw.response(&mut req, resp) { - Ok(Response::Done(resp)) => resp, - _ => panic!(), - }; - assert!(!resp.headers().contains_key(CONTENT_TYPE)); - } - - struct MiddlewareOne; - - impl Middleware for MiddlewareOne { - fn start(&self, _: &HttpRequest) -> Result { - Err(ErrorInternalServerError("middleware error")) - } - } - - #[test] - fn test_middleware_start_error() { - let mut srv = test::TestServer::new(move |app| { - app.middleware( - ErrorHandlers::new() - .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), - ).middleware(MiddlewareOne) - .handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.headers().get(CONTENT_TYPE).unwrap(), "0001"); - } -} diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs deleted file mode 100644 index d890bebef..000000000 --- a/src/middleware/identity.rs +++ /dev/null @@ -1,387 +0,0 @@ -//! Request identity service for Actix applications. -//! -//! [**IdentityService**](struct.IdentityService.html) middleware can be -//! used with different policies types to store identity information. -//! -//! By default, only cookie identity policy is implemented. Other backend -//! implementations can be added separately. -//! -//! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html) -//! uses cookies as identity storage. -//! -//! To access current request identity -//! [**RequestIdentity**](trait.RequestIdentity.html) should be used. -//! *HttpRequest* implements *RequestIdentity* trait. -//! -//! ```rust -//! use actix_web::middleware::identity::RequestIdentity; -//! use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; -//! use actix_web::*; -//! -//! fn index(req: HttpRequest) -> Result { -//! // access request identity -//! if let Some(id) = req.identity() { -//! Ok(format!("Welcome! {}", id)) -//! } else { -//! Ok("Welcome Anonymous!".to_owned()) -//! } -//! } -//! -//! fn login(mut req: HttpRequest) -> HttpResponse { -//! req.remember("User1".to_owned()); // <- remember identity -//! HttpResponse::Ok().finish() -//! } -//! -//! fn logout(mut req: HttpRequest) -> HttpResponse { -//! req.forget(); // <- remove identity -//! HttpResponse::Ok().finish() -//! } -//! -//! fn main() { -//! let app = App::new().middleware(IdentityService::new( -//! // <- create identity middleware -//! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend -//! .name("auth-cookie") -//! .secure(false), -//! )); -//! } -//! ``` -use std::rc::Rc; - -use cookie::{Cookie, CookieJar, Key}; -use futures::future::{err as FutErr, ok as FutOk, FutureResult}; -use futures::Future; -use time::Duration; - -use error::{Error, Result}; -use http::header::{self, HeaderValue}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response, Started}; - -/// The helper trait to obtain your identity from a request. -/// -/// ```rust -/// use actix_web::middleware::identity::RequestIdentity; -/// use actix_web::*; -/// -/// fn index(req: HttpRequest) -> Result { -/// // access request identity -/// if let Some(id) = req.identity() { -/// Ok(format!("Welcome! {}", id)) -/// } else { -/// Ok("Welcome Anonymous!".to_owned()) -/// } -/// } -/// -/// fn login(mut req: HttpRequest) -> HttpResponse { -/// req.remember("User1".to_owned()); // <- remember identity -/// HttpResponse::Ok().finish() -/// } -/// -/// fn logout(mut req: HttpRequest) -> HttpResponse { -/// req.forget(); // <- remove identity -/// HttpResponse::Ok().finish() -/// } -/// # fn main() {} -/// ``` -pub trait RequestIdentity { - /// Return the claimed identity of the user associated request or - /// ``None`` if no identity can be found associated with the request. - fn identity(&self) -> Option; - - /// Remember identity. - fn remember(&self, identity: String); - - /// This method is used to 'forget' the current identity on subsequent - /// requests. - fn forget(&self); -} - -impl RequestIdentity for HttpRequest { - fn identity(&self) -> Option { - if let Some(id) = self.extensions().get::() { - return id.0.identity().map(|s| s.to_owned()); - } - None - } - - fn remember(&self, identity: String) { - if let Some(id) = self.extensions_mut().get_mut::() { - return id.0.as_mut().remember(identity); - } - } - - fn forget(&self) { - if let Some(id) = self.extensions_mut().get_mut::() { - return id.0.forget(); - } - } -} - -/// An identity -pub trait Identity: 'static { - /// Return the claimed identity of the user associated request or - /// ``None`` if no identity can be found associated with the request. - fn identity(&self) -> Option<&str>; - - /// Remember identity. - fn remember(&mut self, key: String); - - /// This method is used to 'forget' the current identity on subsequent - /// requests. - fn forget(&mut self); - - /// Write session to storage backend. - fn write(&mut self, resp: HttpResponse) -> Result; -} - -/// Identity policy definition. -pub trait IdentityPolicy: Sized + 'static { - /// The associated identity - type Identity: Identity; - - /// The return type of the middleware - type Future: Future; - - /// Parse the session from request and load data from a service identity. - fn from_request(&self, request: &HttpRequest) -> Self::Future; -} - -/// Request identity middleware -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; -/// use actix_web::App; -/// -/// fn main() { -/// let app = App::new().middleware(IdentityService::new( -/// // <- create identity middleware -/// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend -/// .name("auth-cookie") -/// .secure(false), -/// )); -/// } -/// ``` -pub struct IdentityService { - backend: T, -} - -impl IdentityService { - /// Create new identity service with specified backend. - pub fn new(backend: T) -> Self { - IdentityService { backend } - } -} - -struct IdentityBox(Box); - -impl> Middleware for IdentityService { - fn start(&self, req: &HttpRequest) -> Result { - let req = req.clone(); - let fut = self.backend.from_request(&req).then(move |res| match res { - Ok(id) => { - req.extensions_mut().insert(IdentityBox(Box::new(id))); - FutOk(None) - } - Err(err) => FutErr(err), - }); - Ok(Started::Future(Box::new(fut))) - } - - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - if let Some(ref mut id) = req.extensions_mut().get_mut::() { - id.0.as_mut().write(resp) - } else { - Ok(Response::Done(resp)) - } - } -} - -#[doc(hidden)] -/// Identity that uses private cookies as identity storage. -pub struct CookieIdentity { - changed: bool, - identity: Option, - inner: Rc, -} - -impl Identity for CookieIdentity { - fn identity(&self) -> Option<&str> { - self.identity.as_ref().map(|s| s.as_ref()) - } - - fn remember(&mut self, value: String) { - self.changed = true; - self.identity = Some(value); - } - - fn forget(&mut self) { - self.changed = true; - self.identity = None; - } - - fn write(&mut self, mut resp: HttpResponse) -> Result { - if self.changed { - let _ = self.inner.set_cookie(&mut resp, self.identity.take()); - } - Ok(Response::Done(resp)) - } -} - -struct CookieIdentityInner { - key: Key, - name: String, - path: String, - domain: Option, - secure: bool, - max_age: Option, -} - -impl CookieIdentityInner { - fn new(key: &[u8]) -> CookieIdentityInner { - CookieIdentityInner { - key: Key::from_master(key), - name: "actix-identity".to_owned(), - path: "/".to_owned(), - domain: None, - secure: true, - max_age: None, - } - } - - fn set_cookie(&self, resp: &mut HttpResponse, id: Option) -> Result<()> { - let some = id.is_some(); - { - let id = id.unwrap_or_else(String::new); - let mut cookie = Cookie::new(self.name.clone(), id); - cookie.set_path(self.path.clone()); - cookie.set_secure(self.secure); - cookie.set_http_only(true); - - if let Some(ref domain) = self.domain { - cookie.set_domain(domain.clone()); - } - - if let Some(max_age) = self.max_age { - cookie.set_max_age(max_age); - } - - let mut jar = CookieJar::new(); - if some { - jar.private(&self.key).add(cookie); - } else { - jar.add_original(cookie.clone()); - jar.private(&self.key).remove(cookie); - } - - for cookie in jar.delta() { - let val = HeaderValue::from_str(&cookie.to_string())?; - resp.headers_mut().append(header::SET_COOKIE, val); - } - } - - Ok(()) - } - - fn load(&self, req: &HttpRequest) -> Option { - if let Ok(cookies) = req.cookies() { - for cookie in cookies.iter() { - if cookie.name() == self.name { - let mut jar = CookieJar::new(); - jar.add_original(cookie.clone()); - - let cookie_opt = jar.private(&self.key).get(&self.name); - if let Some(cookie) = cookie_opt { - return Some(cookie.value().into()); - } - } - } - } - None - } -} - -/// Use cookies for request identity storage. -/// -/// The constructors take a key as an argument. -/// This is the private key for cookie - when this value is changed, -/// all identities are lost. The constructors will panic if the key is less -/// than 32 bytes in length. -/// -/// # Example -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; -/// use actix_web::App; -/// -/// fn main() { -/// let app = App::new().middleware(IdentityService::new( -/// // <- create identity middleware -/// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy -/// .domain("www.rust-lang.org") -/// .name("actix_auth") -/// .path("/") -/// .secure(true), -/// )); -/// } -/// ``` -pub struct CookieIdentityPolicy(Rc); - -impl CookieIdentityPolicy { - /// Construct new `CookieIdentityPolicy` instance. - /// - /// Panics if key length is less than 32 bytes. - pub fn new(key: &[u8]) -> CookieIdentityPolicy { - CookieIdentityPolicy(Rc::new(CookieIdentityInner::new(key))) - } - - /// Sets the `path` field in the session cookie being built. - pub fn path>(mut self, value: S) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().path = value.into(); - self - } - - /// Sets the `name` field in the session cookie being built. - pub fn name>(mut self, value: S) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().name = value.into(); - self - } - - /// Sets the `domain` field in the session cookie being built. - pub fn domain>(mut self, value: S) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into()); - self - } - - /// Sets the `secure` field in the session cookie being built. - /// - /// If the `secure` field is set, a cookie will only be transmitted when the - /// connection is secure - i.e. `https` - pub fn secure(mut self, value: bool) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().secure = value; - self - } - - /// Sets the `max-age` field in the session cookie being built. - pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); - self - } -} - -impl IdentityPolicy for CookieIdentityPolicy { - type Identity = CookieIdentity; - type Future = FutureResult; - - fn from_request(&self, req: &HttpRequest) -> Self::Future { - let identity = self.0.load(req); - FutOk(CookieIdentity { - identity, - changed: false, - inner: Rc::clone(&self.0), - }) - } -} diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs deleted file mode 100644 index b7bb1bb80..000000000 --- a/src/middleware/logger.rs +++ /dev/null @@ -1,384 +0,0 @@ -//! Request logging middleware -use std::collections::HashSet; -use std::env; -use std::fmt::{self, Display, Formatter}; - -use regex::Regex; -use time; - -use error::Result; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Finished, Middleware, Started}; - -/// `Middleware` for logging request and response info to the terminal. -/// -/// `Logger` middleware uses standard log crate to log information. You should -/// enable logger for `actix_web` package to see access log. -/// ([`env_logger`](https://docs.rs/env_logger/*/env_logger/) or similar) -/// -/// ## Usage -/// -/// Create `Logger` middleware with the specified `format`. -/// Default `Logger` could be created with `default` method, it uses the -/// default format: -/// -/// ```ignore -/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T -/// ``` -/// ```rust -/// # extern crate actix_web; -/// extern crate env_logger; -/// use actix_web::middleware::Logger; -/// use actix_web::App; -/// -/// fn main() { -/// std::env::set_var("RUST_LOG", "actix_web=info"); -/// env_logger::init(); -/// -/// let app = App::new() -/// .middleware(Logger::default()) -/// .middleware(Logger::new("%a %{User-Agent}i")) -/// .finish(); -/// } -/// ``` -/// -/// ## Format -/// -/// `%%` The percent sign -/// -/// `%a` Remote IP-address (IP-address of proxy if using reverse proxy) -/// -/// `%t` Time when the request was started to process -/// -/// `%r` First line of request -/// -/// `%s` Response status code -/// -/// `%b` Size of response in bytes, including HTTP headers -/// -/// `%T` Time taken to serve the request, in seconds with floating fraction in -/// .06f format -/// -/// `%D` Time taken to serve the request, in milliseconds -/// -/// `%{FOO}i` request.headers['FOO'] -/// -/// `%{FOO}o` response.headers['FOO'] -/// -/// `%{FOO}e` os.environ['FOO'] -/// -pub struct Logger { - format: Format, - exclude: HashSet, -} - -impl Logger { - /// Create `Logger` middleware with the specified `format`. - pub fn new(format: &str) -> Logger { - Logger { - format: Format::new(format), - exclude: HashSet::new(), - } - } - - /// Ignore and do not log access info for specified path. - pub fn exclude>(mut self, path: T) -> Self { - self.exclude.insert(path.into()); - self - } -} - -impl Default for Logger { - /// Create `Logger` middleware with format: - /// - /// ```ignore - /// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T - /// ``` - fn default() -> Logger { - Logger { - format: Format::default(), - exclude: HashSet::new(), - } - } -} - -struct StartTime(time::Tm); - -impl Logger { - fn log(&self, req: &HttpRequest, resp: &HttpResponse) { - if let Some(entry_time) = req.extensions().get::() { - let render = |fmt: &mut Formatter| { - for unit in &self.format.0 { - unit.render(fmt, req, resp, entry_time.0)?; - } - Ok(()) - }; - info!("{}", FormatDisplay(&render)); - } - } -} - -impl Middleware for Logger { - fn start(&self, req: &HttpRequest) -> Result { - if !self.exclude.contains(req.path()) { - req.extensions_mut().insert(StartTime(time::now())); - } - Ok(Started::Done) - } - - fn finish(&self, req: &HttpRequest, resp: &HttpResponse) -> Finished { - self.log(req, resp); - Finished::Done - } -} - -/// A formatting style for the `Logger`, consisting of multiple -/// `FormatText`s concatenated into one line. -#[derive(Clone)] -#[doc(hidden)] -struct Format(Vec); - -impl Default for Format { - /// Return the default formatting style for the `Logger`: - fn default() -> Format { - Format::new(r#"%a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#) - } -} - -impl Format { - /// Create a `Format` from a format string. - /// - /// Returns `None` if the format string syntax is incorrect. - pub fn new(s: &str) -> Format { - trace!("Access log format: {}", s); - let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrsbTD]?)").unwrap(); - - let mut idx = 0; - let mut results = Vec::new(); - for cap in fmt.captures_iter(s) { - let m = cap.get(0).unwrap(); - let pos = m.start(); - if idx != pos { - results.push(FormatText::Str(s[idx..pos].to_owned())); - } - idx = m.end(); - - if let Some(key) = cap.get(2) { - results.push(match cap.get(3).unwrap().as_str() { - "i" => FormatText::RequestHeader(key.as_str().to_owned()), - "o" => FormatText::ResponseHeader(key.as_str().to_owned()), - "e" => FormatText::EnvironHeader(key.as_str().to_owned()), - _ => unreachable!(), - }) - } else { - let m = cap.get(1).unwrap(); - results.push(match m.as_str() { - "%" => FormatText::Percent, - "a" => FormatText::RemoteAddr, - "t" => FormatText::RequestTime, - "r" => FormatText::RequestLine, - "s" => FormatText::ResponseStatus, - "b" => FormatText::ResponseSize, - "T" => FormatText::Time, - "D" => FormatText::TimeMillis, - _ => FormatText::Str(m.as_str().to_owned()), - }); - } - } - if idx != s.len() { - results.push(FormatText::Str(s[idx..].to_owned())); - } - - Format(results) - } -} - -/// A string of text to be logged. This is either one of the data -/// fields supported by the `Logger`, or a custom `String`. -#[doc(hidden)] -#[derive(Debug, Clone)] -pub enum FormatText { - Str(String), - Percent, - RequestLine, - RequestTime, - ResponseStatus, - ResponseSize, - Time, - TimeMillis, - RemoteAddr, - RequestHeader(String), - ResponseHeader(String), - EnvironHeader(String), -} - -impl FormatText { - fn render( - &self, fmt: &mut Formatter, req: &HttpRequest, resp: &HttpResponse, - entry_time: time::Tm, - ) -> Result<(), fmt::Error> { - match *self { - FormatText::Str(ref string) => fmt.write_str(string), - FormatText::Percent => "%".fmt(fmt), - FormatText::RequestLine => { - if req.query_string().is_empty() { - fmt.write_fmt(format_args!( - "{} {} {:?}", - req.method(), - req.path(), - req.version() - )) - } else { - fmt.write_fmt(format_args!( - "{} {}?{} {:?}", - req.method(), - req.path(), - req.query_string(), - req.version() - )) - } - } - FormatText::ResponseStatus => resp.status().as_u16().fmt(fmt), - FormatText::ResponseSize => resp.response_size().fmt(fmt), - FormatText::Time => { - let rt = time::now() - entry_time; - let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000_000.0; - fmt.write_fmt(format_args!("{:.6}", rt)) - } - FormatText::TimeMillis => { - let rt = time::now() - entry_time; - let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000.0; - fmt.write_fmt(format_args!("{:.6}", rt)) - } - FormatText::RemoteAddr => { - if let Some(remote) = req.connection_info().remote() { - return remote.fmt(fmt); - } else { - "-".fmt(fmt) - } - } - FormatText::RequestTime => entry_time - .strftime("[%d/%b/%Y:%H:%M:%S %z]") - .unwrap() - .fmt(fmt), - FormatText::RequestHeader(ref name) => { - let s = if let Some(val) = req.headers().get(name) { - if let Ok(s) = val.to_str() { - s - } else { - "-" - } - } else { - "-" - }; - fmt.write_fmt(format_args!("{}", s)) - } - FormatText::ResponseHeader(ref name) => { - let s = if let Some(val) = resp.headers().get(name) { - if let Ok(s) = val.to_str() { - s - } else { - "-" - } - } else { - "-" - }; - fmt.write_fmt(format_args!("{}", s)) - } - FormatText::EnvironHeader(ref name) => { - if let Ok(val) = env::var(name) { - fmt.write_fmt(format_args!("{}", val)) - } else { - "-".fmt(fmt) - } - } - } - } -} - -pub(crate) struct FormatDisplay<'a>(&'a Fn(&mut Formatter) -> Result<(), fmt::Error>); - -impl<'a> fmt::Display for FormatDisplay<'a> { - fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { - (self.0)(fmt) - } -} - -#[cfg(test)] -mod tests { - use time; - - use super::*; - use http::{header, StatusCode}; - use test::TestRequest; - - #[test] - fn test_logger() { - let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); - - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ).finish(); - let resp = HttpResponse::build(StatusCode::OK) - .header("X-Test", "ttt") - .force_close() - .finish(); - - match logger.start(&req) { - Ok(Started::Done) => (), - _ => panic!(), - }; - match logger.finish(&req, &resp) { - Finished::Done => (), - _ => panic!(), - } - let entry_time = time::now(); - let render = |fmt: &mut Formatter| { - for unit in &logger.format.0 { - unit.render(fmt, &req, &resp, entry_time)?; - } - Ok(()) - }; - let s = format!("{}", FormatDisplay(&render)); - assert!(s.contains("ACTIX-WEB ttt")); - } - - #[test] - fn test_default_format() { - let format = Format::default(); - - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ).finish(); - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); - let entry_time = time::now(); - - let render = |fmt: &mut Formatter| { - for unit in &format.0 { - unit.render(fmt, &req, &resp, entry_time)?; - } - Ok(()) - }; - let s = format!("{}", FormatDisplay(&render)); - assert!(s.contains("GET / HTTP/1.1")); - assert!(s.contains("200 0")); - assert!(s.contains("ACTIX-WEB")); - - let req = TestRequest::with_uri("/?test").finish(); - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); - let entry_time = time::now(); - - let render = |fmt: &mut Formatter| { - for unit in &format.0 { - unit.render(fmt, &req, &resp, entry_time)?; - } - Ok(()) - }; - let s = format!("{}", FormatDisplay(&render)); - assert!(s.contains("GET /?test HTTP/1.1")); - } -} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs deleted file mode 100644 index c69dbb3e0..000000000 --- a/src/middleware/mod.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! Middlewares -use futures::Future; - -use error::{Error, Result}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; - -mod logger; - -pub mod cors; -pub mod csrf; -mod defaultheaders; -mod errhandlers; -#[cfg(feature = "session")] -pub mod identity; -#[cfg(feature = "session")] -pub mod session; -pub use self::defaultheaders::DefaultHeaders; -pub use self::errhandlers::ErrorHandlers; -pub use self::logger::Logger; - -/// Middleware start result -pub enum Started { - /// Middleware is completed, continue to next middleware - Done, - /// New http response got generated. If middleware generates response - /// handler execution halts. - Response(HttpResponse), - /// Execution completed, runs future to completion. - Future(Box, Error = Error>>), -} - -/// Middleware execution result -pub enum Response { - /// New http response got generated - Done(HttpResponse), - /// Result is a future that resolves to a new http response - Future(Box>), -} - -/// Middleware finish result -pub enum Finished { - /// Execution completed - Done, - /// Execution completed, but run future to completion - Future(Box>), -} - -/// Middleware definition -#[allow(unused_variables)] -pub trait Middleware: 'static { - /// Method is called when request is ready. It may return - /// future, which should resolve before next middleware get called. - fn start(&self, req: &HttpRequest) -> Result { - Ok(Started::Done) - } - - /// Method is called when handler returns response, - /// but before sending http message to peer. - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - Ok(Response::Done(resp)) - } - - /// Method is called after body stream get sent to peer. - fn finish(&self, req: &HttpRequest, resp: &HttpResponse) -> Finished { - Finished::Done - } -} diff --git a/src/middleware/session.rs b/src/middleware/session.rs deleted file mode 100644 index e8b0e5558..000000000 --- a/src/middleware/session.rs +++ /dev/null @@ -1,617 +0,0 @@ -//! User sessions. -//! -//! Actix provides a general solution for session management. The -//! [**SessionStorage**](struct.SessionStorage.html) -//! middleware can be used with different backend types to store session -//! data in different backends. -//! -//! By default, only cookie session backend is implemented. Other -//! backend implementations can be added. -//! -//! [**CookieSessionBackend**](struct.CookieSessionBackend.html) -//! uses cookies as session storage. `CookieSessionBackend` creates sessions -//! which are limited to storing fewer than 4000 bytes of data, as the payload -//! must fit into a single cookie. An internal server error is generated if a -//! session contains more than 4000 bytes. -//! -//! A cookie may have a security policy of *signed* or *private*. Each has -//! a respective `CookieSessionBackend` constructor. -//! -//! A *signed* cookie may be viewed but not modified by the client. A *private* -//! cookie may neither be viewed nor modified by the client. -//! -//! The constructors take a key as an argument. This is the private key -//! for cookie session - when this value is changed, all session data is lost. -//! -//! In general, you create a `SessionStorage` middleware and initialize it -//! with specific backend implementation, such as a `CookieSessionBackend`. -//! To access session data, -//! [*HttpRequest::session()*](trait.RequestSession.html#tymethod.session) -//! must be used. This method returns a -//! [*Session*](struct.Session.html) object, which allows us to get or set -//! session data. -//! -//! ```rust -//! # extern crate actix_web; -//! use actix_web::{actix, server, App, HttpRequest, Result}; -//! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend}; -//! -//! fn index(req: HttpRequest) -> Result<&'static str> { -//! // access session data -//! if let Some(count) = req.session().get::("counter")? { -//! println!("SESSION value: {}", count); -//! req.session().set("counter", count+1)?; -//! } else { -//! req.session().set("counter", 1)?; -//! } -//! -//! Ok("Welcome!") -//! } -//! -//! fn main() { -//! actix::System::run(|| { -//! server::new( -//! || App::new().middleware( -//! SessionStorage::new( // <- create session middleware -//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend -//! .secure(false) -//! ))) -//! .bind("127.0.0.1:59880").unwrap() -//! .start(); -//! # actix::System::current().stop(); -//! }); -//! } -//! ``` -use std::cell::RefCell; -use std::collections::HashMap; -use std::marker::PhantomData; -use std::rc::Rc; -use std::sync::Arc; - -use cookie::{Cookie, CookieJar, Key, SameSite}; -use futures::future::{err as FutErr, ok as FutOk, FutureResult}; -use futures::Future; -use http::header::{self, HeaderValue}; -use serde::de::DeserializeOwned; -use serde::Serialize; -use serde_json; -use serde_json::error::Error as JsonError; -use time::Duration; - -use error::{Error, ResponseError, Result}; -use handler::FromRequest; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response, Started}; - -/// The helper trait to obtain your session data from a request. -/// -/// ```rust -/// use actix_web::middleware::session::RequestSession; -/// use actix_web::*; -/// -/// fn index(mut req: HttpRequest) -> Result<&'static str> { -/// // access session data -/// if let Some(count) = req.session().get::("counter")? { -/// req.session().set("counter", count + 1)?; -/// } else { -/// req.session().set("counter", 1)?; -/// } -/// -/// Ok("Welcome!") -/// } -/// # fn main() {} -/// ``` -pub trait RequestSession { - /// Get the session from the request - fn session(&self) -> Session; -} - -impl RequestSession for HttpRequest { - fn session(&self) -> Session { - if let Some(s_impl) = self.extensions().get::>() { - return Session(SessionInner::Session(Arc::clone(&s_impl))); - } - Session(SessionInner::None) - } -} - -/// The high-level interface you use to modify session data. -/// -/// Session object could be obtained with -/// [`RequestSession::session`](trait.RequestSession.html#tymethod.session) -/// method. `RequestSession` trait is implemented for `HttpRequest`. -/// -/// ```rust -/// use actix_web::middleware::session::RequestSession; -/// use actix_web::*; -/// -/// fn index(mut req: HttpRequest) -> Result<&'static str> { -/// // access session data -/// if let Some(count) = req.session().get::("counter")? { -/// req.session().set("counter", count + 1)?; -/// } else { -/// req.session().set("counter", 1)?; -/// } -/// -/// Ok("Welcome!") -/// } -/// # fn main() {} -/// ``` -pub struct Session(SessionInner); - -enum SessionInner { - Session(Arc), - None, -} - -impl Session { - /// Get a `value` from the session. - pub fn get(&self, key: &str) -> Result> { - match self.0 { - SessionInner::Session(ref sess) => { - if let Some(s) = sess.as_ref().0.borrow().get(key) { - Ok(Some(serde_json::from_str(s)?)) - } else { - Ok(None) - } - } - SessionInner::None => Ok(None), - } - } - - /// Set a `value` from the session. - pub fn set(&self, key: &str, value: T) -> Result<()> { - match self.0 { - SessionInner::Session(ref sess) => { - sess.as_ref() - .0 - .borrow_mut() - .set(key, serde_json::to_string(&value)?); - Ok(()) - } - SessionInner::None => Ok(()), - } - } - - /// Remove value from the session. - pub fn remove(&self, key: &str) { - match self.0 { - SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().remove(key), - SessionInner::None => (), - } - } - - /// Clear the session. - pub fn clear(&self) { - match self.0 { - SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().clear(), - SessionInner::None => (), - } - } -} - -/// Extractor implementation for Session type. -/// -/// ```rust -/// # use actix_web::*; -/// use actix_web::middleware::session::Session; -/// -/// fn index(session: Session) -> Result<&'static str> { -/// // access session data -/// if let Some(count) = session.get::("counter")? { -/// session.set("counter", count + 1)?; -/// } else { -/// session.set("counter", 1)?; -/// } -/// -/// Ok("Welcome!") -/// } -/// # fn main() {} -/// ``` -impl FromRequest for Session { - type Config = (); - type Result = Session; - - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - req.session() - } -} - -struct SessionImplCell(RefCell>); - -/// Session storage middleware -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::session::{CookieSessionBackend, SessionStorage}; -/// use actix_web::App; -/// -/// fn main() { -/// let app = App::new().middleware(SessionStorage::new( -/// // <- create session middleware -/// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend -/// .secure(false), -/// )); -/// } -/// ``` -pub struct SessionStorage(T, PhantomData); - -impl> SessionStorage { - /// Create session storage - pub fn new(backend: T) -> SessionStorage { - SessionStorage(backend, PhantomData) - } -} - -impl> Middleware for SessionStorage { - fn start(&self, req: &HttpRequest) -> Result { - let mut req = req.clone(); - - let fut = self.0.from_request(&mut req).then(move |res| match res { - Ok(sess) => { - req.extensions_mut() - .insert(Arc::new(SessionImplCell(RefCell::new(Box::new(sess))))); - FutOk(None) - } - Err(err) => FutErr(err), - }); - Ok(Started::Future(Box::new(fut))) - } - - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - if let Some(s_box) = req.extensions().get::>() { - s_box.0.borrow_mut().write(resp) - } else { - Ok(Response::Done(resp)) - } - } -} - -/// A simple key-value storage interface that is internally used by `Session`. -pub trait SessionImpl: 'static { - /// Get session value by key - fn get(&self, key: &str) -> Option<&str>; - - /// Set session value - fn set(&mut self, key: &str, value: String); - - /// Remove specific key from session - fn remove(&mut self, key: &str); - - /// Remove all values from session - fn clear(&mut self); - - /// Write session to storage backend. - fn write(&self, resp: HttpResponse) -> Result; -} - -/// Session's storage backend trait definition. -pub trait SessionBackend: Sized + 'static { - /// Session item - type Session: SessionImpl; - /// Future that reads session - type ReadFuture: Future; - - /// Parse the session from request and load data from a storage backend. - fn from_request(&self, request: &mut HttpRequest) -> Self::ReadFuture; -} - -/// Session that uses signed cookies as session storage -pub struct CookieSession { - changed: bool, - state: HashMap, - inner: Rc, -} - -/// Errors that can occur during handling cookie session -#[derive(Fail, Debug)] -pub enum CookieSessionError { - /// Size of the serialized session is greater than 4000 bytes. - #[fail(display = "Size of the serialized session is greater than 4000 bytes.")] - Overflow, - /// Fail to serialize session. - #[fail(display = "Fail to serialize session")] - Serialize(JsonError), -} - -impl ResponseError for CookieSessionError {} - -impl SessionImpl for CookieSession { - fn get(&self, key: &str) -> Option<&str> { - if let Some(s) = self.state.get(key) { - Some(s) - } else { - None - } - } - - fn set(&mut self, key: &str, value: String) { - self.changed = true; - self.state.insert(key.to_owned(), value); - } - - fn remove(&mut self, key: &str) { - self.changed = true; - self.state.remove(key); - } - - fn clear(&mut self) { - self.changed = true; - self.state.clear() - } - - fn write(&self, mut resp: HttpResponse) -> Result { - if self.changed { - let _ = self.inner.set_cookie(&mut resp, &self.state); - } - Ok(Response::Done(resp)) - } -} - -enum CookieSecurity { - Signed, - Private, -} - -struct CookieSessionInner { - key: Key, - security: CookieSecurity, - name: String, - path: String, - domain: Option, - secure: bool, - http_only: bool, - max_age: Option, - same_site: Option, -} - -impl CookieSessionInner { - fn new(key: &[u8], security: CookieSecurity) -> CookieSessionInner { - CookieSessionInner { - security, - key: Key::from_master(key), - name: "actix-session".to_owned(), - path: "/".to_owned(), - domain: None, - secure: true, - http_only: true, - max_age: None, - same_site: None, - } - } - - fn set_cookie( - &self, resp: &mut HttpResponse, state: &HashMap, - ) -> Result<()> { - let value = - serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?; - if value.len() > 4064 { - return Err(CookieSessionError::Overflow.into()); - } - - let mut cookie = Cookie::new(self.name.clone(), value); - cookie.set_path(self.path.clone()); - cookie.set_secure(self.secure); - cookie.set_http_only(self.http_only); - - if let Some(ref domain) = self.domain { - cookie.set_domain(domain.clone()); - } - - if let Some(max_age) = self.max_age { - cookie.set_max_age(max_age); - } - - if let Some(same_site) = self.same_site { - cookie.set_same_site(same_site); - } - - let mut jar = CookieJar::new(); - - match self.security { - CookieSecurity::Signed => jar.signed(&self.key).add(cookie), - CookieSecurity::Private => jar.private(&self.key).add(cookie), - } - - for cookie in jar.delta() { - let val = HeaderValue::from_str(&cookie.encoded().to_string())?; - resp.headers_mut().append(header::SET_COOKIE, val); - } - - Ok(()) - } - - fn load(&self, req: &mut HttpRequest) -> HashMap { - if let Ok(cookies) = req.cookies() { - for cookie in cookies.iter() { - if cookie.name() == self.name { - let mut jar = CookieJar::new(); - jar.add_original(cookie.clone()); - - let cookie_opt = match self.security { - CookieSecurity::Signed => jar.signed(&self.key).get(&self.name), - CookieSecurity::Private => { - jar.private(&self.key).get(&self.name) - } - }; - if let Some(cookie) = cookie_opt { - if let Ok(val) = serde_json::from_str(cookie.value()) { - return val; - } - } - } - } - } - HashMap::new() - } -} - -/// Use cookies for session storage. -/// -/// `CookieSessionBackend` creates sessions which are limited to storing -/// fewer than 4000 bytes of data (as the payload must fit into a single -/// cookie). An Internal Server Error is generated if the session contains more -/// than 4000 bytes. -/// -/// A cookie may have a security policy of *signed* or *private*. Each has a -/// respective `CookieSessionBackend` constructor. -/// -/// A *signed* cookie is stored on the client as plaintext alongside -/// a signature such that the cookie may be viewed but not modified by the -/// client. -/// -/// A *private* cookie is stored on the client as encrypted text -/// such that it may neither be viewed nor modified by the client. -/// -/// The constructors take a key as an argument. -/// This is the private key for cookie session - when this value is changed, -/// all session data is lost. The constructors will panic if the key is less -/// than 32 bytes in length. -/// -/// The backend relies on `cookie` crate to create and read cookies. -/// By default all cookies are percent encoded, but certain symbols may -/// cause troubles when reading cookie, if they are not properly percent encoded. -/// -/// # Example -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::session::CookieSessionBackend; -/// -/// # fn main() { -/// let backend: CookieSessionBackend = CookieSessionBackend::signed(&[0; 32]) -/// .domain("www.rust-lang.org") -/// .name("actix_session") -/// .path("/") -/// .secure(true); -/// # } -/// ``` -pub struct CookieSessionBackend(Rc); - -impl CookieSessionBackend { - /// Construct new *signed* `CookieSessionBackend` instance. - /// - /// Panics if key length is less than 32 bytes. - pub fn signed(key: &[u8]) -> CookieSessionBackend { - CookieSessionBackend(Rc::new(CookieSessionInner::new( - key, - CookieSecurity::Signed, - ))) - } - - /// Construct new *private* `CookieSessionBackend` instance. - /// - /// Panics if key length is less than 32 bytes. - pub fn private(key: &[u8]) -> CookieSessionBackend { - CookieSessionBackend(Rc::new(CookieSessionInner::new( - key, - CookieSecurity::Private, - ))) - } - - /// Sets the `path` field in the session cookie being built. - pub fn path>(mut self, value: S) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().path = value.into(); - self - } - - /// Sets the `name` field in the session cookie being built. - pub fn name>(mut self, value: S) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().name = value.into(); - self - } - - /// Sets the `domain` field in the session cookie being built. - pub fn domain>(mut self, value: S) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into()); - self - } - - /// Sets the `secure` field in the session cookie being built. - /// - /// If the `secure` field is set, a cookie will only be transmitted when the - /// connection is secure - i.e. `https` - pub fn secure(mut self, value: bool) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().secure = value; - self - } - - /// Sets the `http_only` field in the session cookie being built. - pub fn http_only(mut self, value: bool) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().http_only = value; - self - } - - /// Sets the `same_site` field in the session cookie being built. - pub fn same_site(mut self, value: SameSite) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().same_site = Some(value); - self - } - - /// Sets the `max-age` field in the session cookie being built. - pub fn max_age(mut self, value: Duration) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); - self - } -} - -impl SessionBackend for CookieSessionBackend { - type Session = CookieSession; - type ReadFuture = FutureResult; - - fn from_request(&self, req: &mut HttpRequest) -> Self::ReadFuture { - let state = self.0.load(req); - FutOk(CookieSession { - changed: false, - inner: Rc::clone(&self.0), - state, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use application::App; - use test; - - #[test] - fn cookie_session() { - let mut srv = test::TestServer::with_factory(|| { - App::new() - .middleware(SessionStorage::new( - CookieSessionBackend::signed(&[0; 32]).secure(false), - )).resource("/", |r| { - r.f(|req| { - let _ = req.session().set("counter", 100); - "test" - }) - }) - }); - - let request = srv.get().uri(srv.url("/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.cookie("actix-session").is_some()); - } - - #[test] - fn cookie_session_extractor() { - let mut srv = test::TestServer::with_factory(|| { - App::new() - .middleware(SessionStorage::new( - CookieSessionBackend::signed(&[0; 32]).secure(false), - )).resource("/", |r| { - r.with(|ses: Session| { - let _ = ses.set("counter", 100); - "test" - }) - }) - }); - - let request = srv.get().uri(srv.url("/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.cookie("actix-session").is_some()); - } -} diff --git a/src/multipart.rs b/src/multipart.rs deleted file mode 100644 index 862f60ecb..000000000 --- a/src/multipart.rs +++ /dev/null @@ -1,815 +0,0 @@ -//! Multipart requests support -use std::cell::{RefCell, UnsafeCell}; -use std::marker::PhantomData; -use std::rc::Rc; -use std::{cmp, fmt}; - -use bytes::Bytes; -use futures::task::{current as current_task, Task}; -use futures::{Async, Poll, Stream}; -use http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue}; -use http::HttpTryFrom; -use httparse; -use mime; - -use error::{MultipartError, ParseError, PayloadError}; -use payload::PayloadBuffer; - -const MAX_HEADERS: usize = 32; - -/// The server-side implementation of `multipart/form-data` requests. -/// -/// This will parse the incoming stream into `MultipartItem` instances via its -/// Stream implementation. -/// `MultipartItem::Field` contains multipart field. `MultipartItem::Multipart` -/// is used for nested multipart streams. -pub struct Multipart { - safety: Safety, - error: Option, - inner: Option>>>, -} - -/// -pub enum MultipartItem { - /// Multipart field - Field(Field), - /// Nested multipart stream - Nested(Multipart), -} - -enum InnerMultipartItem { - None, - Field(Rc>>), - Multipart(Rc>>), -} - -#[derive(PartialEq, Debug)] -enum InnerState { - /// Stream eof - Eof, - /// Skip data until first boundary - FirstBoundary, - /// Reading boundary - Boundary, - /// Reading Headers, - Headers, -} - -struct InnerMultipart { - payload: PayloadRef, - boundary: String, - state: InnerState, - item: InnerMultipartItem, -} - -impl Multipart<()> { - /// Extract boundary info from headers. - pub fn boundary(headers: &HeaderMap) -> Result { - if let Some(content_type) = headers.get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - if let Ok(ct) = content_type.parse::() { - if let Some(boundary) = ct.get_param(mime::BOUNDARY) { - Ok(boundary.as_str().to_owned()) - } else { - Err(MultipartError::Boundary) - } - } else { - Err(MultipartError::ParseContentType) - } - } else { - Err(MultipartError::ParseContentType) - } - } else { - Err(MultipartError::NoContentType) - } - } -} - -impl Multipart -where - S: Stream, -{ - /// Create multipart instance for boundary. - pub fn new(boundary: Result, stream: S) -> Multipart { - match boundary { - Ok(boundary) => Multipart { - error: None, - safety: Safety::new(), - inner: Some(Rc::new(RefCell::new(InnerMultipart { - boundary, - payload: PayloadRef::new(PayloadBuffer::new(stream)), - state: InnerState::FirstBoundary, - item: InnerMultipartItem::None, - }))), - }, - Err(err) => Multipart { - error: Some(err), - safety: Safety::new(), - inner: None, - }, - } - } -} - -impl Stream for Multipart -where - S: Stream, -{ - type Item = MultipartItem; - type Error = MultipartError; - - fn poll(&mut self) -> Poll, Self::Error> { - if let Some(err) = self.error.take() { - Err(err) - } else if self.safety.current() { - self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety) - } else { - Ok(Async::NotReady) - } - } -} - -impl InnerMultipart -where - S: Stream, -{ - fn read_headers(payload: &mut PayloadBuffer) -> Poll { - match payload.read_until(b"\r\n\r\n")? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(bytes)) => { - let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS]; - match httparse::parse_headers(&bytes, &mut hdrs) { - Ok(httparse::Status::Complete((_, hdrs))) => { - // convert headers - let mut headers = HeaderMap::with_capacity(hdrs.len()); - for h in hdrs { - if let Ok(name) = HeaderName::try_from(h.name) { - if let Ok(value) = HeaderValue::try_from(h.value) { - headers.append(name, value); - } else { - return Err(ParseError::Header.into()); - } - } else { - return Err(ParseError::Header.into()); - } - } - Ok(Async::Ready(headers)) - } - Ok(httparse::Status::Partial) => Err(ParseError::Header.into()), - Err(err) => Err(ParseError::from(err).into()), - } - } - } - } - - fn read_boundary( - payload: &mut PayloadBuffer, boundary: &str, - ) -> Poll { - // TODO: need to read epilogue - match payload.readline()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(chunk)) => { - if chunk.len() == boundary.len() + 4 - && &chunk[..2] == b"--" - && &chunk[2..boundary.len() + 2] == boundary.as_bytes() - { - Ok(Async::Ready(false)) - } else if chunk.len() == boundary.len() + 6 - && &chunk[..2] == b"--" - && &chunk[2..boundary.len() + 2] == boundary.as_bytes() - && &chunk[boundary.len() + 2..boundary.len() + 4] == b"--" - { - Ok(Async::Ready(true)) - } else { - Err(MultipartError::Boundary) - } - } - } - } - - fn skip_until_boundary( - payload: &mut PayloadBuffer, boundary: &str, - ) -> Poll { - let mut eof = false; - loop { - match payload.readline()? { - Async::Ready(Some(chunk)) => { - if chunk.is_empty() { - //ValueError("Could not find starting boundary %r" - //% (self._boundary)) - } - if chunk.len() < boundary.len() { - continue; - } - if &chunk[..2] == b"--" - && &chunk[2..chunk.len() - 2] == boundary.as_bytes() - { - break; - } else { - if chunk.len() < boundary.len() + 2 { - continue; - } - let b: &[u8] = boundary.as_ref(); - if &chunk[..boundary.len()] == b - && &chunk[boundary.len()..boundary.len() + 2] == b"--" - { - eof = true; - break; - } - } - } - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(None) => return Err(MultipartError::Incomplete), - } - } - Ok(Async::Ready(eof)) - } - - fn poll( - &mut self, safety: &Safety, - ) -> Poll>, MultipartError> { - if self.state == InnerState::Eof { - Ok(Async::Ready(None)) - } else { - // release field - loop { - // Nested multipart streams of fields has to be consumed - // before switching to next - if safety.current() { - let stop = match self.item { - InnerMultipartItem::Field(ref mut field) => { - match field.borrow_mut().poll(safety)? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(Some(_)) => continue, - Async::Ready(None) => true, - } - } - InnerMultipartItem::Multipart(ref mut multipart) => { - match multipart.borrow_mut().poll(safety)? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(Some(_)) => continue, - Async::Ready(None) => true, - } - } - _ => false, - }; - if stop { - self.item = InnerMultipartItem::None; - } - if let InnerMultipartItem::None = self.item { - break; - } - } - } - - let headers = if let Some(payload) = self.payload.get_mut(safety) { - match self.state { - // read until first boundary - InnerState::FirstBoundary => { - match InnerMultipart::skip_until_boundary( - payload, - &self.boundary, - )? { - Async::Ready(eof) => { - if eof { - self.state = InnerState::Eof; - return Ok(Async::Ready(None)); - } else { - self.state = InnerState::Headers; - } - } - Async::NotReady => return Ok(Async::NotReady), - } - } - // read boundary - InnerState::Boundary => { - match InnerMultipart::read_boundary(payload, &self.boundary)? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(eof) => { - if eof { - self.state = InnerState::Eof; - return Ok(Async::Ready(None)); - } else { - self.state = InnerState::Headers; - } - } - } - } - _ => (), - } - - // read field headers for next field - if self.state == InnerState::Headers { - if let Async::Ready(headers) = InnerMultipart::read_headers(payload)? - { - self.state = InnerState::Boundary; - headers - } else { - return Ok(Async::NotReady); - } - } else { - unreachable!() - } - } else { - debug!("NotReady: field is in flight"); - return Ok(Async::NotReady); - }; - - // content type - let mut mt = mime::APPLICATION_OCTET_STREAM; - if let Some(content_type) = headers.get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - if let Ok(ct) = content_type.parse::() { - mt = ct; - } - } - } - - self.state = InnerState::Boundary; - - // nested multipart stream - if mt.type_() == mime::MULTIPART { - let inner = if let Some(boundary) = mt.get_param(mime::BOUNDARY) { - Rc::new(RefCell::new(InnerMultipart { - payload: self.payload.clone(), - boundary: boundary.as_str().to_owned(), - state: InnerState::FirstBoundary, - item: InnerMultipartItem::None, - })) - } else { - return Err(MultipartError::Boundary); - }; - - self.item = InnerMultipartItem::Multipart(Rc::clone(&inner)); - - Ok(Async::Ready(Some(MultipartItem::Nested(Multipart { - safety: safety.clone(), - error: None, - inner: Some(inner), - })))) - } else { - let field = Rc::new(RefCell::new(InnerField::new( - self.payload.clone(), - self.boundary.clone(), - &headers, - )?)); - self.item = InnerMultipartItem::Field(Rc::clone(&field)); - - Ok(Async::Ready(Some(MultipartItem::Field(Field::new( - safety.clone(), - headers, - mt, - field, - ))))) - } - } - } -} - -impl Drop for InnerMultipart { - fn drop(&mut self) { - // InnerMultipartItem::Field has to be dropped first because of Safety. - self.item = InnerMultipartItem::None; - } -} - -/// A single field in a multipart stream -pub struct Field { - ct: mime::Mime, - headers: HeaderMap, - inner: Rc>>, - safety: Safety, -} - -impl Field -where - S: Stream, -{ - fn new( - safety: Safety, headers: HeaderMap, ct: mime::Mime, - inner: Rc>>, - ) -> Self { - Field { - ct, - headers, - inner, - safety, - } - } - - /// Get a map of headers - pub fn headers(&self) -> &HeaderMap { - &self.headers - } - - /// Get the content type of the field - pub fn content_type(&self) -> &mime::Mime { - &self.ct - } - - /// Get the content disposition of the field, if it exists - pub fn content_disposition(&self) -> Option { - // RFC 7578: 'Each part MUST contain a Content-Disposition header field - // where the disposition type is "form-data".' - if let Some(content_disposition) = - self.headers.get(::http::header::CONTENT_DISPOSITION) - { - ContentDisposition::from_raw(content_disposition).ok() - } else { - None - } - } -} - -impl Stream for Field -where - S: Stream, -{ - type Item = Bytes; - type Error = MultipartError; - - fn poll(&mut self) -> Poll, Self::Error> { - if self.safety.current() { - self.inner.borrow_mut().poll(&self.safety) - } else { - Ok(Async::NotReady) - } - } -} - -impl fmt::Debug for Field { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "\nMultipartField: {}", self.ct)?; - writeln!(f, " boundary: {}", self.inner.borrow().boundary)?; - writeln!(f, " headers:")?; - for (key, val) in self.headers.iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -struct InnerField { - payload: Option>, - boundary: String, - eof: bool, - length: Option, -} - -impl InnerField -where - S: Stream, -{ - fn new( - payload: PayloadRef, boundary: String, headers: &HeaderMap, - ) -> Result, PayloadError> { - let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - Some(len) - } else { - return Err(PayloadError::Incomplete); - } - } else { - return Err(PayloadError::Incomplete); - } - } else { - None - }; - - Ok(InnerField { - boundary, - payload: Some(payload), - eof: false, - length: len, - }) - } - - /// Reads body part content chunk of the specified size. - /// The body part must has `Content-Length` header with proper value. - fn read_len( - payload: &mut PayloadBuffer, size: &mut u64, - ) -> Poll, MultipartError> { - if *size == 0 { - Ok(Async::Ready(None)) - } else { - match payload.readany() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(None)) => Err(MultipartError::Incomplete), - Ok(Async::Ready(Some(mut chunk))) => { - let len = cmp::min(chunk.len() as u64, *size); - *size -= len; - let ch = chunk.split_to(len as usize); - if !chunk.is_empty() { - payload.unprocessed(chunk); - } - Ok(Async::Ready(Some(ch))) - } - Err(err) => Err(err.into()), - } - } - } - - /// Reads content chunk of body part with unknown length. - /// The `Content-Length` header for body part is not necessary. - fn read_stream( - payload: &mut PayloadBuffer, boundary: &str, - ) -> Poll, MultipartError> { - match payload.read_until(b"\r")? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(mut chunk)) => { - if chunk.len() == 1 { - payload.unprocessed(chunk); - match payload.read_exact(boundary.len() + 4)? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(mut chunk)) => { - if &chunk[..2] == b"\r\n" - && &chunk[2..4] == b"--" - && &chunk[4..] == boundary.as_bytes() - { - payload.unprocessed(chunk); - Ok(Async::Ready(None)) - } else { - // \r might be part of data stream - let ch = chunk.split_to(1); - payload.unprocessed(chunk); - Ok(Async::Ready(Some(ch))) - } - } - } - } else { - let to = chunk.len() - 1; - let ch = chunk.split_to(to); - payload.unprocessed(chunk); - Ok(Async::Ready(Some(ch))) - } - } - } - } - - fn poll(&mut self, s: &Safety) -> Poll, MultipartError> { - if self.payload.is_none() { - return Ok(Async::Ready(None)); - } - - let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) { - let res = if let Some(ref mut len) = self.length { - InnerField::read_len(payload, len)? - } else { - InnerField::read_stream(payload, &self.boundary)? - }; - - match res { - Async::NotReady => Async::NotReady, - Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)), - Async::Ready(None) => { - self.eof = true; - match payload.readline()? { - Async::NotReady => Async::NotReady, - Async::Ready(None) => Async::Ready(None), - Async::Ready(Some(line)) => { - if line.as_ref() != b"\r\n" { - warn!("multipart field did not read all the data or it is malformed"); - } - Async::Ready(None) - } - } - } - } - } else { - Async::NotReady - }; - - if Async::Ready(None) == result { - self.payload.take(); - } - Ok(result) - } -} - -struct PayloadRef { - payload: Rc>>, -} - -impl PayloadRef -where - S: Stream, -{ - fn new(payload: PayloadBuffer) -> PayloadRef { - PayloadRef { - payload: Rc::new(payload.into()), - } - } - - fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadBuffer> - where - 'a: 'b, - { - // Unsafe: Invariant is inforced by Safety Safety is used as ref counter, - // only top most ref can have mutable access to payload. - if s.current() { - let payload: &mut PayloadBuffer = unsafe { &mut *self.payload.get() }; - Some(payload) - } else { - None - } - } -} - -impl Clone for PayloadRef { - fn clone(&self) -> PayloadRef { - PayloadRef { - payload: Rc::clone(&self.payload), - } - } -} - -/// Counter. It tracks of number of clones of payloads and give access to -/// payload only to top most task panics if Safety get destroyed and it not top -/// most task. -#[derive(Debug)] -struct Safety { - task: Option, - level: usize, - payload: Rc>, -} - -impl Safety { - fn new() -> Safety { - let payload = Rc::new(PhantomData); - Safety { - task: None, - level: Rc::strong_count(&payload), - payload, - } - } - - fn current(&self) -> bool { - Rc::strong_count(&self.payload) == self.level - } -} - -impl Clone for Safety { - fn clone(&self) -> Safety { - let payload = Rc::clone(&self.payload); - Safety { - task: Some(current_task()), - level: Rc::strong_count(&payload), - payload, - } - } -} - -impl Drop for Safety { - fn drop(&mut self) { - // parent task is dead - if Rc::strong_count(&self.payload) != self.level { - panic!("Safety get dropped but it is not from top-most task"); - } - if let Some(task) = self.task.take() { - task.notify() - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bytes::Bytes; - use futures::future::{lazy, result}; - use payload::{Payload, PayloadWriter}; - use tokio::runtime::current_thread::Runtime; - - #[test] - fn test_boundary() { - let headers = HeaderMap::new(); - match Multipart::boundary(&headers) { - Err(MultipartError::NoContentType) => (), - _ => unreachable!("should not happen"), - } - - let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("test"), - ); - - match Multipart::boundary(&headers) { - Err(MultipartError::ParseContentType) => (), - _ => unreachable!("should not happen"), - } - - let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("multipart/mixed"), - ); - match Multipart::boundary(&headers) { - Err(MultipartError::Boundary) => (), - _ => unreachable!("should not happen"), - } - - let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static( - "multipart/mixed; boundary=\"5c02368e880e436dab70ed54e1c58209\"", - ), - ); - - assert_eq!( - Multipart::boundary(&headers).unwrap(), - "5c02368e880e436dab70ed54e1c58209" - ); - } - - #[test] - fn test_multipart() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); - - let bytes = Bytes::from( - "testasdadsad\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ - Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\ - Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ - test\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ - Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ - data\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n"); - sender.feed_data(bytes); - - let mut multipart = Multipart::new( - Ok("abbc761f78ff4d7cb7573b5a23f96ef0".to_owned()), - payload, - ); - match multipart.poll() { - Ok(Async::Ready(Some(item))) => match item { - MultipartItem::Field(mut field) => { - { - use http::header::{DispositionParam, DispositionType}; - let cd = field.content_disposition().unwrap(); - assert_eq!(cd.disposition, DispositionType::FormData); - assert_eq!( - cd.parameters[0], - DispositionParam::Name("file".into()) - ); - } - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); - - match field.poll() { - Ok(Async::Ready(Some(chunk))) => { - assert_eq!(chunk, "test") - } - _ => unreachable!(), - } - match field.poll() { - Ok(Async::Ready(None)) => (), - _ => unreachable!(), - } - } - _ => unreachable!(), - }, - _ => unreachable!(), - } - - match multipart.poll() { - Ok(Async::Ready(Some(item))) => match item { - MultipartItem::Field(mut field) => { - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); - - match field.poll() { - Ok(Async::Ready(Some(chunk))) => { - assert_eq!(chunk, "data") - } - _ => unreachable!(), - } - match field.poll() { - Ok(Async::Ready(None)) => (), - _ => unreachable!(), - } - } - _ => unreachable!(), - }, - _ => unreachable!(), - } - - match multipart.poll() { - Ok(Async::Ready(None)) => (), - _ => unreachable!(), - } - - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); - } -} diff --git a/src/param.rs b/src/param.rs deleted file mode 100644 index d0664df99..000000000 --- a/src/param.rs +++ /dev/null @@ -1,303 +0,0 @@ -use std; -use std::ops::Index; -use std::path::PathBuf; -use std::rc::Rc; -use std::str::FromStr; - -use http::StatusCode; -use smallvec::SmallVec; - -use error::{InternalError, ResponseError, UriSegmentError}; -use uri::Url; - -/// A trait to abstract the idea of creating a new instance of a type from a -/// path parameter. -pub trait FromParam: Sized { - /// The associated error which can be returned from parsing. - type Err: ResponseError; - - /// Parses a string `s` to return a value of this type. - fn from_param(s: &str) -> Result; -} - -#[derive(Debug, Clone)] -pub(crate) enum ParamItem { - Static(&'static str), - UrlSegment(u16, u16), -} - -/// Route match information -/// -/// If resource path contains variable patterns, `Params` stores this variables. -#[derive(Debug, Clone)] -pub struct Params { - url: Url, - pub(crate) tail: u16, - pub(crate) segments: SmallVec<[(Rc, ParamItem); 3]>, -} - -impl Params { - pub(crate) fn new() -> Params { - Params { - url: Url::default(), - tail: 0, - segments: SmallVec::new(), - } - } - - pub(crate) fn with_url(url: &Url) -> Params { - Params { - url: url.clone(), - tail: 0, - segments: SmallVec::new(), - } - } - - pub(crate) fn clear(&mut self) { - self.segments.clear(); - } - - pub(crate) fn set_tail(&mut self, tail: u16) { - self.tail = tail; - } - - pub(crate) fn set_url(&mut self, url: Url) { - self.url = url; - } - - pub(crate) fn add(&mut self, name: Rc, value: ParamItem) { - self.segments.push((name, value)); - } - - pub(crate) fn add_static(&mut self, name: &str, value: &'static str) { - self.segments - .push((Rc::new(name.to_string()), ParamItem::Static(value))); - } - - /// Check if there are any matched patterns - pub fn is_empty(&self) -> bool { - self.segments.is_empty() - } - - /// Check number of extracted parameters - pub fn len(&self) -> usize { - self.segments.len() - } - - /// Get matched parameter by name without type conversion - pub fn get(&self, key: &str) -> Option<&str> { - for item in self.segments.iter() { - if key == item.0.as_str() { - return match item.1 { - ParamItem::Static(ref s) => Some(&s), - ParamItem::UrlSegment(s, e) => { - Some(&self.url.path()[(s as usize)..(e as usize)]) - } - }; - } - } - if key == "tail" { - Some(&self.url.path()[(self.tail as usize)..]) - } else { - None - } - } - - /// Get unprocessed part of path - pub fn unprocessed(&self) -> &str { - &self.url.path()[(self.tail as usize)..] - } - - /// Get matched `FromParam` compatible parameter by name. - /// - /// If keyed parameter is not available empty string is used as default - /// value. - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// fn index(req: HttpRequest) -> Result { - /// let ivalue: isize = req.match_info().query("val")?; - /// Ok(format!("isuze value: {:?}", ivalue)) - /// } - /// # fn main() {} - /// ``` - pub fn query(&self, key: &str) -> Result::Err> { - if let Some(s) = self.get(key) { - T::from_param(s) - } else { - T::from_param("") - } - } - - /// Return iterator to items in parameter container - pub fn iter(&self) -> ParamsIter { - ParamsIter { - idx: 0, - params: self, - } - } -} - -#[derive(Debug)] -pub struct ParamsIter<'a> { - idx: usize, - params: &'a Params, -} - -impl<'a> Iterator for ParamsIter<'a> { - type Item = (&'a str, &'a str); - - #[inline] - fn next(&mut self) -> Option<(&'a str, &'a str)> { - if self.idx < self.params.len() { - let idx = self.idx; - let res = match self.params.segments[idx].1 { - ParamItem::Static(ref s) => &s, - ParamItem::UrlSegment(s, e) => { - &self.params.url.path()[(s as usize)..(e as usize)] - } - }; - self.idx += 1; - return Some((&self.params.segments[idx].0, res)); - } - None - } -} - -impl<'a> Index<&'a str> for Params { - type Output = str; - - fn index(&self, name: &'a str) -> &str { - self.get(name) - .expect("Value for parameter is not available") - } -} - -impl Index for Params { - type Output = str; - - fn index(&self, idx: usize) -> &str { - match self.segments[idx].1 { - ParamItem::Static(ref s) => &s, - ParamItem::UrlSegment(s, e) => &self.url.path()[(s as usize)..(e as usize)], - } - } -} - -/// Creates a `PathBuf` from a path parameter. The returned `PathBuf` is -/// percent-decoded. If a segment is equal to "..", the previous segment (if -/// any) is skipped. -/// -/// For security purposes, if a segment meets any of the following conditions, -/// an `Err` is returned indicating the condition met: -/// -/// * Decoded segment starts with any of: `.` (except `..`), `*` -/// * Decoded segment ends with any of: `:`, `>`, `<` -/// * Decoded segment contains any of: `/` -/// * On Windows, decoded segment contains any of: '\' -/// * Percent-encoding results in invalid UTF8. -/// -/// As a result of these conditions, a `PathBuf` parsed from request path -/// parameter is safe to interpolate within, or use as a suffix of, a path -/// without additional checks. -impl FromParam for PathBuf { - type Err = UriSegmentError; - - fn from_param(val: &str) -> Result { - let mut buf = PathBuf::new(); - for segment in val.split('/') { - if segment == ".." { - buf.pop(); - } else if segment.starts_with('.') { - return Err(UriSegmentError::BadStart('.')); - } else if segment.starts_with('*') { - return Err(UriSegmentError::BadStart('*')); - } else if segment.ends_with(':') { - return Err(UriSegmentError::BadEnd(':')); - } else if segment.ends_with('>') { - return Err(UriSegmentError::BadEnd('>')); - } else if segment.ends_with('<') { - return Err(UriSegmentError::BadEnd('<')); - } else if segment.is_empty() { - continue; - } else if cfg!(windows) && segment.contains('\\') { - return Err(UriSegmentError::BadChar('\\')); - } else { - buf.push(segment) - } - } - - Ok(buf) - } -} - -macro_rules! FROM_STR { - ($type:ty) => { - impl FromParam for $type { - type Err = InternalError<<$type as FromStr>::Err>; - fn from_param(val: &str) -> Result { - <$type as FromStr>::from_str(val) - .map_err(|e| InternalError::new(e, StatusCode::BAD_REQUEST)) - } - } - }; -} - -FROM_STR!(u8); -FROM_STR!(u16); -FROM_STR!(u32); -FROM_STR!(u64); -FROM_STR!(usize); -FROM_STR!(i8); -FROM_STR!(i16); -FROM_STR!(i32); -FROM_STR!(i64); -FROM_STR!(isize); -FROM_STR!(f32); -FROM_STR!(f64); -FROM_STR!(String); -FROM_STR!(std::net::IpAddr); -FROM_STR!(std::net::Ipv4Addr); -FROM_STR!(std::net::Ipv6Addr); -FROM_STR!(std::net::SocketAddr); -FROM_STR!(std::net::SocketAddrV4); -FROM_STR!(std::net::SocketAddrV6); - -#[cfg(test)] -mod tests { - use super::*; - use std::iter::FromIterator; - - #[test] - fn test_path_buf() { - assert_eq!( - PathBuf::from_param("/test/.tt"), - Err(UriSegmentError::BadStart('.')) - ); - assert_eq!( - PathBuf::from_param("/test/*tt"), - Err(UriSegmentError::BadStart('*')) - ); - assert_eq!( - PathBuf::from_param("/test/tt:"), - Err(UriSegmentError::BadEnd(':')) - ); - assert_eq!( - PathBuf::from_param("/test/tt<"), - Err(UriSegmentError::BadEnd('<')) - ); - assert_eq!( - PathBuf::from_param("/test/tt>"), - Err(UriSegmentError::BadEnd('>')) - ); - assert_eq!( - PathBuf::from_param("/seg1/seg2/"), - Ok(PathBuf::from_iter(vec!["seg1", "seg2"])) - ); - assert_eq!( - PathBuf::from_param("/seg1/../seg2/"), - Ok(PathBuf::from_iter(vec!["seg2"])) - ); - } -} diff --git a/src/pipeline.rs b/src/pipeline.rs deleted file mode 100644 index 1940f9308..000000000 --- a/src/pipeline.rs +++ /dev/null @@ -1,869 +0,0 @@ -use std::marker::PhantomData; -use std::rc::Rc; -use std::{io, mem}; - -use futures::sync::oneshot; -use futures::{Async, Future, Poll, Stream}; -use log::Level::Debug; - -use body::{Body, BodyStream}; -use context::{ActorHttpContext, Frame}; -use error::Error; -use handler::{AsyncResult, AsyncResultItem}; -use header::ContentEncoding; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Finished, Middleware, Response, Started}; -use server::{HttpHandlerTask, Writer, WriterState}; - -#[doc(hidden)] -pub trait PipelineHandler { - fn encoding(&self) -> ContentEncoding; - - fn handle(&self, &HttpRequest) -> AsyncResult; -} - -#[doc(hidden)] -pub struct Pipeline( - PipelineInfo, - PipelineState, - Rc>>>, -); - -enum PipelineState { - None, - Error, - Starting(StartMiddlewares), - Handler(WaitingResponse), - RunMiddlewares(RunMiddlewares), - Response(ProcessResponse), - Finishing(FinishingMiddlewares), - Completed(Completed), -} - -impl> PipelineState { - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - match *self { - PipelineState::Starting(ref mut state) => state.poll(info, mws), - PipelineState::Handler(ref mut state) => state.poll(info, mws), - PipelineState::RunMiddlewares(ref mut state) => state.poll(info, mws), - PipelineState::Finishing(ref mut state) => state.poll(info, mws), - PipelineState::Completed(ref mut state) => state.poll(info), - PipelineState::Response(ref mut state) => state.poll(info, mws), - PipelineState::None | PipelineState::Error => None, - } - } -} - -struct PipelineInfo { - req: HttpRequest, - count: u16, - context: Option>, - error: Option, - disconnected: Option, - encoding: ContentEncoding, -} - -impl PipelineInfo { - fn poll_context(&mut self) -> Poll<(), Error> { - if let Some(ref mut context) = self.context { - match context.poll() { - Err(err) => Err(err), - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(_)) => Ok(Async::Ready(())), - } - } else { - Ok(Async::Ready(())) - } - } -} - -impl> Pipeline { - pub(crate) fn new( - req: HttpRequest, mws: Rc>>>, handler: Rc, - ) -> Pipeline { - let mut info = PipelineInfo { - req, - count: 0, - error: None, - context: None, - disconnected: None, - encoding: handler.encoding(), - }; - let state = StartMiddlewares::init(&mut info, &mws, handler); - - Pipeline(info, state, mws) - } -} - -impl Pipeline { - #[inline] - fn is_done(&self) -> bool { - match self.1 { - PipelineState::None - | PipelineState::Error - | PipelineState::Starting(_) - | PipelineState::Handler(_) - | PipelineState::RunMiddlewares(_) - | PipelineState::Response(_) => true, - PipelineState::Finishing(_) | PipelineState::Completed(_) => false, - } - } -} - -impl> HttpHandlerTask for Pipeline { - fn disconnected(&mut self) { - self.0.disconnected = Some(true); - } - - fn poll_io(&mut self, io: &mut Writer) -> Poll { - let mut state = mem::replace(&mut self.1, PipelineState::None); - - loop { - if let PipelineState::Response(st) = state { - match st.poll_io(io, &mut self.0, &self.2) { - Ok(state) => { - self.1 = state; - if let Some(error) = self.0.error.take() { - return Err(error); - } else { - return Ok(Async::Ready(self.is_done())); - } - } - Err(state) => { - self.1 = state; - return Ok(Async::NotReady); - } - } - } - match state { - PipelineState::None => return Ok(Async::Ready(true)), - PipelineState::Error => { - return Err( - io::Error::new(io::ErrorKind::Other, "Internal error").into() - ) - } - _ => (), - } - - match state.poll(&mut self.0, &self.2) { - Some(st) => state = st, - None => { - return { - self.1 = state; - Ok(Async::NotReady) - } - } - } - } - } - - fn poll_completed(&mut self) -> Poll<(), Error> { - let mut state = mem::replace(&mut self.1, PipelineState::None); - loop { - match state { - PipelineState::None | PipelineState::Error => { - return Ok(Async::Ready(())) - } - _ => (), - } - - if let Some(st) = state.poll(&mut self.0, &self.2) { - state = st; - } else { - self.1 = state; - return Ok(Async::NotReady); - } - } - } -} - -type Fut = Box, Error = Error>>; - -/// Middlewares start executor -struct StartMiddlewares { - hnd: Rc, - fut: Option, - _s: PhantomData, -} - -impl> StartMiddlewares { - fn init( - info: &mut PipelineInfo, mws: &[Box>], hnd: Rc, - ) -> PipelineState { - // execute middlewares, we need this stage because middlewares could be - // non-async and we can move to next state immediately - let len = mws.len() as u16; - - loop { - if info.count == len { - let reply = hnd.handle(&info.req); - return WaitingResponse::init(info, mws, reply); - } else { - match mws[info.count as usize].start(&info.req) { - Ok(Started::Done) => info.count += 1, - Ok(Started::Response(resp)) => { - return RunMiddlewares::init(info, mws, resp); - } - Ok(Started::Future(fut)) => { - return PipelineState::Starting(StartMiddlewares { - hnd, - fut: Some(fut), - _s: PhantomData, - }) - } - Err(err) => { - return RunMiddlewares::init(info, mws, err.into()); - } - } - } - } - } - - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - let len = mws.len() as u16; - - 'outer: loop { - match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => { - return None; - } - Ok(Async::Ready(resp)) => { - info.count += 1; - if let Some(resp) = resp { - return Some(RunMiddlewares::init(info, mws, resp)); - } - loop { - if info.count == len { - let reply = self.hnd.handle(&info.req); - return Some(WaitingResponse::init(info, mws, reply)); - } else { - let res = mws[info.count as usize].start(&info.req); - match res { - Ok(Started::Done) => info.count += 1, - Ok(Started::Response(resp)) => { - return Some(RunMiddlewares::init(info, mws, resp)); - } - Ok(Started::Future(fut)) => { - self.fut = Some(fut); - continue 'outer; - } - Err(err) => { - return Some(RunMiddlewares::init( - info, - mws, - err.into(), - )); - } - } - } - } - } - Err(err) => { - return Some(RunMiddlewares::init(info, mws, err.into())); - } - } - } - } -} - -// waiting for response -struct WaitingResponse { - fut: Box>, - _s: PhantomData, - _h: PhantomData, -} - -impl WaitingResponse { - #[inline] - fn init( - info: &mut PipelineInfo, mws: &[Box>], - reply: AsyncResult, - ) -> PipelineState { - match reply.into() { - AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, mws, resp), - AsyncResultItem::Err(err) => RunMiddlewares::init(info, mws, err.into()), - AsyncResultItem::Future(fut) => PipelineState::Handler(WaitingResponse { - fut, - _s: PhantomData, - _h: PhantomData, - }), - } - } - - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - match self.fut.poll() { - Ok(Async::NotReady) => None, - Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, mws, resp)), - Err(err) => Some(RunMiddlewares::init(info, mws, err.into())), - } - } -} - -/// Middlewares response executor -struct RunMiddlewares { - curr: usize, - fut: Option>>, - _s: PhantomData, - _h: PhantomData, -} - -impl RunMiddlewares { - #[inline] - fn init( - info: &mut PipelineInfo, mws: &[Box>], mut resp: HttpResponse, - ) -> PipelineState { - if info.count == 0 { - return ProcessResponse::init(resp); - } - let mut curr = 0; - let len = mws.len(); - - loop { - let state = mws[curr].response(&info.req, resp); - resp = match state { - Err(err) => { - info.count = (curr + 1) as u16; - return ProcessResponse::init(err.into()); - } - Ok(Response::Done(r)) => { - curr += 1; - if curr == len { - return ProcessResponse::init(r); - } else { - r - } - } - Ok(Response::Future(fut)) => { - return PipelineState::RunMiddlewares(RunMiddlewares { - curr, - fut: Some(fut), - _s: PhantomData, - _h: PhantomData, - }); - } - }; - } - } - - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - let len = mws.len(); - - loop { - // poll latest fut - let mut resp = match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => return None, - Ok(Async::Ready(resp)) => { - self.curr += 1; - resp - } - Err(err) => return Some(ProcessResponse::init(err.into())), - }; - - loop { - if self.curr == len { - return Some(ProcessResponse::init(resp)); - } else { - let state = mws[self.curr].response(&info.req, resp); - match state { - Err(err) => return Some(ProcessResponse::init(err.into())), - Ok(Response::Done(r)) => { - self.curr += 1; - resp = r - } - Ok(Response::Future(fut)) => { - self.fut = Some(fut); - break; - } - } - } - } - } - } -} - -struct ProcessResponse { - resp: Option, - iostate: IOState, - running: RunningState, - drain: Option>, - _s: PhantomData, - _h: PhantomData, -} - -#[derive(PartialEq, Debug)] -enum RunningState { - Running, - Paused, - Done, -} - -impl RunningState { - #[inline] - fn pause(&mut self) { - if *self != RunningState::Done { - *self = RunningState::Paused - } - } - #[inline] - fn resume(&mut self) { - if *self != RunningState::Done { - *self = RunningState::Running - } - } -} - -enum IOState { - Response, - Payload(BodyStream), - Actor(Box), - Done, -} - -impl ProcessResponse { - #[inline] - fn init(resp: HttpResponse) -> PipelineState { - PipelineState::Response(ProcessResponse { - resp: Some(resp), - iostate: IOState::Response, - running: RunningState::Running, - drain: None, - _s: PhantomData, - _h: PhantomData, - }) - } - - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - // connection is dead at this point - match mem::replace(&mut self.iostate, IOState::Done) { - IOState::Response => Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )), - IOState::Payload(_) => Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )), - IOState::Actor(mut ctx) => { - if info.disconnected.take().is_some() { - ctx.disconnected(); - } - loop { - match ctx.poll() { - Ok(Async::Ready(Some(vec))) => { - if vec.is_empty() { - continue; - } - for frame in vec { - match frame { - Frame::Chunk(None) => { - info.context = Some(ctx); - return Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - Frame::Chunk(Some(_)) => (), - Frame::Drain(fut) => { - let _ = fut.send(()); - } - } - } - } - Ok(Async::Ready(None)) => { - return Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )) - } - Ok(Async::NotReady) => { - self.iostate = IOState::Actor(ctx); - return None; - } - Err(err) => { - info.context = Some(ctx); - info.error = Some(err); - return Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - } - } - } - IOState::Done => Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )), - } - } - - fn poll_io( - mut self, io: &mut Writer, info: &mut PipelineInfo, - mws: &[Box>], - ) -> Result, PipelineState> { - loop { - if self.drain.is_none() && self.running != RunningState::Paused { - // if task is paused, write buffer is probably full - 'inner: loop { - let result = match mem::replace(&mut self.iostate, IOState::Done) { - IOState::Response => { - let encoding = self - .resp - .as_ref() - .unwrap() - .content_encoding() - .unwrap_or(info.encoding); - - let result = match io.start( - &info.req, - self.resp.as_mut().unwrap(), - encoding, - ) { - Ok(res) => res, - Err(err) => { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - }; - - if let Some(err) = self.resp.as_ref().unwrap().error() { - if self.resp.as_ref().unwrap().status().is_server_error() - { - error!( - "Error occured during request handling, status: {} {}", - self.resp.as_ref().unwrap().status(), err - ); - } else { - warn!( - "Error occured during request handling: {}", - err - ); - } - if log_enabled!(Debug) { - debug!("{:?}", err); - } - } - - // always poll stream or actor for the first time - match self.resp.as_mut().unwrap().replace_body(Body::Empty) { - Body::Streaming(stream) => { - self.iostate = IOState::Payload(stream); - continue 'inner; - } - Body::Actor(ctx) => { - self.iostate = IOState::Actor(ctx); - continue 'inner; - } - _ => (), - } - - result - } - IOState::Payload(mut body) => match body.poll() { - Ok(Async::Ready(None)) => { - if let Err(err) = io.write_eof() { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - break; - } - Ok(Async::Ready(Some(chunk))) => { - self.iostate = IOState::Payload(body); - match io.write(&chunk.into()) { - Err(err) => { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - Ok(result) => result, - } - } - Ok(Async::NotReady) => { - self.iostate = IOState::Payload(body); - break; - } - Err(err) => { - info.error = Some(err); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - }, - IOState::Actor(mut ctx) => { - if info.disconnected.take().is_some() { - ctx.disconnected(); - } - match ctx.poll() { - Ok(Async::Ready(Some(vec))) => { - if vec.is_empty() { - self.iostate = IOState::Actor(ctx); - break; - } - let mut res = None; - for frame in vec { - match frame { - Frame::Chunk(None) => { - info.context = Some(ctx); - if let Err(err) = io.write_eof() { - info.error = Some(err.into()); - return Ok( - FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - ), - ); - } - break 'inner; - } - Frame::Chunk(Some(chunk)) => match io - .write(&chunk) - { - Err(err) => { - info.context = Some(ctx); - info.error = Some(err.into()); - return Ok( - FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - ), - ); - } - Ok(result) => res = Some(result), - }, - Frame::Drain(fut) => self.drain = Some(fut), - } - } - self.iostate = IOState::Actor(ctx); - if self.drain.is_some() { - self.running.resume(); - break 'inner; - } - res.unwrap() - } - Ok(Async::Ready(None)) => break, - Ok(Async::NotReady) => { - self.iostate = IOState::Actor(ctx); - break; - } - Err(err) => { - info.context = Some(ctx); - info.error = Some(err); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - } - } - IOState::Done => break, - }; - - match result { - WriterState::Pause => { - self.running.pause(); - break; - } - WriterState::Done => self.running.resume(), - } - } - } - - // flush io but only if we need to - if self.running == RunningState::Paused || self.drain.is_some() { - match io.poll_completed(false) { - Ok(Async::Ready(_)) => { - self.running.resume(); - - // resolve drain futures - if let Some(tx) = self.drain.take() { - let _ = tx.send(()); - } - // restart io processing - continue; - } - Ok(Async::NotReady) => return Err(PipelineState::Response(self)), - Err(err) => { - if let IOState::Actor(mut ctx) = - mem::replace(&mut self.iostate, IOState::Done) - { - ctx.disconnected(); - info.context = Some(ctx); - } - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - } - } - break; - } - - // response is completed - match self.iostate { - IOState::Done => { - match io.write_eof() { - Ok(_) => (), - Err(err) => { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - } - self.resp.as_mut().unwrap().set_response_size(io.written()); - Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )) - } - _ => Err(PipelineState::Response(self)), - } - } -} - -/// Middlewares start executor -struct FinishingMiddlewares { - resp: Option, - fut: Option>>, - _s: PhantomData, - _h: PhantomData, -} - -impl FinishingMiddlewares { - #[inline] - fn init( - info: &mut PipelineInfo, mws: &[Box>], resp: HttpResponse, - ) -> PipelineState { - if info.count == 0 { - resp.release(); - Completed::init(info) - } else { - let mut state = FinishingMiddlewares { - resp: Some(resp), - fut: None, - _s: PhantomData, - _h: PhantomData, - }; - if let Some(st) = state.poll(info, mws) { - st - } else { - PipelineState::Finishing(state) - } - } - } - - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - loop { - // poll latest fut - let not_ready = if let Some(ref mut fut) = self.fut { - match fut.poll() { - Ok(Async::NotReady) => true, - Ok(Async::Ready(())) => false, - Err(err) => { - error!("Middleware finish error: {}", err); - false - } - } - } else { - false - }; - if not_ready { - return None; - } - self.fut = None; - if info.count == 0 { - self.resp.take().unwrap().release(); - return Some(Completed::init(info)); - } - - info.count -= 1; - let state = - mws[info.count as usize].finish(&info.req, self.resp.as_ref().unwrap()); - match state { - Finished::Done => { - if info.count == 0 { - self.resp.take().unwrap().release(); - return Some(Completed::init(info)); - } - } - Finished::Future(fut) => { - self.fut = Some(fut); - } - } - } - } -} - -#[derive(Debug)] -struct Completed(PhantomData, PhantomData); - -impl Completed { - #[inline] - fn init(info: &mut PipelineInfo) -> PipelineState { - if let Some(ref err) = info.error { - error!("Error occurred during request handling: {}", err); - } - - if info.context.is_none() { - PipelineState::None - } else { - match info.poll_context() { - Ok(Async::NotReady) => { - PipelineState::Completed(Completed(PhantomData, PhantomData)) - } - Ok(Async::Ready(())) => PipelineState::None, - Err(_) => PipelineState::Error, - } - } - } - - #[inline] - fn poll(&mut self, info: &mut PipelineInfo) -> Option> { - match info.poll_context() { - Ok(Async::NotReady) => None, - Ok(Async::Ready(())) => Some(PipelineState::None), - Err(_) => Some(PipelineState::Error), - } - } -} diff --git a/src/pred.rs b/src/pred.rs deleted file mode 100644 index 99d6e608b..000000000 --- a/src/pred.rs +++ /dev/null @@ -1,328 +0,0 @@ -//! Route match predicates -#![allow(non_snake_case)] -use std::marker::PhantomData; - -use http; -use http::{header, HttpTryFrom}; -use server::message::Request; - -/// Trait defines resource route predicate. -/// Predicate can modify request object. It is also possible to -/// to store extra attributes on request by using `Extensions` container, -/// Extensions container available via `HttpRequest::extensions()` method. -pub trait Predicate { - /// Check if request matches predicate - fn check(&self, &Request, &S) -> bool; -} - -/// Return predicate that matches if any of supplied predicate matches. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{pred, App, HttpResponse}; -/// -/// fn main() { -/// App::new().resource("/index.html", |r| { -/// r.route() -/// .filter(pred::Any(pred::Get()).or(pred::Post())) -/// .f(|r| HttpResponse::MethodNotAllowed()) -/// }); -/// } -/// ``` -pub fn Any + 'static>(pred: P) -> AnyPredicate { - AnyPredicate(vec![Box::new(pred)]) -} - -/// Matches if any of supplied predicate matches. -pub struct AnyPredicate(Vec>>); - -impl AnyPredicate { - /// Add new predicate to list of predicates to check - pub fn or + 'static>(mut self, pred: P) -> Self { - self.0.push(Box::new(pred)); - self - } -} - -impl Predicate for AnyPredicate { - fn check(&self, req: &Request, state: &S) -> bool { - for p in &self.0 { - if p.check(req, state) { - return true; - } - } - false - } -} - -/// Return predicate that matches if all of supplied predicate matches. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{pred, App, HttpResponse}; -/// -/// fn main() { -/// App::new().resource("/index.html", |r| { -/// r.route() -/// .filter( -/// pred::All(pred::Get()) -/// .and(pred::Header("content-type", "text/plain")), -/// ) -/// .f(|_| HttpResponse::MethodNotAllowed()) -/// }); -/// } -/// ``` -pub fn All + 'static>(pred: P) -> AllPredicate { - AllPredicate(vec![Box::new(pred)]) -} - -/// Matches if all of supplied predicate matches. -pub struct AllPredicate(Vec>>); - -impl AllPredicate { - /// Add new predicate to list of predicates to check - pub fn and + 'static>(mut self, pred: P) -> Self { - self.0.push(Box::new(pred)); - self - } -} - -impl Predicate for AllPredicate { - fn check(&self, req: &Request, state: &S) -> bool { - for p in &self.0 { - if !p.check(req, state) { - return false; - } - } - true - } -} - -/// Return predicate that matches if supplied predicate does not match. -pub fn Not + 'static>(pred: P) -> NotPredicate { - NotPredicate(Box::new(pred)) -} - -#[doc(hidden)] -pub struct NotPredicate(Box>); - -impl Predicate for NotPredicate { - fn check(&self, req: &Request, state: &S) -> bool { - !self.0.check(req, state) - } -} - -/// Http method predicate -#[doc(hidden)] -pub struct MethodPredicate(http::Method, PhantomData); - -impl Predicate for MethodPredicate { - fn check(&self, req: &Request, _: &S) -> bool { - *req.method() == self.0 - } -} - -/// Predicate to match *GET* http method -pub fn Get() -> MethodPredicate { - MethodPredicate(http::Method::GET, PhantomData) -} - -/// Predicate to match *POST* http method -pub fn Post() -> MethodPredicate { - MethodPredicate(http::Method::POST, PhantomData) -} - -/// Predicate to match *PUT* http method -pub fn Put() -> MethodPredicate { - MethodPredicate(http::Method::PUT, PhantomData) -} - -/// Predicate to match *DELETE* http method -pub fn Delete() -> MethodPredicate { - MethodPredicate(http::Method::DELETE, PhantomData) -} - -/// Predicate to match *HEAD* http method -pub fn Head() -> MethodPredicate { - MethodPredicate(http::Method::HEAD, PhantomData) -} - -/// Predicate to match *OPTIONS* http method -pub fn Options() -> MethodPredicate { - MethodPredicate(http::Method::OPTIONS, PhantomData) -} - -/// Predicate to match *CONNECT* http method -pub fn Connect() -> MethodPredicate { - MethodPredicate(http::Method::CONNECT, PhantomData) -} - -/// Predicate to match *PATCH* http method -pub fn Patch() -> MethodPredicate { - MethodPredicate(http::Method::PATCH, PhantomData) -} - -/// Predicate to match *TRACE* http method -pub fn Trace() -> MethodPredicate { - MethodPredicate(http::Method::TRACE, PhantomData) -} - -/// Predicate to match specified http method -pub fn Method(method: http::Method) -> MethodPredicate { - MethodPredicate(method, PhantomData) -} - -/// Return predicate that matches if request contains specified header and -/// value. -pub fn Header( - name: &'static str, value: &'static str, -) -> HeaderPredicate { - HeaderPredicate( - header::HeaderName::try_from(name).unwrap(), - header::HeaderValue::from_static(value), - PhantomData, - ) -} - -#[doc(hidden)] -pub struct HeaderPredicate(header::HeaderName, header::HeaderValue, PhantomData); - -impl Predicate for HeaderPredicate { - fn check(&self, req: &Request, _: &S) -> bool { - if let Some(val) = req.headers().get(&self.0) { - return val == self.1; - } - false - } -} - -/// Return predicate that matches if request contains specified Host name. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{pred, App, HttpResponse}; -/// -/// fn main() { -/// App::new().resource("/index.html", |r| { -/// r.route() -/// .filter(pred::Host("www.rust-lang.org")) -/// .f(|_| HttpResponse::MethodNotAllowed()) -/// }); -/// } -/// ``` -pub fn Host>(host: H) -> HostPredicate { - HostPredicate(host.as_ref().to_string(), None, PhantomData) -} - -#[doc(hidden)] -pub struct HostPredicate(String, Option, PhantomData); - -impl HostPredicate { - /// Set reuest scheme to match - pub fn scheme>(&mut self, scheme: H) { - self.1 = Some(scheme.as_ref().to_string()) - } -} - -impl Predicate for HostPredicate { - fn check(&self, req: &Request, _: &S) -> bool { - let info = req.connection_info(); - if let Some(ref scheme) = self.1 { - self.0 == info.host() && scheme == info.scheme() - } else { - self.0 == info.host() - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use http::{header, Method}; - use test::TestRequest; - - #[test] - fn test_header() { - let req = TestRequest::with_header( - header::TRANSFER_ENCODING, - header::HeaderValue::from_static("chunked"), - ).finish(); - - let pred = Header("transfer-encoding", "chunked"); - assert!(pred.check(&req, req.state())); - - let pred = Header("transfer-encoding", "other"); - assert!(!pred.check(&req, req.state())); - - let pred = Header("content-type", "other"); - assert!(!pred.check(&req, req.state())); - } - - #[test] - fn test_host() { - let req = TestRequest::default() - .header( - header::HOST, - header::HeaderValue::from_static("www.rust-lang.org"), - ).finish(); - - let pred = Host("www.rust-lang.org"); - assert!(pred.check(&req, req.state())); - - let pred = Host("localhost"); - assert!(!pred.check(&req, req.state())); - } - - #[test] - fn test_methods() { - let req = TestRequest::default().finish(); - let req2 = TestRequest::default().method(Method::POST).finish(); - - assert!(Get().check(&req, req.state())); - assert!(!Get().check(&req2, req2.state())); - assert!(Post().check(&req2, req2.state())); - assert!(!Post().check(&req, req.state())); - - let r = TestRequest::default().method(Method::PUT).finish(); - assert!(Put().check(&r, r.state())); - assert!(!Put().check(&req, req.state())); - - let r = TestRequest::default().method(Method::DELETE).finish(); - assert!(Delete().check(&r, r.state())); - assert!(!Delete().check(&req, req.state())); - - let r = TestRequest::default().method(Method::HEAD).finish(); - assert!(Head().check(&r, r.state())); - assert!(!Head().check(&req, req.state())); - - let r = TestRequest::default().method(Method::OPTIONS).finish(); - assert!(Options().check(&r, r.state())); - assert!(!Options().check(&req, req.state())); - - let r = TestRequest::default().method(Method::CONNECT).finish(); - assert!(Connect().check(&r, r.state())); - assert!(!Connect().check(&req, req.state())); - - let r = TestRequest::default().method(Method::PATCH).finish(); - assert!(Patch().check(&r, r.state())); - assert!(!Patch().check(&req, req.state())); - - let r = TestRequest::default().method(Method::TRACE).finish(); - assert!(Trace().check(&r, r.state())); - assert!(!Trace().check(&req, req.state())); - } - - #[test] - fn test_preds() { - let r = TestRequest::default().method(Method::TRACE).finish(); - - assert!(Not(Get()).check(&r, r.state())); - assert!(!Not(Trace()).check(&r, r.state())); - - assert!(All(Trace()).and(Trace()).check(&r, r.state())); - assert!(!All(Get()).and(Trace()).check(&r, r.state())); - - assert!(Any(Get()).or(Trace()).check(&r, r.state())); - assert!(!Any(Get()).or(Get()).check(&r, r.state())); - } -} diff --git a/src/resource.rs b/src/resource.rs deleted file mode 100644 index d884dd447..000000000 --- a/src/resource.rs +++ /dev/null @@ -1,324 +0,0 @@ -use std::ops::Deref; -use std::rc::Rc; - -use futures::Future; -use http::Method; -use smallvec::SmallVec; - -use error::Error; -use handler::{AsyncResult, FromRequest, Handler, Responder}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::Middleware; -use pred; -use route::Route; -use router::ResourceDef; -use with::WithFactory; - -#[derive(Copy, Clone)] -pub(crate) struct RouteId(usize); - -/// *Resource* is an entry in route table which corresponds to requested URL. -/// -/// Resource in turn has at least one route. -/// Route consists of an object that implements `Handler` trait (handler) -/// and list of predicates (objects that implement `Predicate` trait). -/// Route uses builder-like pattern for configuration. -/// During request handling, resource object iterate through all routes -/// and check all predicates for specific route, if request matches all -/// predicates route route considered matched and route handler get called. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{App, HttpResponse, http}; -/// -/// fn main() { -/// let app = App::new() -/// .resource( -/// "/", |r| r.method(http::Method::GET).f(|r| HttpResponse::Ok())) -/// .finish(); -/// } -pub struct Resource { - rdef: ResourceDef, - routes: SmallVec<[Route; 3]>, - middlewares: Rc>>>, -} - -impl Resource { - /// Create new resource with specified resource definition - pub fn new(rdef: ResourceDef) -> Self { - Resource { - rdef, - routes: SmallVec::new(), - middlewares: Rc::new(Vec::new()), - } - } - - /// Name of the resource - pub(crate) fn get_name(&self) -> &str { - self.rdef.name() - } - - /// Set resource name - pub fn name(&mut self, name: &str) { - self.rdef.set_name(name); - } - - /// Resource definition - pub fn rdef(&self) -> &ResourceDef { - &self.rdef - } -} - -impl Resource { - /// Register a new route and return mutable reference to *Route* object. - /// *Route* is used for route configuration, i.e. adding predicates, - /// setting up handler. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// - /// fn main() { - /// let app = App::new() - /// .resource("/", |r| { - /// r.route() - /// .filter(pred::Any(pred::Get()).or(pred::Put())) - /// .filter(pred::Header("Content-Type", "text/plain")) - /// .f(|r| HttpResponse::Ok()) - /// }) - /// .finish(); - /// } - /// ``` - pub fn route(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap() - } - - /// Register a new `GET` route. - pub fn get(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Get()) - } - - /// Register a new `POST` route. - pub fn post(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Post()) - } - - /// Register a new `PUT` route. - pub fn put(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Put()) - } - - /// Register a new `DELETE` route. - pub fn delete(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Delete()) - } - - /// Register a new `HEAD` route. - pub fn head(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Head()) - } - - /// Register a new route and add method check to route. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// - /// App::new().resource("/", |r| r.method(http::Method::GET).f(index)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().resource("/", |r| r.route().filter(pred::Get()).f(index)); - /// ``` - pub fn method(&mut self, method: Method) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Method(method)) - } - - /// Register a new route and add handler object. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// fn handler(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// - /// App::new().resource("/", |r| r.h(handler)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn handler(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().resource("/", |r| r.route().h(handler)); - /// ``` - pub fn h>(&mut self, handler: H) { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().h(handler) - } - - /// Register a new route and add handler function. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// - /// App::new().resource("/", |r| r.f(index)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().resource("/", |r| r.route().f(index)); - /// ``` - pub fn f(&mut self, handler: F) - where - F: Fn(&HttpRequest) -> R + 'static, - R: Responder + 'static, - { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().f(handler) - } - - /// Register a new route and add handler. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } - /// - /// App::new().resource("/", |r| r.with(index)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().resource("/", |r| r.route().with(index)); - /// ``` - pub fn with(&mut self, handler: F) - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().with(handler); - } - - /// Register a new route and add async handler. - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate futures; - /// use actix_web::*; - /// use futures::future::Future; - /// - /// fn index(req: HttpRequest) -> Box> { - /// unimplemented!() - /// } - /// - /// App::new().resource("/", |r| r.with_async(index)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate futures; - /// # use actix_web::*; - /// # use futures::future::Future; - /// # fn index(req: HttpRequest) -> Box> { - /// # unimplemented!() - /// # } - /// App::new().resource("/", |r| r.route().with_async(index)); - /// ``` - pub fn with_async(&mut self, handler: F) - where - F: Fn(T) -> R + 'static, - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().with_async(handler); - } - - /// Register a resource middleware - /// - /// This is similar to `App's` middlewares, but - /// middlewares get invoked on resource level. - /// - /// *Note* `Middleware::finish()` fires right after response get - /// prepared. It does not wait until body get sent to peer. - pub fn middleware>(&mut self, mw: M) { - Rc::get_mut(&mut self.middlewares) - .unwrap() - .push(Box::new(mw)); - } - - #[inline] - pub(crate) fn get_route_id(&self, req: &HttpRequest) -> Option { - for idx in 0..self.routes.len() { - if (&self.routes[idx]).check(req) { - return Some(RouteId(idx)); - } - } - None - } - - #[inline] - pub(crate) fn handle( - &self, id: RouteId, req: &HttpRequest, - ) -> AsyncResult { - if self.middlewares.is_empty() { - (&self.routes[id.0]).handle(req) - } else { - (&self.routes[id.0]).compose(req.clone(), Rc::clone(&self.middlewares)) - } - } -} - -/// Default resource -pub struct DefaultResource(Rc>); - -impl Deref for DefaultResource { - type Target = Resource; - - fn deref(&self) -> &Resource { - self.0.as_ref() - } -} - -impl Clone for DefaultResource { - fn clone(&self) -> Self { - DefaultResource(self.0.clone()) - } -} - -impl From> for DefaultResource { - fn from(res: Resource) -> Self { - DefaultResource(Rc::new(res)) - } -} diff --git a/src/route.rs b/src/route.rs deleted file mode 100644 index e4a7a9572..000000000 --- a/src/route.rs +++ /dev/null @@ -1,666 +0,0 @@ -use std::marker::PhantomData; -use std::rc::Rc; - -use futures::{Async, Future, Poll}; - -use error::Error; -use handler::{ - AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler, Responder, - RouteHandler, WrapHandler, -}; -use http::StatusCode; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{ - Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse, - Started as MiddlewareStarted, -}; -use pred::Predicate; -use with::{WithAsyncFactory, WithFactory}; - -/// Resource route definition -/// -/// Route uses builder-like pattern for configuration. -/// If handler is not explicitly set, default *404 Not Found* handler is used. -pub struct Route { - preds: Vec>>, - handler: InnerHandler, -} - -impl Default for Route { - fn default() -> Route { - Route { - preds: Vec::new(), - handler: InnerHandler::new(|_: &_| HttpResponse::new(StatusCode::NOT_FOUND)), - } - } -} - -impl Route { - #[inline] - pub(crate) fn check(&self, req: &HttpRequest) -> bool { - let state = req.state(); - for pred in &self.preds { - if !pred.check(req, state) { - return false; - } - } - true - } - - #[inline] - pub(crate) fn handle(&self, req: &HttpRequest) -> AsyncResult { - self.handler.handle(req) - } - - #[inline] - pub(crate) fn compose( - &self, req: HttpRequest, mws: Rc>>>, - ) -> AsyncResult { - AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone()))) - } - - /// Add match predicate to route. - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn main() { - /// App::new().resource("/path", |r| { - /// r.route() - /// .filter(pred::Get()) - /// .filter(pred::Header("content-type", "text/plain")) - /// .f(|req| HttpResponse::Ok()) - /// }) - /// # .finish(); - /// # } - /// ``` - pub fn filter + 'static>(&mut self, p: T) -> &mut Self { - self.preds.push(Box::new(p)); - self - } - - /// Set handler object. Usually call to this method is last call - /// during route configuration, so it does not return reference to self. - pub fn h>(&mut self, handler: H) { - self.handler = InnerHandler::new(handler); - } - - /// Set handler function. Usually call to this method is last call - /// during route configuration, so it does not return reference to self. - pub fn f(&mut self, handler: F) - where - F: Fn(&HttpRequest) -> R + 'static, - R: Responder + 'static, - { - self.handler = InnerHandler::new(handler); - } - - /// Set async handler function. - pub fn a(&mut self, handler: H) - where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - { - self.handler = InnerHandler::async(handler); - } - - /// Set handler function, use request extractor for parameters. - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Path, Result}; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// /// extract path info using serde - /// fn index(info: Path) -> Result { - /// Ok(format!("Welcome {}!", info.username)) - /// } - /// - /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET).with(index), - /// ); // <- use `with` extractor - /// } - /// ``` - /// - /// It is possible to use multiple extractors for one handler function. - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// # use std::collections::HashMap; - /// use actix_web::{http, App, Json, Path, Query, Result}; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// /// extract path info using serde - /// fn index( - /// path: Path, query: Query>, body: Json, - /// ) -> Result { - /// Ok(format!("Welcome {}!", path.username)) - /// } - /// - /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET).with(index), - /// ); // <- use `with` extractor - /// } - /// ``` - pub fn with(&mut self, handler: F) - where - F: WithFactory + 'static, - R: Responder + 'static, - T: FromRequest + 'static, - { - self.h(handler.create()); - } - - /// Set handler function. Same as `.with()` but it allows to configure - /// extractor. Configuration closure accepts config objects as tuple. - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Path, Result}; - /// - /// /// extract text data from request - /// fn index(body: String) -> Result { - /// Ok(format!("Body {}!", body)) - /// } - /// - /// fn main() { - /// let app = App::new().resource("/index.html", |r| { - /// r.method(http::Method::GET) - /// .with_config(index, |cfg| { // <- register handler - /// cfg.0.limit(4096); // <- limit size of the payload - /// }) - /// }); - /// } - /// ``` - pub fn with_config(&mut self, handler: F, cfg_f: C) - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - C: FnOnce(&mut T::Config), - { - let mut cfg = ::default(); - cfg_f(&mut cfg); - self.h(handler.create_with_config(cfg)); - } - - /// Set async handler function, use request extractor for parameters. - /// Also this method needs to be used if your handler function returns - /// `impl Future<>` - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Error, Path}; - /// use futures::Future; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// /// extract path info using serde - /// fn index(info: Path) -> Box> { - /// unimplemented!() - /// } - /// - /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET).with_async(index), - /// ); // <- use `with` extractor - /// } - /// ``` - pub fn with_async(&mut self, handler: F) - where - F: WithAsyncFactory, - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - { - self.h(handler.create()); - } - - /// Set async handler function, use request extractor for parameters. - /// This method allows to configure extractor. Configuration closure - /// accepts config objects as tuple. - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Error, Form}; - /// use futures::Future; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// /// extract path info using serde - /// fn index(info: Form) -> Box> { - /// unimplemented!() - /// } - /// - /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET) - /// .with_async_config(index, |cfg| { - /// cfg.0.limit(4096); - /// }), - /// ); // <- use `with` extractor - /// } - /// ``` - pub fn with_async_config(&mut self, handler: F, cfg: C) - where - F: WithAsyncFactory, - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - C: FnOnce(&mut T::Config), - { - let mut extractor_cfg = ::default(); - cfg(&mut extractor_cfg); - self.h(handler.create_with_config(extractor_cfg)); - } -} - -/// `RouteHandler` wrapper. This struct is required because it needs to be -/// shared for resource level middlewares. -struct InnerHandler(Rc>>); - -impl InnerHandler { - #[inline] - fn new>(h: H) -> Self { - InnerHandler(Rc::new(Box::new(WrapHandler::new(h)))) - } - - #[inline] - fn async(h: H) -> Self - where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - { - InnerHandler(Rc::new(Box::new(AsyncHandler::new(h)))) - } - - #[inline] - pub fn handle(&self, req: &HttpRequest) -> AsyncResult { - self.0.handle(req) - } -} - -impl Clone for InnerHandler { - #[inline] - fn clone(&self) -> Self { - InnerHandler(Rc::clone(&self.0)) - } -} - -/// Compose resource level middlewares with route handler. -struct Compose { - info: ComposeInfo, - state: ComposeState, -} - -struct ComposeInfo { - count: usize, - req: HttpRequest, - mws: Rc>>>, - handler: InnerHandler, -} - -enum ComposeState { - Starting(StartMiddlewares), - Handler(WaitingResponse), - RunMiddlewares(RunMiddlewares), - Finishing(FinishingMiddlewares), - Completed(Response), -} - -impl ComposeState { - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - match *self { - ComposeState::Starting(ref mut state) => state.poll(info), - ComposeState::Handler(ref mut state) => state.poll(info), - ComposeState::RunMiddlewares(ref mut state) => state.poll(info), - ComposeState::Finishing(ref mut state) => state.poll(info), - ComposeState::Completed(_) => None, - } - } -} - -impl Compose { - fn new( - req: HttpRequest, mws: Rc>>>, handler: InnerHandler, - ) -> Self { - let mut info = ComposeInfo { - count: 0, - req, - mws, - handler, - }; - let state = StartMiddlewares::init(&mut info); - - Compose { state, info } - } -} - -impl Future for Compose { - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - loop { - if let ComposeState::Completed(ref mut resp) = self.state { - let resp = resp.resp.take().unwrap(); - return Ok(Async::Ready(resp)); - } - if let Some(state) = self.state.poll(&mut self.info) { - self.state = state; - } else { - return Ok(Async::NotReady); - } - } - } -} - -/// Middlewares start executor -struct StartMiddlewares { - fut: Option, - _s: PhantomData, -} - -type Fut = Box, Error = Error>>; - -impl StartMiddlewares { - fn init(info: &mut ComposeInfo) -> ComposeState { - let len = info.mws.len(); - - loop { - if info.count == len { - let reply = info.handler.handle(&info.req); - return WaitingResponse::init(info, reply); - } else { - let result = info.mws[info.count].start(&info.req); - match result { - Ok(MiddlewareStarted::Done) => info.count += 1, - Ok(MiddlewareStarted::Response(resp)) => { - return RunMiddlewares::init(info, resp); - } - Ok(MiddlewareStarted::Future(fut)) => { - return ComposeState::Starting(StartMiddlewares { - fut: Some(fut), - _s: PhantomData, - }); - } - Err(err) => { - return RunMiddlewares::init(info, err.into()); - } - } - } - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.len(); - - 'outer: loop { - match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => { - return None; - } - Ok(Async::Ready(resp)) => { - info.count += 1; - if let Some(resp) = resp { - return Some(RunMiddlewares::init(info, resp)); - } - loop { - if info.count == len { - let reply = info.handler.handle(&info.req); - return Some(WaitingResponse::init(info, reply)); - } else { - let result = info.mws[info.count].start(&info.req); - match result { - Ok(MiddlewareStarted::Done) => info.count += 1, - Ok(MiddlewareStarted::Response(resp)) => { - return Some(RunMiddlewares::init(info, resp)); - } - Ok(MiddlewareStarted::Future(fut)) => { - self.fut = Some(fut); - continue 'outer; - } - Err(err) => { - return Some(RunMiddlewares::init(info, err.into())); - } - } - } - } - } - Err(err) => { - return Some(RunMiddlewares::init(info, err.into())); - } - } - } - } -} - -type HandlerFuture = Future; - -// waiting for response -struct WaitingResponse { - fut: Box, - _s: PhantomData, -} - -impl WaitingResponse { - #[inline] - fn init( - info: &mut ComposeInfo, reply: AsyncResult, - ) -> ComposeState { - match reply.into() { - AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp), - AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), - AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse { - fut, - _s: PhantomData, - }), - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - match self.fut.poll() { - Ok(Async::NotReady) => None, - Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, resp)), - Err(err) => Some(RunMiddlewares::init(info, err.into())), - } - } -} - -/// Middlewares response executor -struct RunMiddlewares { - curr: usize, - fut: Option>>, - _s: PhantomData, -} - -impl RunMiddlewares { - fn init(info: &mut ComposeInfo, mut resp: HttpResponse) -> ComposeState { - let mut curr = 0; - let len = info.mws.len(); - - loop { - let state = info.mws[curr].response(&info.req, resp); - resp = match state { - Err(err) => { - info.count = curr + 1; - return FinishingMiddlewares::init(info, err.into()); - } - Ok(MiddlewareResponse::Done(r)) => { - curr += 1; - if curr == len { - return FinishingMiddlewares::init(info, r); - } else { - r - } - } - Ok(MiddlewareResponse::Future(fut)) => { - return ComposeState::RunMiddlewares(RunMiddlewares { - curr, - fut: Some(fut), - _s: PhantomData, - }); - } - }; - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.len(); - - loop { - // poll latest fut - let mut resp = match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => return None, - Ok(Async::Ready(resp)) => { - self.curr += 1; - resp - } - Err(err) => return Some(FinishingMiddlewares::init(info, err.into())), - }; - - loop { - if self.curr == len { - return Some(FinishingMiddlewares::init(info, resp)); - } else { - let state = info.mws[self.curr].response(&info.req, resp); - match state { - Err(err) => { - return Some(FinishingMiddlewares::init(info, err.into())) - } - Ok(MiddlewareResponse::Done(r)) => { - self.curr += 1; - resp = r - } - Ok(MiddlewareResponse::Future(fut)) => { - self.fut = Some(fut); - break; - } - } - } - } - } - } -} - -/// Middlewares start executor -struct FinishingMiddlewares { - resp: Option, - fut: Option>>, - _s: PhantomData, -} - -impl FinishingMiddlewares { - fn init(info: &mut ComposeInfo, resp: HttpResponse) -> ComposeState { - if info.count == 0 { - Response::init(resp) - } else { - let mut state = FinishingMiddlewares { - resp: Some(resp), - fut: None, - _s: PhantomData, - }; - if let Some(st) = state.poll(info) { - st - } else { - ComposeState::Finishing(state) - } - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - loop { - // poll latest fut - let not_ready = if let Some(ref mut fut) = self.fut { - match fut.poll() { - Ok(Async::NotReady) => true, - Ok(Async::Ready(())) => false, - Err(err) => { - error!("Middleware finish error: {}", err); - false - } - } - } else { - false - }; - if not_ready { - return None; - } - self.fut = None; - if info.count == 0 { - return Some(Response::init(self.resp.take().unwrap())); - } - - info.count -= 1; - - let state = info.mws[info.count as usize] - .finish(&info.req, self.resp.as_ref().unwrap()); - match state { - MiddlewareFinished::Done => { - if info.count == 0 { - return Some(Response::init(self.resp.take().unwrap())); - } - } - MiddlewareFinished::Future(fut) => { - self.fut = Some(fut); - } - } - } - } -} - -struct Response { - resp: Option, - _s: PhantomData, -} - -impl Response { - fn init(resp: HttpResponse) -> ComposeState { - ComposeState::Completed(Response { - resp: Some(resp), - _s: PhantomData, - }) - } -} diff --git a/src/router.rs b/src/router.rs deleted file mode 100644 index aa15e46d2..000000000 --- a/src/router.rs +++ /dev/null @@ -1,1247 +0,0 @@ -use std::cell::RefCell; -use std::cmp::min; -use std::collections::HashMap; -use std::hash::{Hash, Hasher}; -use std::rc::Rc; - -use regex::{escape, Regex}; -use url::Url; - -use error::UrlGenerationError; -use handler::{AsyncResult, FromRequest, Responder, RouteHandler}; -use http::{Method, StatusCode}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use param::{ParamItem, Params}; -use pred::Predicate; -use resource::{DefaultResource, Resource}; -use scope::Scope; -use server::Request; -use with::WithFactory; - -#[derive(Debug, Copy, Clone, PartialEq)] -pub(crate) enum ResourceId { - Default, - Normal(u16), -} - -enum ResourcePattern { - Resource(ResourceDef), - Handler(ResourceDef, Option>>>), - Scope(ResourceDef, Vec>>), -} - -enum ResourceItem { - Resource(Resource), - Handler(Box>), - Scope(Scope), -} - -/// Interface for application router. -pub struct Router { - rmap: Rc, - patterns: Vec>, - resources: Vec>, - default: Option>, -} - -/// Information about current resource -#[derive(Clone)] -pub struct ResourceInfo { - rmap: Rc, - resource: ResourceId, - params: Params, - prefix: u16, -} - -impl ResourceInfo { - /// Name os the resource - #[inline] - pub fn name(&self) -> &str { - if let ResourceId::Normal(idx) = self.resource { - self.rmap.patterns[idx as usize].0.name() - } else { - "" - } - } - - /// This method returns reference to matched `ResourceDef` object. - #[inline] - pub fn rdef(&self) -> Option<&ResourceDef> { - if let ResourceId::Normal(idx) = self.resource { - Some(&self.rmap.patterns[idx as usize].0) - } else { - None - } - } - - pub(crate) fn set_prefix(&mut self, prefix: u16) { - self.prefix = prefix; - } - - /// Get a reference to the Params object. - /// - /// Params is a container for url parameters. - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. - #[inline] - pub fn match_info(&self) -> &Params { - &self.params - } - - #[inline] - pub(crate) fn merge(&mut self, info: &ResourceInfo) { - let mut p = info.params.clone(); - p.set_tail(self.params.tail); - for item in &self.params.segments { - p.add(item.0.clone(), item.1.clone()); - } - - self.prefix = info.params.tail; - self.params = p; - } - - /// Generate url for named resource - /// - /// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method. - /// url_for) for detailed information. - pub fn url_for( - &self, req: &Request, name: &str, elements: U, - ) -> Result - where - U: IntoIterator, - I: AsRef, - { - let mut path = String::new(); - let mut elements = elements.into_iter(); - - if self - .rmap - .patterns_for(name, &mut path, &mut elements)? - .is_some() - { - if path.starts_with('/') { - let conn = req.connection_info(); - Ok(Url::parse(&format!( - "{}://{}{}", - conn.scheme(), - conn.host(), - path - ))?) - } else { - Ok(Url::parse(&path)?) - } - } else { - Err(UrlGenerationError::ResourceNotFound) - } - } - - /// Check if application contains matching resource. - /// - /// This method does not take `prefix` into account. - /// For example if prefix is `/test` and router contains route `/name`, - /// following path would be recognizable `/test/name` but `has_resource()` call - /// would return `false`. - pub fn has_resource(&self, path: &str) -> bool { - self.rmap.has_resource(path) - } - - /// Check if application contains matching resource. - /// - /// This method does take `prefix` into account - /// but behaves like `has_route` in case `prefix` is not set in the router. - /// - /// For example if prefix is `/test` and router contains route `/name`, the - /// following path would be recognizable `/test/name` and `has_prefixed_route()` call - /// would return `true`. - /// It will not match against prefix in case it's not given. For example for `/name` - /// with a `/test` prefix would return `false` - pub fn has_prefixed_resource(&self, path: &str) -> bool { - let prefix = self.prefix as usize; - if prefix >= path.len() { - return false; - } - self.rmap.has_resource(&path[prefix..]) - } -} - -pub(crate) struct ResourceMap { - root: ResourceDef, - parent: RefCell>>, - named: HashMap, - patterns: Vec<(ResourceDef, Option>)>, - nested: Vec>, -} - -impl ResourceMap { - fn has_resource(&self, path: &str) -> bool { - let path = if path.is_empty() { "/" } else { path }; - - for (pattern, rmap) in &self.patterns { - if let Some(ref rmap) = rmap { - if let Some(plen) = pattern.is_prefix_match(path) { - return rmap.has_resource(&path[plen..]); - } - } else if pattern.is_match(path) { - return true; - } - } - false - } - - fn patterns_for( - &self, name: &str, path: &mut String, elements: &mut U, - ) -> Result, UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if self.pattern_for(name, path, elements)?.is_some() { - Ok(Some(())) - } else { - self.parent_pattern_for(name, path, elements) - } - } - - fn pattern_for( - &self, name: &str, path: &mut String, elements: &mut U, - ) -> Result, UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(pattern) = self.named.get(name) { - self.fill_root(path, elements)?; - pattern.resource_path(path, elements)?; - Ok(Some(())) - } else { - for rmap in &self.nested { - if rmap.pattern_for(name, path, elements)?.is_some() { - return Ok(Some(())); - } - } - Ok(None) - } - } - - fn fill_root( - &self, path: &mut String, elements: &mut U, - ) -> Result<(), UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(ref parent) = *self.parent.borrow() { - parent.fill_root(path, elements)?; - } - self.root.resource_path(path, elements) - } - - fn parent_pattern_for( - &self, name: &str, path: &mut String, elements: &mut U, - ) -> Result, UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(ref parent) = *self.parent.borrow() { - if let Some(pattern) = parent.named.get(name) { - self.fill_root(path, elements)?; - pattern.resource_path(path, elements)?; - Ok(Some(())) - } else { - parent.parent_pattern_for(name, path, elements) - } - } else { - Ok(None) - } - } -} - -impl Default for Router { - fn default() -> Self { - Router::new(ResourceDef::new("")) - } -} - -impl Router { - pub(crate) fn new(root: ResourceDef) -> Self { - Router { - rmap: Rc::new(ResourceMap { - root, - parent: RefCell::new(None), - named: HashMap::new(), - patterns: Vec::new(), - nested: Vec::new(), - }), - resources: Vec::new(), - patterns: Vec::new(), - default: None, - } - } - - #[inline] - pub(crate) fn route_info_params(&self, idx: u16, params: Params) -> ResourceInfo { - ResourceInfo { - params, - prefix: 0, - rmap: self.rmap.clone(), - resource: ResourceId::Normal(idx), - } - } - - #[cfg(test)] - pub(crate) fn default_route_info(&self) -> ResourceInfo { - ResourceInfo { - params: Params::new(), - rmap: self.rmap.clone(), - resource: ResourceId::Default, - prefix: 0, - } - } - - pub(crate) fn set_prefix(&mut self, path: &str) { - Rc::get_mut(&mut self.rmap).unwrap().root = ResourceDef::new(path); - } - - pub(crate) fn register_resource(&mut self, resource: Resource) { - { - let rmap = Rc::get_mut(&mut self.rmap).unwrap(); - - let name = resource.get_name(); - if !name.is_empty() { - assert!( - !rmap.named.contains_key(name), - "Named resource {:?} is registered.", - name - ); - rmap.named.insert(name.to_owned(), resource.rdef().clone()); - } - rmap.patterns.push((resource.rdef().clone(), None)); - } - self.patterns - .push(ResourcePattern::Resource(resource.rdef().clone())); - self.resources.push(ResourceItem::Resource(resource)); - } - - pub(crate) fn register_scope(&mut self, mut scope: Scope) { - Rc::get_mut(&mut self.rmap) - .unwrap() - .patterns - .push((scope.rdef().clone(), Some(scope.router().rmap.clone()))); - Rc::get_mut(&mut self.rmap) - .unwrap() - .nested - .push(scope.router().rmap.clone()); - - let filters = scope.take_filters(); - self.patterns - .push(ResourcePattern::Scope(scope.rdef().clone(), filters)); - self.resources.push(ResourceItem::Scope(scope)); - } - - pub(crate) fn register_handler( - &mut self, path: &str, hnd: Box>, - filters: Option>>>, - ) { - let rdef = ResourceDef::prefix(path); - Rc::get_mut(&mut self.rmap) - .unwrap() - .patterns - .push((rdef.clone(), None)); - self.resources.push(ResourceItem::Handler(hnd)); - self.patterns.push(ResourcePattern::Handler(rdef, filters)); - } - - pub(crate) fn has_default_resource(&self) -> bool { - self.default.is_some() - } - - pub(crate) fn register_default_resource(&mut self, resource: DefaultResource) { - self.default = Some(resource); - } - - pub(crate) fn finish(&mut self) { - for resource in &mut self.resources { - match resource { - ResourceItem::Resource(_) => (), - ResourceItem::Scope(scope) => { - if !scope.has_default_resource() { - if let Some(ref default) = self.default { - scope.default_resource(default.clone()); - } - } - *scope.router().rmap.parent.borrow_mut() = Some(self.rmap.clone()); - scope.finish(); - } - ResourceItem::Handler(hnd) => { - if !hnd.has_default_resource() { - if let Some(ref default) = self.default { - hnd.default_resource(default.clone()); - } - } - hnd.finish() - } - } - } - } - - pub(crate) fn register_external(&mut self, name: &str, rdef: ResourceDef) { - let rmap = Rc::get_mut(&mut self.rmap).unwrap(); - assert!( - !rmap.named.contains_key(name), - "Named resource {:?} is registered.", - name - ); - rmap.named.insert(name.to_owned(), rdef); - } - - pub(crate) fn register_route(&mut self, path: &str, method: Method, f: F) - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - { - let out = { - // get resource handler - let mut iterator = self.resources.iter_mut(); - - loop { - if let Some(ref mut resource) = iterator.next() { - if let ResourceItem::Resource(ref mut resource) = resource { - if resource.rdef().pattern() == path { - resource.method(method).with(f); - break None; - } - } - } else { - let mut resource = Resource::new(ResourceDef::new(path)); - resource.method(method).with(f); - break Some(resource); - } - } - }; - if let Some(out) = out { - self.register_resource(out); - } - } - - /// Handle request - pub fn handle(&self, req: &HttpRequest) -> AsyncResult { - let resource = match req.resource().resource { - ResourceId::Normal(idx) => &self.resources[idx as usize], - ResourceId::Default => { - if let Some(ref default) = self.default { - if let Some(id) = default.get_route_id(req) { - return default.handle(id, req); - } - } - return AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)); - } - }; - match resource { - ResourceItem::Resource(ref resource) => { - if let Some(id) = resource.get_route_id(req) { - return resource.handle(id, req); - } - - if let Some(ref default) = self.default { - if let Some(id) = default.get_route_id(req) { - return default.handle(id, req); - } - } - } - ResourceItem::Handler(hnd) => return hnd.handle(req), - ResourceItem::Scope(hnd) => return hnd.handle(req), - } - AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) - } - - /// Query for matched resource - pub fn recognize(&self, req: &Request, state: &S, tail: usize) -> ResourceInfo { - if tail <= req.path().len() { - 'outer: for (idx, resource) in self.patterns.iter().enumerate() { - match resource { - ResourcePattern::Resource(rdef) => { - if let Some(params) = rdef.match_with_params(req, tail) { - return self.route_info_params(idx as u16, params); - } - } - ResourcePattern::Handler(rdef, filters) => { - if let Some(params) = rdef.match_prefix_with_params(req, tail) { - if let Some(ref filters) = filters { - for filter in filters { - if !filter.check(req, state) { - continue 'outer; - } - } - } - return self.route_info_params(idx as u16, params); - } - } - ResourcePattern::Scope(rdef, filters) => { - if let Some(params) = rdef.match_prefix_with_params(req, tail) { - for filter in filters { - if !filter.check(req, state) { - continue 'outer; - } - } - return self.route_info_params(idx as u16, params); - } - } - } - } - } - ResourceInfo { - prefix: tail as u16, - params: Params::new(), - rmap: self.rmap.clone(), - resource: ResourceId::Default, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -enum PatternElement { - Str(String), - Var(String), -} - -#[derive(Clone, Debug)] -enum PatternType { - Static(String), - Prefix(String), - Dynamic(Regex, Vec>, usize), -} - -#[derive(Debug, Copy, Clone, PartialEq)] -/// Resource type -pub enum ResourceType { - /// Normal resource - Normal, - /// Resource for application default handler - Default, - /// External resource - External, - /// Unknown resource type - Unset, -} - -/// Resource type describes an entry in resources table -#[derive(Clone, Debug)] -pub struct ResourceDef { - tp: PatternType, - rtp: ResourceType, - name: String, - pattern: String, - elements: Vec, -} - -impl ResourceDef { - /// Parse path pattern and create new `ResourceDef` instance. - /// - /// Panics if path pattern is wrong. - pub fn new(path: &str) -> Self { - ResourceDef::with_prefix(path, false, !path.is_empty()) - } - - /// Parse path pattern and create new `ResourceDef` instance. - /// - /// Use `prefix` type instead of `static`. - /// - /// Panics if path regex pattern is wrong. - pub fn prefix(path: &str) -> Self { - ResourceDef::with_prefix(path, true, !path.is_empty()) - } - - /// Construct external resource def - /// - /// Panics if path pattern is wrong. - pub fn external(path: &str) -> Self { - let mut resource = ResourceDef::with_prefix(path, false, false); - resource.rtp = ResourceType::External; - resource - } - - /// Parse path pattern and create new `ResourceDef` instance with custom prefix - pub fn with_prefix(path: &str, for_prefix: bool, slash: bool) -> Self { - let mut path = path.to_owned(); - if slash && !path.starts_with('/') { - path.insert(0, '/'); - } - let (pattern, elements, is_dynamic, len) = ResourceDef::parse(&path, for_prefix); - - let tp = if is_dynamic { - let re = match Regex::new(&pattern) { - Ok(re) => re, - Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err), - }; - // actix creates one router per thread - let names = re - .capture_names() - .filter_map(|name| name.map(|name| Rc::new(name.to_owned()))) - .collect(); - PatternType::Dynamic(re, names, len) - } else if for_prefix { - PatternType::Prefix(pattern.clone()) - } else { - PatternType::Static(pattern.clone()) - }; - - ResourceDef { - tp, - elements, - name: "".to_string(), - rtp: ResourceType::Normal, - pattern: path.to_owned(), - } - } - - /// Resource type - pub fn rtype(&self) -> ResourceType { - self.rtp - } - - /// Resource name - pub fn name(&self) -> &str { - &self.name - } - - /// Resource name - pub(crate) fn set_name(&mut self, name: &str) { - self.name = name.to_owned(); - } - - /// Path pattern of the resource - pub fn pattern(&self) -> &str { - &self.pattern - } - - /// Is this path a match against this resource? - pub fn is_match(&self, path: &str) -> bool { - match self.tp { - PatternType::Static(ref s) => s == path, - PatternType::Dynamic(ref re, _, _) => re.is_match(path), - PatternType::Prefix(ref s) => path.starts_with(s), - } - } - - fn is_prefix_match(&self, path: &str) -> Option { - let plen = path.len(); - let path = if path.is_empty() { "/" } else { path }; - - match self.tp { - PatternType::Static(ref s) => if s == path { - Some(plen) - } else { - None - }, - PatternType::Dynamic(ref re, _, len) => { - if let Some(captures) = re.captures(path) { - let mut pos = 0; - let mut passed = false; - for capture in captures.iter() { - if let Some(ref m) = capture { - if !passed { - passed = true; - continue; - } - - pos = m.end(); - } - } - Some(plen + pos + len) - } else { - None - } - } - PatternType::Prefix(ref s) => { - let len = if path == s { - s.len() - } else if path.starts_with(s) - && (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/')) - { - if s.ends_with('/') { - s.len() - 1 - } else { - s.len() - } - } else { - return None; - }; - Some(min(plen, len)) - } - } - } - - /// Are the given path and parameters a match against this resource? - pub fn match_with_params(&self, req: &Request, plen: usize) -> Option { - let path = &req.path()[plen..]; - - match self.tp { - PatternType::Static(ref s) => if s != path { - None - } else { - Some(Params::with_url(req.url())) - }, - PatternType::Dynamic(ref re, ref names, _) => { - if let Some(captures) = re.captures(path) { - let mut params = Params::with_url(req.url()); - let mut idx = 0; - let mut passed = false; - for capture in captures.iter() { - if let Some(ref m) = capture { - if !passed { - passed = true; - continue; - } - params.add( - names[idx].clone(), - ParamItem::UrlSegment( - (plen + m.start()) as u16, - (plen + m.end()) as u16, - ), - ); - idx += 1; - } - } - params.set_tail(req.path().len() as u16); - Some(params) - } else { - None - } - } - PatternType::Prefix(ref s) => if !path.starts_with(s) { - None - } else { - Some(Params::with_url(req.url())) - }, - } - } - - /// Is the given path a prefix match and do the parameters match against this resource? - pub fn match_prefix_with_params( - &self, req: &Request, plen: usize, - ) -> Option { - let path = &req.path()[plen..]; - let path = if path.is_empty() { "/" } else { path }; - - match self.tp { - PatternType::Static(ref s) => if s == path { - let mut params = Params::with_url(req.url()); - params.set_tail(req.path().len() as u16); - Some(params) - } else { - None - }, - PatternType::Dynamic(ref re, ref names, len) => { - if let Some(captures) = re.captures(path) { - let mut params = Params::with_url(req.url()); - let mut pos = 0; - let mut passed = false; - let mut idx = 0; - for capture in captures.iter() { - if let Some(ref m) = capture { - if !passed { - passed = true; - continue; - } - - params.add( - names[idx].clone(), - ParamItem::UrlSegment( - (plen + m.start()) as u16, - (plen + m.end()) as u16, - ), - ); - idx += 1; - pos = m.end(); - } - } - params.set_tail((plen + pos + len) as u16); - Some(params) - } else { - None - } - } - PatternType::Prefix(ref s) => { - let len = if path == s { - s.len() - } else if path.starts_with(s) - && (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/')) - { - if s.ends_with('/') { - s.len() - 1 - } else { - s.len() - } - } else { - return None; - }; - let mut params = Params::with_url(req.url()); - params.set_tail(min(req.path().len(), plen + len) as u16); - Some(params) - } - } - } - - /// Build resource path. - pub fn resource_path( - &self, path: &mut String, elements: &mut U, - ) -> Result<(), UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - match self.tp { - PatternType::Prefix(ref p) => path.push_str(p), - PatternType::Static(ref p) => path.push_str(p), - PatternType::Dynamic(..) => { - for el in &self.elements { - match *el { - PatternElement::Str(ref s) => path.push_str(s), - PatternElement::Var(_) => { - if let Some(val) = elements.next() { - path.push_str(val.as_ref()) - } else { - return Err(UrlGenerationError::NotEnoughElements); - } - } - } - } - } - }; - Ok(()) - } - - fn parse_param(pattern: &str) -> (PatternElement, String, &str) { - const DEFAULT_PATTERN: &str = "[^/]+"; - let mut params_nesting = 0usize; - let close_idx = pattern - .find(|c| match c { - '{' => { - params_nesting += 1; - false - } - '}' => { - params_nesting -= 1; - params_nesting == 0 - } - _ => false, - }).expect("malformed param"); - let (mut param, rem) = pattern.split_at(close_idx + 1); - param = ¶m[1..param.len() - 1]; // Remove outer brackets - let (name, pattern) = match param.find(':') { - Some(idx) => { - let (name, pattern) = param.split_at(idx); - (name, &pattern[1..]) - } - None => (param, DEFAULT_PATTERN), - }; - ( - PatternElement::Var(name.to_string()), - format!(r"(?P<{}>{})", &name, &pattern), - rem, - ) - } - - fn parse( - mut pattern: &str, for_prefix: bool, - ) -> (String, Vec, bool, usize) { - if pattern.find('{').is_none() { - return ( - String::from(pattern), - vec![PatternElement::Str(String::from(pattern))], - false, - pattern.chars().count(), - ); - }; - - let mut elems = Vec::new(); - let mut re = String::from("^"); - - while let Some(idx) = pattern.find('{') { - let (prefix, rem) = pattern.split_at(idx); - elems.push(PatternElement::Str(String::from(prefix))); - re.push_str(&escape(prefix)); - let (param_pattern, re_part, rem) = Self::parse_param(rem); - elems.push(param_pattern); - re.push_str(&re_part); - pattern = rem; - } - - elems.push(PatternElement::Str(String::from(pattern))); - re.push_str(&escape(pattern)); - - if !for_prefix { - re.push_str("$"); - } - - (re, elems, true, pattern.chars().count()) - } -} - -impl PartialEq for ResourceDef { - fn eq(&self, other: &ResourceDef) -> bool { - self.pattern == other.pattern - } -} - -impl Eq for ResourceDef {} - -impl Hash for ResourceDef { - fn hash(&self, state: &mut H) { - self.pattern.hash(state); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test::TestRequest; - - #[test] - fn test_recognizer10() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/name"))); - router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); - router.register_resource(Resource::new(ResourceDef::new( - "/name/{val}/index.html", - ))); - router.register_resource(Resource::new(ResourceDef::new("/file/{file}.{ext}"))); - router.register_resource(Resource::new(ResourceDef::new( - "/v{val}/{val2}/index.html", - ))); - router.register_resource(Resource::new(ResourceDef::new("/v/{tail:.*}"))); - router.register_resource(Resource::new(ResourceDef::new("/test2/{test}.html"))); - router.register_resource(Resource::new(ResourceDef::new("/{test}/index.html"))); - - let req = TestRequest::with_uri("/name").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(0)); - assert!(info.match_info().is_empty()); - - let req = TestRequest::with_uri("/name/value").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(1)); - assert_eq!(info.match_info().get("val").unwrap(), "value"); - assert_eq!(&info.match_info()["val"], "value"); - - let req = TestRequest::with_uri("/name/value2/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(2)); - assert_eq!(info.match_info().get("val").unwrap(), "value2"); - - let req = TestRequest::with_uri("/file/file.gz").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(3)); - assert_eq!(info.match_info().get("file").unwrap(), "file"); - assert_eq!(info.match_info().get("ext").unwrap(), "gz"); - - let req = TestRequest::with_uri("/vtest/ttt/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(4)); - assert_eq!(info.match_info().get("val").unwrap(), "test"); - assert_eq!(info.match_info().get("val2").unwrap(), "ttt"); - - let req = TestRequest::with_uri("/v/blah-blah/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(5)); - assert_eq!( - info.match_info().get("tail").unwrap(), - "blah-blah/index.html" - ); - - let req = TestRequest::with_uri("/test2/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(6)); - assert_eq!(info.match_info().get("test").unwrap(), "index"); - - let req = TestRequest::with_uri("/bbb/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(7)); - assert_eq!(info.match_info().get("test").unwrap(), "bbb"); - } - - #[test] - fn test_recognizer_2() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/index.json"))); - router.register_resource(Resource::new(ResourceDef::new("/{source}.json"))); - - let req = TestRequest::with_uri("/index.json").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(0)); - - let req = TestRequest::with_uri("/test.json").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(1)); - } - - #[test] - fn test_recognizer_with_prefix() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/name"))); - router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); - - let req = TestRequest::with_uri("/name").finish(); - let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, ResourceId::Default); - - let req = TestRequest::with_uri("/test/name").finish(); - let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, ResourceId::Normal(0)); - - let req = TestRequest::with_uri("/test/name/value").finish(); - let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, ResourceId::Normal(1)); - assert_eq!(info.match_info().get("val").unwrap(), "value"); - assert_eq!(&info.match_info()["val"], "value"); - - // same patterns - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/name"))); - router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); - - let req = TestRequest::with_uri("/name").finish(); - let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, ResourceId::Default); - - let req = TestRequest::with_uri("/test2/name").finish(); - let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, ResourceId::Normal(0)); - - let req = TestRequest::with_uri("/test2/name-test").finish(); - let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, ResourceId::Default); - - let req = TestRequest::with_uri("/test2/name/ttt").finish(); - let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, ResourceId::Normal(1)); - assert_eq!(&info.match_info()["val"], "ttt"); - } - - #[test] - fn test_parse_static() { - let re = ResourceDef::new("/"); - assert!(re.is_match("/")); - assert!(!re.is_match("/a")); - - let re = ResourceDef::new("/name"); - assert!(re.is_match("/name")); - assert!(!re.is_match("/name1")); - assert!(!re.is_match("/name/")); - assert!(!re.is_match("/name~")); - - let re = ResourceDef::new("/name/"); - assert!(re.is_match("/name/")); - assert!(!re.is_match("/name")); - assert!(!re.is_match("/name/gs")); - - let re = ResourceDef::new("/user/profile"); - assert!(re.is_match("/user/profile")); - assert!(!re.is_match("/user/profile/profile")); - } - - #[test] - fn test_parse_param() { - let re = ResourceDef::new("/user/{id}"); - assert!(re.is_match("/user/profile")); - assert!(re.is_match("/user/2345")); - assert!(!re.is_match("/user/2345/")); - assert!(!re.is_match("/user/2345/sdg")); - - let req = TestRequest::with_uri("/user/profile").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(info.get("id").unwrap(), "profile"); - - let req = TestRequest::with_uri("/user/1245125").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(info.get("id").unwrap(), "1245125"); - - let re = ResourceDef::new("/v{version}/resource/{id}"); - assert!(re.is_match("/v1/resource/320120")); - assert!(!re.is_match("/v/resource/1")); - assert!(!re.is_match("/resource")); - - let req = TestRequest::with_uri("/v151/resource/adahg32").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(info.get("version").unwrap(), "151"); - assert_eq!(info.get("id").unwrap(), "adahg32"); - - let re = ResourceDef::new("/{id:[[:digit:]]{6}}"); - assert!(re.is_match("/012345")); - assert!(!re.is_match("/012")); - assert!(!re.is_match("/01234567")); - assert!(!re.is_match("/XXXXXX")); - - let req = TestRequest::with_uri("/012345").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(info.get("id").unwrap(), "012345"); - } - - #[test] - fn test_resource_prefix() { - let re = ResourceDef::prefix("/name"); - assert!(re.is_match("/name")); - assert!(re.is_match("/name/")); - assert!(re.is_match("/name/test/test")); - assert!(re.is_match("/name1")); - assert!(re.is_match("/name~")); - - let re = ResourceDef::prefix("/name/"); - assert!(re.is_match("/name/")); - assert!(re.is_match("/name/gs")); - assert!(!re.is_match("/name")); - } - - #[test] - fn test_reousrce_prefix_dynamic() { - let re = ResourceDef::prefix("/{name}/"); - assert!(re.is_match("/name/")); - assert!(re.is_match("/name/gs")); - assert!(!re.is_match("/name")); - - let req = TestRequest::with_uri("/test2/").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(&info["name"], "test2"); - assert_eq!(&info[0], "test2"); - - let req = TestRequest::with_uri("/test2/subpath1/subpath2/index.html").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(&info["name"], "test2"); - assert_eq!(&info[0], "test2"); - } - - #[test] - fn test_request_resource() { - let mut router = Router::<()>::default(); - let mut resource = Resource::new(ResourceDef::new("/index.json")); - resource.name("r1"); - router.register_resource(resource); - let mut resource = Resource::new(ResourceDef::new("/test.json")); - resource.name("r2"); - router.register_resource(resource); - - let req = TestRequest::with_uri("/index.json").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(0)); - - assert_eq!(info.name(), "r1"); - - let req = TestRequest::with_uri("/test.json").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(1)); - assert_eq!(info.name(), "r2"); - } - - #[test] - fn test_has_resource() { - let mut router = Router::<()>::default(); - let scope = Scope::new("/test").resource("/name", |_| "done"); - router.register_scope(scope); - - { - let info = router.default_route_info(); - assert!(!info.has_resource("/test")); - assert!(info.has_resource("/test/name")); - } - - let scope = - Scope::new("/test2").nested("/test10", |s| s.resource("/name", |_| "done")); - router.register_scope(scope); - - let info = router.default_route_info(); - assert!(info.has_resource("/test2/test10/name")); - } - - #[test] - fn test_url_for() { - let mut router = Router::<()>::new(ResourceDef::prefix("")); - - let mut resource = Resource::new(ResourceDef::new("/tttt")); - resource.name("r0"); - router.register_resource(resource); - - let scope = Scope::new("/test").resource("/name", |r| { - r.name("r1"); - }); - router.register_scope(scope); - - let scope = Scope::new("/test2") - .nested("/test10", |s| s.resource("/name", |r| r.name("r2"))); - router.register_scope(scope); - router.finish(); - - let req = TestRequest::with_uri("/test").request(); - { - let info = router.default_route_info(); - - let res = info - .url_for(&req, "r0", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/tttt"); - - let res = info - .url_for(&req, "r1", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/test/name"); - - let res = info - .url_for(&req, "r2", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/test2/test10/name"); - } - - let req = TestRequest::with_uri("/test/name").request(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(1)); - - let res = info - .url_for(&req, "r0", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/tttt"); - - let res = info - .url_for(&req, "r1", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/test/name"); - - let res = info - .url_for(&req, "r2", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/test2/test10/name"); - } - - #[test] - fn test_url_for_dynamic() { - let mut router = Router::<()>::new(ResourceDef::prefix("")); - - let mut resource = Resource::new(ResourceDef::new("/{name}/test/index.{ext}")); - resource.name("r0"); - router.register_resource(resource); - - let scope = Scope::new("/{name1}").nested("/{name2}", |s| { - s.resource("/{name3}/test/index.{ext}", |r| r.name("r2")) - }); - router.register_scope(scope); - router.finish(); - - let req = TestRequest::with_uri("/test").request(); - { - let info = router.default_route_info(); - - let res = info.url_for(&req, "r0", vec!["sec1", "html"]).unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/sec1/test/index.html"); - - let res = info - .url_for(&req, "r2", vec!["sec1", "sec2", "sec3", "html"]) - .unwrap(); - assert_eq!( - res.as_str(), - "http://localhost:8080/sec1/sec2/sec3/test/index.html" - ); - } - } -} diff --git a/src/scope.rs b/src/scope.rs deleted file mode 100644 index 43789d427..000000000 --- a/src/scope.rs +++ /dev/null @@ -1,1236 +0,0 @@ -use std::marker::PhantomData; -use std::mem; -use std::rc::Rc; - -use futures::{Async, Future, Poll}; - -use error::Error; -use handler::{ - AsyncResult, AsyncResultItem, FromRequest, Handler, Responder, RouteHandler, - WrapHandler, -}; -use http::Method; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{ - Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse, - Started as MiddlewareStarted, -}; -use pred::Predicate; -use resource::{DefaultResource, Resource}; -use router::{ResourceDef, Router}; -use server::Request; -use with::WithFactory; - -/// Resources scope -/// -/// Scope is a set of resources with common root path. -/// Scopes collect multiple paths under a common path prefix. -/// Scope path can contain variable path segments as resources. -/// Scope prefix is always complete path segment, i.e `/app` would -/// be converted to a `/app/` and it would not match `/app` path. -/// -/// You can get variable path segments from `HttpRequest::match_info()`. -/// `Path` extractor also is able to extract scope level variable segments. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{http, App, HttpRequest, HttpResponse}; -/// -/// fn main() { -/// let app = App::new().scope("/{project_id}/", |scope| { -/// scope -/// .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) -/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) -/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) -/// }); -/// } -/// ``` -/// -/// In the above example three routes get registered: -/// * /{project_id}/path1 - reponds to all http method -/// * /{project_id}/path2 - `GET` requests -/// * /{project_id}/path3 - `HEAD` requests -/// -pub struct Scope { - rdef: ResourceDef, - router: Rc>, - filters: Vec>>, - middlewares: Rc>>>, -} - -#[cfg_attr( - feature = "cargo-clippy", - allow(clippy::new_without_default_derive) -)] -impl Scope { - /// Create a new scope - pub fn new(path: &str) -> Scope { - let rdef = ResourceDef::prefix(path); - Scope { - rdef: rdef.clone(), - router: Rc::new(Router::new(rdef)), - filters: Vec::new(), - middlewares: Rc::new(Vec::new()), - } - } - - #[inline] - pub(crate) fn rdef(&self) -> &ResourceDef { - &self.rdef - } - - pub(crate) fn router(&self) -> &Router { - self.router.as_ref() - } - - #[inline] - pub(crate) fn take_filters(&mut self) -> Vec>> { - mem::replace(&mut self.filters, Vec::new()) - } - - /// Add match predicate to scope. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, pred, App, HttpRequest, HttpResponse, Path}; - /// - /// fn index(data: Path<(String, String)>) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope - /// .filter(pred::Header("content-type", "text/plain")) - /// .route("/test1", http::Method::GET, index) - /// .route("/test2", http::Method::POST, |_: HttpRequest| { - /// HttpResponse::MethodNotAllowed() - /// }) - /// }); - /// } - /// ``` - pub fn filter + 'static>(mut self, p: T) -> Self { - self.filters.push(Box::new(p)); - self - } - - /// Create nested scope with new state. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{App, HttpRequest}; - /// - /// struct AppState; - /// - /// fn index(req: &HttpRequest) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope.with_state("/state2", AppState, |scope| { - /// scope.resource("/test1", |r| r.f(index)) - /// }) - /// }); - /// } - /// ``` - pub fn with_state(mut self, path: &str, state: T, f: F) -> Scope - where - F: FnOnce(Scope) -> Scope, - { - let rdef = ResourceDef::prefix(path); - let scope = Scope { - rdef: rdef.clone(), - filters: Vec::new(), - router: Rc::new(Router::new(rdef)), - middlewares: Rc::new(Vec::new()), - }; - let mut scope = f(scope); - - let state = Rc::new(state); - let filters: Vec>> = vec![Box::new(FiltersWrapper { - state: Rc::clone(&state), - filters: scope.take_filters(), - })]; - let handler = Box::new(Wrapper { scope, state }); - - Rc::get_mut(&mut self.router).unwrap().register_handler( - path, - handler, - Some(filters), - ); - - self - } - - /// Create nested scope. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{App, HttpRequest}; - /// - /// struct AppState; - /// - /// fn index(req: &HttpRequest) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::with_state(AppState).scope("/app", |scope| { - /// scope.nested("/v1", |scope| scope.resource("/test1", |r| r.f(index))) - /// }); - /// } - /// ``` - pub fn nested(mut self, path: &str, f: F) -> Scope - where - F: FnOnce(Scope) -> Scope, - { - let rdef = ResourceDef::prefix(&insert_slash(path)); - let scope = Scope { - rdef: rdef.clone(), - filters: Vec::new(), - router: Rc::new(Router::new(rdef)), - middlewares: Rc::new(Vec::new()), - }; - Rc::get_mut(&mut self.router) - .unwrap() - .register_scope(f(scope)); - - self - } - - /// Configure route for a specific path. - /// - /// This is a simplified version of the `Scope::resource()` method. - /// Handler functions need to accept one request extractor - /// argument. - /// - /// This method could be called multiple times, in that case - /// multiple routes would be registered for same resource path. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse, Path}; - /// - /// fn index(data: Path<(String, String)>) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope.route("/test1", http::Method::GET, index).route( - /// "/test2", - /// http::Method::POST, - /// |_: HttpRequest| HttpResponse::MethodNotAllowed(), - /// ) - /// }); - /// } - /// ``` - pub fn route(mut self, path: &str, method: Method, f: F) -> Scope - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - { - Rc::get_mut(&mut self.router).unwrap().register_route( - &insert_slash(path), - method, - f, - ); - self - } - - /// Configure resource for a specific path. - /// - /// This method is similar to an `App::resource()` method. - /// Resources may have variable path segments. Resource path uses scope - /// path as a path prefix. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// - /// fn main() { - /// let app = App::new().scope("/api", |scope| { - /// scope.resource("/users/{userid}/{friend}", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// r.route() - /// .filter(pred::Any(pred::Get()).or(pred::Put())) - /// .filter(pred::Header("Content-Type", "text/plain")) - /// .f(|_| HttpResponse::Ok()) - /// }) - /// }); - /// } - /// ``` - pub fn resource(mut self, path: &str, f: F) -> Scope - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // add resource - let mut resource = Resource::new(ResourceDef::new(&insert_slash(path))); - f(&mut resource); - - Rc::get_mut(&mut self.router) - .unwrap() - .register_resource(resource); - self - } - - /// Default resource to be used if no matching route could be found. - pub fn default_resource(mut self, f: F) -> Scope - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // create and configure default resource - let mut resource = Resource::new(ResourceDef::new("")); - f(&mut resource); - - Rc::get_mut(&mut self.router) - .expect("Multiple copies of scope router") - .register_default_resource(resource.into()); - - self - } - - /// Configure handler for specific path prefix. - /// - /// A path prefix consists of valid path segments, i.e for the - /// prefix `/app` any request with the paths `/app`, `/app/` or - /// `/app/test` would match, but the path `/application` would - /// not. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().scope("/scope-prefix", |scope| { - /// scope.handler("/app", |req: &HttpRequest| match *req.method() { - /// http::Method::GET => HttpResponse::Ok(), - /// http::Method::POST => HttpResponse::MethodNotAllowed(), - /// _ => HttpResponse::NotFound(), - /// }) - /// }); - /// } - /// ``` - pub fn handler>(mut self, path: &str, handler: H) -> Scope { - let path = insert_slash(path.trim().trim_right_matches('/')); - Rc::get_mut(&mut self.router) - .expect("Multiple copies of scope router") - .register_handler(&path, Box::new(WrapHandler::new(handler)), None); - self - } - - /// Register a scope middleware - /// - /// This is similar to `App's` middlewares, but - /// middlewares get invoked on scope level. - /// - /// *Note* `Middleware::finish()` fires right after response get - /// prepared. It does not wait until body get sent to the peer. - pub fn middleware>(mut self, mw: M) -> Scope { - Rc::get_mut(&mut self.middlewares) - .expect("Can not use after configuration") - .push(Box::new(mw)); - self - } -} - -fn insert_slash(path: &str) -> String { - let mut path = path.to_owned(); - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/'); - }; - path -} - -impl RouteHandler for Scope { - fn handle(&self, req: &HttpRequest) -> AsyncResult { - let tail = req.match_info().tail as usize; - - // recognize resources - let info = self.router.recognize(req, req.state(), tail); - let req2 = req.with_route_info(info); - if self.middlewares.is_empty() { - self.router.handle(&req2) - } else { - AsyncResult::async(Box::new(Compose::new( - req2, - Rc::clone(&self.router), - Rc::clone(&self.middlewares), - ))) - } - } - - fn has_default_resource(&self) -> bool { - self.router.has_default_resource() - } - - fn default_resource(&mut self, default: DefaultResource) { - Rc::get_mut(&mut self.router) - .expect("Can not use after configuration") - .register_default_resource(default); - } - - fn finish(&mut self) { - Rc::get_mut(&mut self.router) - .expect("Can not use after configuration") - .finish(); - } -} - -struct Wrapper { - state: Rc, - scope: Scope, -} - -impl RouteHandler for Wrapper { - fn handle(&self, req: &HttpRequest) -> AsyncResult { - let req = req.with_state(Rc::clone(&self.state)); - self.scope.handle(&req) - } -} - -struct FiltersWrapper { - state: Rc, - filters: Vec>>, -} - -impl Predicate for FiltersWrapper { - fn check(&self, req: &Request, _: &S2) -> bool { - for filter in &self.filters { - if !filter.check(&req, &self.state) { - return false; - } - } - true - } -} - -/// Compose resource level middlewares with route handler. -struct Compose { - info: ComposeInfo, - state: ComposeState, -} - -struct ComposeInfo { - count: usize, - req: HttpRequest, - router: Rc>, - mws: Rc>>>, -} - -enum ComposeState { - Starting(StartMiddlewares), - Handler(WaitingResponse), - RunMiddlewares(RunMiddlewares), - Finishing(FinishingMiddlewares), - Completed(Response), -} - -impl ComposeState { - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - match *self { - ComposeState::Starting(ref mut state) => state.poll(info), - ComposeState::Handler(ref mut state) => state.poll(info), - ComposeState::RunMiddlewares(ref mut state) => state.poll(info), - ComposeState::Finishing(ref mut state) => state.poll(info), - ComposeState::Completed(_) => None, - } - } -} - -impl Compose { - fn new( - req: HttpRequest, router: Rc>, mws: Rc>>>, - ) -> Self { - let mut info = ComposeInfo { - mws, - req, - router, - count: 0, - }; - let state = StartMiddlewares::init(&mut info); - - Compose { state, info } - } -} - -impl Future for Compose { - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - loop { - if let ComposeState::Completed(ref mut resp) = self.state { - let resp = resp.resp.take().unwrap(); - return Ok(Async::Ready(resp)); - } - if let Some(state) = self.state.poll(&mut self.info) { - self.state = state; - } else { - return Ok(Async::NotReady); - } - } - } -} - -/// Middlewares start executor -struct StartMiddlewares { - fut: Option, - _s: PhantomData, -} - -type Fut = Box, Error = Error>>; - -impl StartMiddlewares { - fn init(info: &mut ComposeInfo) -> ComposeState { - let len = info.mws.len(); - - loop { - if info.count == len { - let reply = info.router.handle(&info.req); - return WaitingResponse::init(info, reply); - } else { - let result = info.mws[info.count].start(&info.req); - match result { - Ok(MiddlewareStarted::Done) => info.count += 1, - Ok(MiddlewareStarted::Response(resp)) => { - return RunMiddlewares::init(info, resp); - } - Ok(MiddlewareStarted::Future(fut)) => { - return ComposeState::Starting(StartMiddlewares { - fut: Some(fut), - _s: PhantomData, - }); - } - Err(err) => { - return RunMiddlewares::init(info, err.into()); - } - } - } - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.len(); - - 'outer: loop { - match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => { - return None; - } - Ok(Async::Ready(resp)) => { - info.count += 1; - - if let Some(resp) = resp { - return Some(RunMiddlewares::init(info, resp)); - } - loop { - if info.count == len { - let reply = info.router.handle(&info.req); - return Some(WaitingResponse::init(info, reply)); - } else { - let result = info.mws[info.count].start(&info.req); - match result { - Ok(MiddlewareStarted::Done) => info.count += 1, - Ok(MiddlewareStarted::Response(resp)) => { - return Some(RunMiddlewares::init(info, resp)); - } - Ok(MiddlewareStarted::Future(fut)) => { - self.fut = Some(fut); - continue 'outer; - } - Err(err) => { - return Some(RunMiddlewares::init(info, err.into())); - } - } - } - } - } - Err(err) => { - return Some(RunMiddlewares::init(info, err.into())); - } - } - } - } -} - -// waiting for response -struct WaitingResponse { - fut: Box>, - _s: PhantomData, -} - -impl WaitingResponse { - #[inline] - fn init( - info: &mut ComposeInfo, reply: AsyncResult, - ) -> ComposeState { - match reply.into() { - AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp), - AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), - AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse { - fut, - _s: PhantomData, - }), - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - match self.fut.poll() { - Ok(Async::NotReady) => None, - Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, resp)), - Err(err) => Some(RunMiddlewares::init(info, err.into())), - } - } -} - -/// Middlewares response executor -struct RunMiddlewares { - curr: usize, - fut: Option>>, - _s: PhantomData, -} - -impl RunMiddlewares { - fn init(info: &mut ComposeInfo, mut resp: HttpResponse) -> ComposeState { - let mut curr = 0; - let len = info.mws.len(); - - loop { - let state = info.mws[curr].response(&info.req, resp); - resp = match state { - Err(err) => { - info.count = curr + 1; - return FinishingMiddlewares::init(info, err.into()); - } - Ok(MiddlewareResponse::Done(r)) => { - curr += 1; - if curr == len { - return FinishingMiddlewares::init(info, r); - } else { - r - } - } - Ok(MiddlewareResponse::Future(fut)) => { - return ComposeState::RunMiddlewares(RunMiddlewares { - curr, - fut: Some(fut), - _s: PhantomData, - }); - } - }; - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.len(); - - loop { - // poll latest fut - let mut resp = match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => return None, - Ok(Async::Ready(resp)) => { - self.curr += 1; - resp - } - Err(err) => return Some(FinishingMiddlewares::init(info, err.into())), - }; - - loop { - if self.curr == len { - return Some(FinishingMiddlewares::init(info, resp)); - } else { - let state = info.mws[self.curr].response(&info.req, resp); - match state { - Err(err) => { - return Some(FinishingMiddlewares::init(info, err.into())) - } - Ok(MiddlewareResponse::Done(r)) => { - self.curr += 1; - resp = r - } - Ok(MiddlewareResponse::Future(fut)) => { - self.fut = Some(fut); - break; - } - } - } - } - } - } -} - -/// Middlewares start executor -struct FinishingMiddlewares { - resp: Option, - fut: Option>>, - _s: PhantomData, -} - -impl FinishingMiddlewares { - fn init(info: &mut ComposeInfo, resp: HttpResponse) -> ComposeState { - if info.count == 0 { - Response::init(resp) - } else { - let mut state = FinishingMiddlewares { - resp: Some(resp), - fut: None, - _s: PhantomData, - }; - if let Some(st) = state.poll(info) { - st - } else { - ComposeState::Finishing(state) - } - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - loop { - // poll latest fut - let not_ready = if let Some(ref mut fut) = self.fut { - match fut.poll() { - Ok(Async::NotReady) => true, - Ok(Async::Ready(())) => false, - Err(err) => { - error!("Middleware finish error: {}", err); - false - } - } - } else { - false - }; - if not_ready { - return None; - } - self.fut = None; - if info.count == 0 { - return Some(Response::init(self.resp.take().unwrap())); - } - - info.count -= 1; - let state = info.mws[info.count as usize] - .finish(&info.req, self.resp.as_ref().unwrap()); - match state { - MiddlewareFinished::Done => { - if info.count == 0 { - return Some(Response::init(self.resp.take().unwrap())); - } - } - MiddlewareFinished::Future(fut) => { - self.fut = Some(fut); - } - } - } - } -} - -struct Response { - resp: Option, - _s: PhantomData, -} - -impl Response { - fn init(resp: HttpResponse) -> ComposeState { - ComposeState::Completed(Response { - resp: Some(resp), - _s: PhantomData, - }) - } -} - -#[cfg(test)] -mod tests { - use bytes::Bytes; - - use application::App; - use body::Body; - use http::{Method, StatusCode}; - use httprequest::HttpRequest; - use httpresponse::HttpResponse; - use pred; - use test::TestRequest; - - #[test] - fn test_scope() { - let app = App::new() - .scope("/app", |scope| { - scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }).finish(); - - let req = TestRequest::with_uri("/app/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_scope_root() { - let app = App::new() - .scope("/app", |scope| { - scope - .resource("", |r| r.f(|_| HttpResponse::Ok())) - .resource("/", |r| r.f(|_| HttpResponse::Created())) - }).finish(); - - let req = TestRequest::with_uri("/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_scope_root2() { - let app = App::new() - .scope("/app/", |scope| { - scope.resource("", |r| r.f(|_| HttpResponse::Ok())) - }).finish(); - - let req = TestRequest::with_uri("/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_scope_root3() { - let app = App::new() - .scope("/app/", |scope| { - scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) - }).finish(); - - let req = TestRequest::with_uri("/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_route() { - let app = App::new() - .scope("app", |scope| { - scope - .route("/path1", Method::GET, |_: HttpRequest<_>| { - HttpResponse::Ok() - }).route("/path1", Method::DELETE, |_: HttpRequest<_>| { - HttpResponse::Ok() - }) - }).finish(); - - let req = TestRequest::with_uri("/app/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::DELETE) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_route_without_leading_slash() { - let app = App::new() - .scope("app", |scope| { - scope - .route("path1", Method::GET, |_: HttpRequest<_>| HttpResponse::Ok()) - .route("path1", Method::DELETE, |_: HttpRequest<_>| { - HttpResponse::Ok() - }) - }).finish(); - - let req = TestRequest::with_uri("/app/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::DELETE) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_filter() { - let app = App::new() - .scope("/app", |scope| { - scope - .filter(pred::Get()) - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }).finish(); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::GET) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_scope_variable_segment() { - let app = App::new() - .scope("/ab-{project}", |scope| { - scope.resource("/path1", |r| { - r.f(|r| { - HttpResponse::Ok() - .body(format!("project: {}", &r.match_info()["project"])) - }) - }) - }).finish(); - - let req = TestRequest::with_uri("/ab-project1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - match resp.as_msg().body() { - &Body::Binary(ref b) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: project1")); - } - _ => panic!(), - } - - let req = TestRequest::with_uri("/aa-project1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_with_state() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1", State, |scope| { - scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_scope_with_state_root() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1", State, |scope| { - scope - .resource("", |r| r.f(|_| HttpResponse::Ok())) - .resource("/", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/t1/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_scope_with_state_root2() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1/", State, |scope| { - scope.resource("", |r| r.f(|_| HttpResponse::Ok())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_scope_with_state_root3() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1/", State, |scope| { - scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_with_state_filter() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1", State, |scope| { - scope - .filter(pred::Get()) - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::GET) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_nested_scope() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_nested_scope_no_slash() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("t1", |scope| { - scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_nested_scope_root() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope - .resource("", |r| r.f(|_| HttpResponse::Ok())) - .resource("/", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/t1/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_nested_scope_filter() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope - .filter(pred::Get()) - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::GET) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_nested_scope_with_variable_segment() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/{project_id}", |scope| { - scope.resource("/path1", |r| { - r.f(|r| { - HttpResponse::Created().body(format!( - "project: {}", - &r.match_info()["project_id"] - )) - }) - }) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/project_1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - - match resp.as_msg().body() { - &Body::Binary(ref b) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: project_1")); - } - _ => panic!(), - } - } - - #[test] - fn test_nested2_scope_with_variable_segment() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/{project}", |scope| { - scope.nested("/{id}", |scope| { - scope.resource("/path1", |r| { - r.f(|r| { - HttpResponse::Created().body(format!( - "project: {} - {}", - &r.match_info()["project"], - &r.match_info()["id"], - )) - }) - }) - }) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/test/1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - - match resp.as_msg().body() { - &Body::Binary(ref b) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); - } - _ => panic!(), - } - - let req = TestRequest::with_uri("/app/test/1/path2").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_default_resource() { - let app = App::new() - .scope("/app", |scope| { - scope - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - .default_resource(|r| r.f(|_| HttpResponse::BadRequest())) - }).finish(); - - let req = TestRequest::with_uri("/app/path2").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST); - - let req = TestRequest::with_uri("/path2").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_default_resource_propagation() { - let app = App::new() - .scope("/app1", |scope| { - scope.default_resource(|r| r.f(|_| HttpResponse::BadRequest())) - }).scope("/app2", |scope| scope) - .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) - .finish(); - - let req = TestRequest::with_uri("/non-exist").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); - - let req = TestRequest::with_uri("/app1/non-exist").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST); - - let req = TestRequest::with_uri("/app2/non-exist").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); - } - - #[test] - fn test_handler() { - let app = App::new() - .scope("/scope", |scope| { - scope.handler("/test", |_: &_| HttpResponse::Ok()) - }).finish(); - - let req = TestRequest::with_uri("/scope/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/scope/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/scope/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/scope/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/scope/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } -} diff --git a/src/server/acceptor.rs b/src/server/acceptor.rs deleted file mode 100644 index 2e1b1f283..000000000 --- a/src/server/acceptor.rs +++ /dev/null @@ -1,396 +0,0 @@ -use std::time::Duration; -use std::{fmt, net}; - -use actix_net::server::ServerMessage; -use actix_net::service::{NewService, Service}; -use futures::future::{err, ok, Either, FutureResult}; -use futures::{Async, Future, Poll}; -use tokio_reactor::Handle; -use tokio_tcp::TcpStream; -use tokio_timer::{sleep, Delay}; - -use super::channel::HttpProtocol; -use super::error::AcceptorError; -use super::handler::HttpHandler; -use super::settings::ServiceConfig; -use super::IoStream; - -/// This trait indicates types that can create acceptor service for http server. -pub trait AcceptorServiceFactory: Send + Clone + 'static { - type Io: IoStream + Send; - type NewService: NewService; - - fn create(&self) -> Self::NewService; -} - -impl AcceptorServiceFactory for F -where - F: Fn() -> T + Send + Clone + 'static, - T::Response: IoStream + Send, - T: NewService, - T::InitError: fmt::Debug, -{ - type Io = T::Response; - type NewService = T; - - fn create(&self) -> T { - (self)() - } -} - -#[derive(Clone)] -/// Default acceptor service convert `TcpStream` to a `tokio_tcp::TcpStream` -pub(crate) struct DefaultAcceptor; - -impl AcceptorServiceFactory for DefaultAcceptor { - type Io = TcpStream; - type NewService = DefaultAcceptor; - - fn create(&self) -> Self::NewService { - DefaultAcceptor - } -} - -impl NewService for DefaultAcceptor { - type Request = TcpStream; - type Response = TcpStream; - type Error = (); - type InitError = (); - type Service = DefaultAcceptor; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(DefaultAcceptor) - } -} - -impl Service for DefaultAcceptor { - type Request = TcpStream; - type Response = TcpStream; - type Error = (); - type Future = FutureResult; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - ok(req) - } -} - -pub(crate) struct TcpAcceptor { - inner: T, -} - -impl TcpAcceptor -where - T: NewService>, - T::InitError: fmt::Debug, -{ - pub(crate) fn new(inner: T) -> Self { - TcpAcceptor { inner } - } -} - -impl NewService for TcpAcceptor -where - T: NewService>, - T::InitError: fmt::Debug, -{ - type Request = net::TcpStream; - type Response = T::Response; - type Error = AcceptorError; - type InitError = T::InitError; - type Service = TcpAcceptorService; - type Future = TcpAcceptorResponse; - - fn new_service(&self) -> Self::Future { - TcpAcceptorResponse { - fut: self.inner.new_service(), - } - } -} - -pub(crate) struct TcpAcceptorResponse -where - T: NewService, - T::InitError: fmt::Debug, -{ - fut: T::Future, -} - -impl Future for TcpAcceptorResponse -where - T: NewService, - T::InitError: fmt::Debug, -{ - type Item = TcpAcceptorService; - type Error = T::InitError; - - fn poll(&mut self) -> Poll { - match self.fut.poll() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(service)) => { - Ok(Async::Ready(TcpAcceptorService { inner: service })) - } - Err(e) => { - error!("Can not create accetor service: {:?}", e); - Err(e) - } - } - } -} - -pub(crate) struct TcpAcceptorService { - inner: T, -} - -impl Service for TcpAcceptorService -where - T: Service>, -{ - type Request = net::TcpStream; - type Response = T::Response; - type Error = AcceptorError; - type Future = Either>; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.inner.poll_ready() - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - let stream = TcpStream::from_std(req, &Handle::default()).map_err(|e| { - error!("Can not convert to an async tcp stream: {}", e); - AcceptorError::Io(e) - }); - - match stream { - Ok(stream) => Either::A(self.inner.call(stream)), - Err(e) => Either::B(err(e)), - } - } -} - -#[doc(hidden)] -/// Acceptor timeout middleware -/// -/// Applies timeout to request prcoessing. -pub struct AcceptorTimeout { - inner: T, - timeout: Duration, -} - -impl AcceptorTimeout { - /// Create new `AcceptorTimeout` instance. timeout is in milliseconds. - pub fn new(timeout: u64, inner: T) -> Self { - Self { - inner, - timeout: Duration::from_millis(timeout), - } - } -} - -impl NewService for AcceptorTimeout { - type Request = T::Request; - type Response = T::Response; - type Error = AcceptorError; - type InitError = T::InitError; - type Service = AcceptorTimeoutService; - type Future = AcceptorTimeoutFut; - - fn new_service(&self) -> Self::Future { - AcceptorTimeoutFut { - fut: self.inner.new_service(), - timeout: self.timeout, - } - } -} - -#[doc(hidden)] -pub struct AcceptorTimeoutFut { - fut: T::Future, - timeout: Duration, -} - -impl Future for AcceptorTimeoutFut { - type Item = AcceptorTimeoutService; - type Error = T::InitError; - - fn poll(&mut self) -> Poll { - let inner = try_ready!(self.fut.poll()); - Ok(Async::Ready(AcceptorTimeoutService { - inner, - timeout: self.timeout, - })) - } -} - -#[doc(hidden)] -/// Acceptor timeout service -/// -/// Applies timeout to request prcoessing. -pub struct AcceptorTimeoutService { - inner: T, - timeout: Duration, -} - -impl Service for AcceptorTimeoutService { - type Request = T::Request; - type Response = T::Response; - type Error = AcceptorError; - type Future = AcceptorTimeoutResponse; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.inner.poll_ready().map_err(AcceptorError::Service) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - AcceptorTimeoutResponse { - fut: self.inner.call(req), - sleep: sleep(self.timeout), - } - } -} - -#[doc(hidden)] -pub struct AcceptorTimeoutResponse { - fut: T::Future, - sleep: Delay, -} - -impl Future for AcceptorTimeoutResponse { - type Item = T::Response; - type Error = AcceptorError; - - fn poll(&mut self) -> Poll { - match self.fut.poll().map_err(AcceptorError::Service)? { - Async::NotReady => match self.sleep.poll() { - Err(_) => Err(AcceptorError::Timeout), - Ok(Async::Ready(_)) => Err(AcceptorError::Timeout), - Ok(Async::NotReady) => Ok(Async::NotReady), - }, - Async::Ready(resp) => Ok(Async::Ready(resp)), - } - } -} - -pub(crate) struct ServerMessageAcceptor { - inner: T, - settings: ServiceConfig, -} - -impl ServerMessageAcceptor -where - H: HttpHandler, - T: NewService, -{ - pub(crate) fn new(settings: ServiceConfig, inner: T) -> Self { - ServerMessageAcceptor { inner, settings } - } -} - -impl NewService for ServerMessageAcceptor -where - H: HttpHandler, - T: NewService, -{ - type Request = ServerMessage; - type Response = (); - type Error = T::Error; - type InitError = T::InitError; - type Service = ServerMessageAcceptorService; - type Future = ServerMessageAcceptorResponse; - - fn new_service(&self) -> Self::Future { - ServerMessageAcceptorResponse { - fut: self.inner.new_service(), - settings: self.settings.clone(), - } - } -} - -pub(crate) struct ServerMessageAcceptorResponse -where - H: HttpHandler, - T: NewService, -{ - fut: T::Future, - settings: ServiceConfig, -} - -impl Future for ServerMessageAcceptorResponse -where - H: HttpHandler, - T: NewService, -{ - type Item = ServerMessageAcceptorService; - type Error = T::InitError; - - fn poll(&mut self) -> Poll { - match self.fut.poll()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(service) => Ok(Async::Ready(ServerMessageAcceptorService { - inner: service, - settings: self.settings.clone(), - })), - } - } -} - -pub(crate) struct ServerMessageAcceptorService { - inner: T, - settings: ServiceConfig, -} - -impl Service for ServerMessageAcceptorService -where - H: HttpHandler, - T: Service, -{ - type Request = ServerMessage; - type Response = (); - type Error = T::Error; - type Future = - Either, FutureResult<(), Self::Error>>; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.inner.poll_ready() - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - match req { - ServerMessage::Connect(stream) => { - Either::A(ServerMessageAcceptorServiceFut { - fut: self.inner.call(stream), - }) - } - ServerMessage::Shutdown(_) => Either::B(ok(())), - ServerMessage::ForceShutdown => { - self.settings - .head() - .traverse(|proto: &mut HttpProtocol| proto.shutdown()); - Either::B(ok(())) - } - } - } -} - -pub(crate) struct ServerMessageAcceptorServiceFut { - fut: T::Future, -} - -impl Future for ServerMessageAcceptorServiceFut -where - T: Service, -{ - type Item = (); - type Error = T::Error; - - fn poll(&mut self) -> Poll { - match self.fut.poll()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(_) => Ok(Async::Ready(())), - } - } -} diff --git a/src/server/builder.rs b/src/server/builder.rs deleted file mode 100644 index ec6ce9923..000000000 --- a/src/server/builder.rs +++ /dev/null @@ -1,117 +0,0 @@ -use std::{fmt, net}; - -use actix_net::either::Either; -use actix_net::server::{Server, ServiceFactory}; -use actix_net::service::{NewService, NewServiceExt}; - -use super::acceptor::{ - AcceptorServiceFactory, AcceptorTimeout, ServerMessageAcceptor, TcpAcceptor, -}; -use super::error::AcceptorError; -use super::handler::IntoHttpHandler; -use super::service::HttpService; -use super::settings::{ServerSettings, ServiceConfig}; -use super::KeepAlive; - -pub(crate) trait ServiceProvider { - fn register( - &self, server: Server, lst: net::TcpListener, host: String, - addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64, - client_shutdown: u64, - ) -> Server; -} - -/// Utility type that builds complete http pipeline -pub(crate) struct HttpServiceBuilder -where - F: Fn() -> H + Send + Clone, -{ - factory: F, - acceptor: A, -} - -impl HttpServiceBuilder -where - F: Fn() -> H + Send + Clone + 'static, - H: IntoHttpHandler, - A: AcceptorServiceFactory, - ::InitError: fmt::Debug, -{ - /// Create http service builder - pub fn new(factory: F, acceptor: A) -> Self { - Self { factory, acceptor } - } - - fn finish( - &self, host: String, addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, - client_timeout: u64, client_shutdown: u64, - ) -> impl ServiceFactory { - let factory = self.factory.clone(); - let acceptor = self.acceptor.clone(); - move || { - let app = (factory)().into_handler(); - let settings = ServiceConfig::new( - app, - keep_alive, - client_timeout, - client_shutdown, - ServerSettings::new(addr, &host, false), - ); - - if secure { - Either::B(ServerMessageAcceptor::new( - settings.clone(), - TcpAcceptor::new(AcceptorTimeout::new( - client_timeout, - acceptor.create(), - )).map_err(|_| ()) - .map_init_err(|_| ()) - .and_then( - HttpService::new(settings) - .map_init_err(|_| ()) - .map_err(|_| ()), - ), - )) - } else { - Either::A(ServerMessageAcceptor::new( - settings.clone(), - TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service)) - .map_err(|_| ()) - .map_init_err(|_| ()) - .and_then( - HttpService::new(settings) - .map_init_err(|_| ()) - .map_err(|_| ()), - ), - )) - } - } - } -} - -impl ServiceProvider for HttpServiceBuilder -where - F: Fn() -> H + Send + Clone + 'static, - A: AcceptorServiceFactory, - ::InitError: fmt::Debug, - H: IntoHttpHandler, -{ - fn register( - &self, server: Server, lst: net::TcpListener, host: String, - addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64, - client_shutdown: u64, - ) -> Server { - server.listen2( - "actix-web", - lst, - self.finish( - host, - addr, - keep_alive, - secure, - client_timeout, - client_shutdown, - ), - ) - } -} diff --git a/src/server/channel.rs b/src/server/channel.rs deleted file mode 100644 index b1fef964e..000000000 --- a/src/server/channel.rs +++ /dev/null @@ -1,436 +0,0 @@ -use std::net::Shutdown; -use std::{io, mem, time}; - -use bytes::{Buf, BufMut, BytesMut}; -use futures::{Async, Future, Poll}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_timer::Delay; - -use super::error::HttpDispatchError; -use super::settings::ServiceConfig; -use super::{h1, h2, HttpHandler, IoStream}; -use error::Error; -use http::StatusCode; - -const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; - -pub(crate) enum HttpProtocol { - H1(h1::Http1Dispatcher), - H2(h2::Http2), - Unknown(ServiceConfig, T, BytesMut), - None, -} - -impl HttpProtocol { - pub(crate) fn shutdown(&mut self) { - match self { - HttpProtocol::H1(ref mut h1) => { - let io = h1.io(); - let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0))); - let _ = IoStream::shutdown(io, Shutdown::Both); - } - HttpProtocol::H2(ref mut h2) => h2.shutdown(), - HttpProtocol::Unknown(_, io, _) => { - let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0))); - let _ = IoStream::shutdown(io, Shutdown::Both); - } - HttpProtocol::None => (), - } - } -} - -enum ProtocolKind { - Http1, - Http2, -} - -#[doc(hidden)] -pub struct HttpChannel -where - T: IoStream, - H: HttpHandler + 'static, -{ - node: Node>, - node_reg: bool, - ka_timeout: Option, -} - -impl HttpChannel -where - T: IoStream, - H: HttpHandler + 'static, -{ - pub(crate) fn new(settings: ServiceConfig, io: T) -> HttpChannel { - let ka_timeout = settings.client_timer(); - - HttpChannel { - ka_timeout, - node_reg: false, - node: Node::new(HttpProtocol::Unknown( - settings, - io, - BytesMut::with_capacity(8192), - )), - } - } -} - -impl Drop for HttpChannel -where - T: IoStream, - H: HttpHandler + 'static, -{ - fn drop(&mut self) { - self.node.remove(); - } -} - -impl Future for HttpChannel -where - T: IoStream, - H: HttpHandler + 'static, -{ - type Item = (); - type Error = HttpDispatchError; - - fn poll(&mut self) -> Poll { - // keep-alive timer - if self.ka_timeout.is_some() { - match self.ka_timeout.as_mut().unwrap().poll() { - Ok(Async::Ready(_)) => { - trace!("Slow request timed out, close connection"); - let proto = mem::replace(self.node.get_mut(), HttpProtocol::None); - if let HttpProtocol::Unknown(settings, io, buf) = proto { - *self.node.get_mut() = - HttpProtocol::H1(h1::Http1Dispatcher::for_error( - settings, - io, - StatusCode::REQUEST_TIMEOUT, - self.ka_timeout.take(), - buf, - )); - return self.poll(); - } - return Ok(Async::Ready(())); - } - Ok(Async::NotReady) => (), - Err(_) => panic!("Something is really wrong"), - } - } - - if !self.node_reg { - self.node_reg = true; - let settings = match self.node.get_mut() { - HttpProtocol::H1(ref mut h1) => h1.settings().clone(), - HttpProtocol::H2(ref mut h2) => h2.settings().clone(), - HttpProtocol::Unknown(ref mut settings, _, _) => settings.clone(), - HttpProtocol::None => unreachable!(), - }; - settings.head().insert(&mut self.node); - } - - let mut is_eof = false; - let kind = match self.node.get_mut() { - HttpProtocol::H1(ref mut h1) => return h1.poll(), - HttpProtocol::H2(ref mut h2) => return h2.poll(), - HttpProtocol::Unknown(_, ref mut io, ref mut buf) => { - let mut err = None; - let mut disconnect = false; - match io.read_available(buf) { - Ok(Async::Ready((read_some, stream_closed))) => { - is_eof = stream_closed; - // Only disconnect if no data was read. - if is_eof && !read_some { - disconnect = true; - } - } - Err(e) => { - err = Some(e.into()); - } - _ => (), - } - if disconnect { - debug!("Ignored premature client disconnection"); - return Ok(Async::Ready(())); - } else if let Some(e) = err { - return Err(e); - } - - if buf.len() >= 14 { - if buf[..14] == HTTP2_PREFACE[..] { - ProtocolKind::Http2 - } else { - ProtocolKind::Http1 - } - } else { - return Ok(Async::NotReady); - } - } - HttpProtocol::None => unreachable!(), - }; - - // upgrade to specific http protocol - let proto = mem::replace(self.node.get_mut(), HttpProtocol::None); - if let HttpProtocol::Unknown(settings, io, buf) = proto { - match kind { - ProtocolKind::Http1 => { - *self.node.get_mut() = HttpProtocol::H1(h1::Http1Dispatcher::new( - settings, - io, - buf, - is_eof, - self.ka_timeout.take(), - )); - return self.poll(); - } - ProtocolKind::Http2 => { - *self.node.get_mut() = HttpProtocol::H2(h2::Http2::new( - settings, - io, - buf.freeze(), - self.ka_timeout.take(), - )); - return self.poll(); - } - } - } - unreachable!() - } -} - -#[doc(hidden)] -pub struct H1Channel -where - T: IoStream, - H: HttpHandler + 'static, -{ - node: Node>, - node_reg: bool, -} - -impl H1Channel -where - T: IoStream, - H: HttpHandler + 'static, -{ - pub(crate) fn new(settings: ServiceConfig, io: T) -> H1Channel { - H1Channel { - node_reg: false, - node: Node::new(HttpProtocol::H1(h1::Http1Dispatcher::new( - settings, - io, - BytesMut::with_capacity(8192), - false, - None, - ))), - } - } -} - -impl Drop for H1Channel -where - T: IoStream, - H: HttpHandler + 'static, -{ - fn drop(&mut self) { - self.node.remove(); - } -} - -impl Future for H1Channel -where - T: IoStream, - H: HttpHandler + 'static, -{ - type Item = (); - type Error = HttpDispatchError; - - fn poll(&mut self) -> Poll { - if !self.node_reg { - self.node_reg = true; - let settings = match self.node.get_mut() { - HttpProtocol::H1(ref mut h1) => h1.settings().clone(), - _ => unreachable!(), - }; - settings.head().insert(&mut self.node); - } - - match self.node.get_mut() { - HttpProtocol::H1(ref mut h1) => h1.poll(), - _ => unreachable!(), - } - } -} - -pub(crate) struct Node { - next: Option<*mut Node>, - prev: Option<*mut Node>, - element: T, -} - -impl Node { - fn new(element: T) -> Self { - Node { - element, - next: None, - prev: None, - } - } - - fn get_mut(&mut self) -> &mut T { - &mut self.element - } - - fn insert(&mut self, next_el: &mut Node) { - let next: *mut Node = next_el as *const _ as *mut _; - - if let Some(next2) = self.next { - unsafe { - let n = next2.as_mut().unwrap(); - n.prev = Some(next); - } - next_el.next = Some(next2 as *mut _); - } - self.next = Some(next); - - unsafe { - let next: &mut Node = &mut *next; - next.prev = Some(self as *mut _); - } - } - - fn remove(&mut self) { - let next = self.next.take(); - let prev = self.prev.take(); - - if let Some(prev) = prev { - unsafe { - prev.as_mut().unwrap().next = next; - } - } - if let Some(next) = next { - unsafe { - next.as_mut().unwrap().prev = prev; - } - } - } -} - -impl Node<()> { - pub(crate) fn head() -> Self { - Node { - next: None, - prev: None, - element: (), - } - } - - pub(crate) fn traverse)>(&self, f: F) - where - T: IoStream, - H: HttpHandler + 'static, - { - if let Some(n) = self.next.as_ref() { - unsafe { - let mut next: &mut Node> = - &mut *(n.as_ref().unwrap() as *const _ as *mut _); - loop { - f(&mut next.element); - - next = if let Some(n) = next.next.as_ref() { - &mut **n - } else { - return; - } - } - } - } - } -} - -/// Wrapper for `AsyncRead + AsyncWrite` types -pub(crate) struct WrapperStream -where - T: AsyncRead + AsyncWrite + 'static, -{ - io: T, -} - -impl WrapperStream -where - T: AsyncRead + AsyncWrite + 'static, -{ - pub fn new(io: T) -> Self { - WrapperStream { io } - } -} - -impl IoStream for WrapperStream -where - T: AsyncRead + AsyncWrite + 'static, -{ - #[inline] - fn shutdown(&mut self, _: Shutdown) -> io::Result<()> { - Ok(()) - } - #[inline] - fn set_nodelay(&mut self, _: bool) -> io::Result<()> { - Ok(()) - } - #[inline] - fn set_linger(&mut self, _: Option) -> io::Result<()> { - Ok(()) - } - #[inline] - fn set_keepalive(&mut self, _: Option) -> io::Result<()> { - Ok(()) - } -} - -impl io::Read for WrapperStream -where - T: AsyncRead + AsyncWrite + 'static, -{ - #[inline] - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.io.read(buf) - } -} - -impl io::Write for WrapperStream -where - T: AsyncRead + AsyncWrite + 'static, -{ - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - self.io.write(buf) - } - #[inline] - fn flush(&mut self) -> io::Result<()> { - self.io.flush() - } -} - -impl AsyncRead for WrapperStream -where - T: AsyncRead + AsyncWrite + 'static, -{ - #[inline] - fn read_buf(&mut self, buf: &mut B) -> Poll { - self.io.read_buf(buf) - } -} - -impl AsyncWrite for WrapperStream -where - T: AsyncRead + AsyncWrite + 'static, -{ - #[inline] - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.io.shutdown() - } - #[inline] - fn write_buf(&mut self, buf: &mut B) -> Poll { - self.io.write_buf(buf) - } -} diff --git a/src/server/error.rs b/src/server/error.rs index 3ae9a107b..d9e1239e1 100644 --- a/src/server/error.rs +++ b/src/server/error.rs @@ -4,7 +4,6 @@ use std::io; use futures::{Async, Poll}; use http2; -use super::{helpers, HttpHandlerTask, Writer}; use error::{Error, ParseError}; use http::{StatusCode, Version}; @@ -89,31 +88,3 @@ impl From for HttpDispatchError { HttpDispatchError::Http2(err) } } - -pub(crate) struct ServerError(Version, StatusCode); - -impl ServerError { - pub fn err(ver: Version, status: StatusCode) -> Box { - Box::new(ServerError(ver, status)) - } -} - -impl HttpHandlerTask for ServerError { - fn poll_io(&mut self, io: &mut Writer) -> Poll { - { - let bytes = io.buffer(); - // Buffer should have sufficient capacity for status line - // and extra space - bytes.reserve(helpers::STATUS_LINE_BUF_SIZE + 1); - helpers::write_status_line(self.0, self.1.as_u16(), bytes); - } - // Convert Status Code to Reason. - let reason = self.1.canonical_reason().unwrap_or(""); - io.buffer().extend_from_slice(reason.as_bytes()); - // No response body. - io.buffer().extend_from_slice(b"\r\ncontent-length: 0\r\n"); - // date header - io.set_date(); - Ok(Async::Ready(true)) - } -} diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs deleted file mode 100644 index c27a4c44a..000000000 --- a/src/server/h1writer.rs +++ /dev/null @@ -1,356 +0,0 @@ -// #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] - -use std::io::{self, Write}; - -use bytes::{BufMut, BytesMut}; -use futures::{Async, Poll}; -use tokio_io::AsyncWrite; - -use super::helpers; -use super::output::{Output, ResponseInfo, ResponseLength}; -use super::settings::ServiceConfig; -use super::Request; -use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; -use body::{Binary, Body}; -use header::ContentEncoding; -use http::header::{ - HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, -}; -use http::{Method, Version}; -use httpresponse::HttpResponse; - -const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific - -bitflags! { - struct Flags: u8 { - const STARTED = 0b0000_0001; - const UPGRADE = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; - const DISCONNECTED = 0b0000_1000; - } -} - -pub(crate) struct H1Writer { - flags: Flags, - stream: T, - written: u64, - headers_size: u32, - buffer: Output, - buffer_capacity: usize, - settings: ServiceConfig, -} - -impl H1Writer { - pub fn new(stream: T, settings: ServiceConfig) -> H1Writer { - H1Writer { - flags: Flags::KEEPALIVE, - written: 0, - headers_size: 0, - buffer: Output::Buffer(settings.get_bytes()), - buffer_capacity: 0, - stream, - settings, - } - } - - pub fn get_mut(&mut self) -> &mut T { - &mut self.stream - } - - pub fn reset(&mut self) { - self.written = 0; - self.flags = Flags::KEEPALIVE; - } - - pub fn flushed(&mut self) -> bool { - self.buffer.is_empty() - } - - pub fn disconnected(&mut self) { - self.flags.insert(Flags::DISCONNECTED); - } - - pub fn upgrade(&self) -> bool { - self.flags.contains(Flags::UPGRADE) - } - - pub fn keepalive(&self) -> bool { - self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE) - } - - fn write_data(stream: &mut T, data: &[u8]) -> io::Result { - let mut written = 0; - while written < data.len() { - match stream.write(&data[written..]) { - Ok(0) => { - return Err(io::Error::new(io::ErrorKind::WriteZero, "")); - } - Ok(n) => { - written += n; - } - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - return Ok(written) - } - Err(err) => return Err(err), - } - } - Ok(written) - } -} - -impl Drop for H1Writer { - fn drop(&mut self) { - if let Some(bytes) = self.buffer.take_option() { - self.settings.release_bytes(bytes); - } - } -} - -impl Writer for H1Writer { - #[inline] - fn written(&self) -> u64 { - self.written - } - - #[inline] - fn set_date(&mut self) { - self.settings.set_date(self.buffer.as_mut(), true) - } - - #[inline] - fn buffer(&mut self) -> &mut BytesMut { - self.buffer.as_mut() - } - - fn start( - &mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding, - ) -> io::Result { - // prepare task - let mut info = ResponseInfo::new(req.inner.method == Method::HEAD); - self.buffer.for_server(&mut info, &req.inner, msg, encoding); - if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { - self.flags = Flags::STARTED | Flags::KEEPALIVE; - } else { - self.flags = Flags::STARTED; - } - - // Connection upgrade - let version = msg.version().unwrap_or_else(|| req.inner.version); - if msg.upgrade() { - self.flags.insert(Flags::UPGRADE); - msg.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("upgrade")); - } - // keep-alive - else if self.flags.contains(Flags::KEEPALIVE) { - if version < Version::HTTP_11 { - msg.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("keep-alive")); - } - } else if version >= Version::HTTP_11 { - msg.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("close")); - } - let body = msg.replace_body(Body::Empty); - - // render message - { - // output buffer - let mut buffer = self.buffer.as_mut(); - - let reason = msg.reason().as_bytes(); - if let Body::Binary(ref bytes) = body { - buffer.reserve( - 256 + msg.headers().len() * AVERAGE_HEADER_SIZE - + bytes.len() - + reason.len(), - ); - } else { - buffer.reserve( - 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len(), - ); - } - - // status line - helpers::write_status_line(version, msg.status().as_u16(), &mut buffer); - buffer.extend_from_slice(reason); - - // content length - match info.length { - ResponseLength::Chunked => { - buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") - } - ResponseLength::Zero => { - buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n") - } - ResponseLength::Length(len) => { - helpers::write_content_length(len, &mut buffer) - } - ResponseLength::Length64(len) => { - buffer.extend_from_slice(b"\r\ncontent-length: "); - write!(buffer.writer(), "{}", len)?; - buffer.extend_from_slice(b"\r\n"); - } - ResponseLength::None => buffer.extend_from_slice(b"\r\n"), - } - if let Some(ce) = info.content_encoding { - buffer.extend_from_slice(b"content-encoding: "); - buffer.extend_from_slice(ce.as_ref()); - buffer.extend_from_slice(b"\r\n"); - } - - // write headers - let mut pos = 0; - let mut has_date = false; - let mut remaining = buffer.remaining_mut(); - let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) }; - for (key, value) in msg.headers() { - match *key { - TRANSFER_ENCODING => continue, - CONTENT_ENCODING => if encoding != ContentEncoding::Identity { - continue; - }, - CONTENT_LENGTH => match info.length { - ResponseLength::None => (), - _ => continue, - }, - DATE => { - has_date = true; - } - _ => (), - } - - let v = value.as_ref(); - let k = key.as_str().as_bytes(); - let len = k.len() + v.len() + 4; - if len > remaining { - unsafe { - buffer.advance_mut(pos); - } - pos = 0; - buffer.reserve(len); - remaining = buffer.remaining_mut(); - unsafe { - buf = &mut *(buffer.bytes_mut() as *mut _); - } - } - - buf[pos..pos + k.len()].copy_from_slice(k); - pos += k.len(); - buf[pos..pos + 2].copy_from_slice(b": "); - pos += 2; - buf[pos..pos + v.len()].copy_from_slice(v); - pos += v.len(); - buf[pos..pos + 2].copy_from_slice(b"\r\n"); - pos += 2; - remaining -= len; - } - unsafe { - buffer.advance_mut(pos); - } - - // optimized date header, set_date writes \r\n - if !has_date { - self.settings.set_date(&mut buffer, true); - } else { - // msg eof - buffer.extend_from_slice(b"\r\n"); - } - self.headers_size = buffer.len() as u32; - } - - if let Body::Binary(bytes) = body { - self.written = bytes.len() as u64; - self.buffer.write(bytes.as_ref())?; - } else { - // capacity, makes sense only for streaming or actor - self.buffer_capacity = msg.write_buffer_capacity(); - - msg.replace_body(body); - } - Ok(WriterState::Done) - } - - fn write(&mut self, payload: &Binary) -> io::Result { - self.written += payload.len() as u64; - if !self.flags.contains(Flags::DISCONNECTED) { - if self.flags.contains(Flags::STARTED) { - // shortcut for upgraded connection - if self.flags.contains(Flags::UPGRADE) { - if self.buffer.is_empty() { - let pl: &[u8] = payload.as_ref(); - let n = match Self::write_data(&mut self.stream, pl) { - Err(err) => { - self.disconnected(); - return Err(err); - } - Ok(val) => val, - }; - if n < pl.len() { - self.buffer.write(&pl[n..])?; - return Ok(WriterState::Done); - } - } else { - self.buffer.write(payload.as_ref())?; - } - } else { - // TODO: add warning, write after EOF - self.buffer.write(payload.as_ref())?; - } - } else { - // could be response to EXCEPT header - self.buffer.write(payload.as_ref())?; - } - } - - if self.buffer.len() > self.buffer_capacity { - Ok(WriterState::Pause) - } else { - Ok(WriterState::Done) - } - } - - fn write_eof(&mut self) -> io::Result { - if !self.buffer.write_eof()? { - Err(io::Error::new( - io::ErrorKind::Other, - "Last payload item, but eof is not reached", - )) - } else if self.buffer.len() > MAX_WRITE_BUFFER_SIZE { - Ok(WriterState::Pause) - } else { - Ok(WriterState::Done) - } - } - - #[inline] - fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> { - if self.flags.contains(Flags::DISCONNECTED) { - return Err(io::Error::new(io::ErrorKind::Other, "disconnected")); - } - - if !self.buffer.is_empty() { - let written = { - match Self::write_data(&mut self.stream, self.buffer.as_ref().as_ref()) { - Err(err) => { - self.disconnected(); - return Err(err); - } - Ok(val) => val, - } - }; - let _ = self.buffer.split_to(written); - if shutdown && !self.buffer.is_empty() - || (self.buffer.len() > self.buffer_capacity) - { - return Ok(Async::NotReady); - } - } - if shutdown { - self.stream.poll_flush()?; - self.stream.shutdown() - } else { - Ok(self.stream.poll_flush()?) - } - } -} diff --git a/src/server/h2.rs b/src/server/h2.rs deleted file mode 100644 index 27ae47851..000000000 --- a/src/server/h2.rs +++ /dev/null @@ -1,453 +0,0 @@ -use std::collections::VecDeque; -use std::io::{Read, Write}; -use std::net::SocketAddr; -use std::rc::Rc; -use std::time::Instant; -use std::{cmp, io, mem}; - -use bytes::{Buf, Bytes}; -use futures::{Async, Future, Poll, Stream}; -use http2::server::{self, Connection, Handshake, SendResponse}; -use http2::{Reason, RecvStream}; -use modhttp::request::Parts; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_timer::Delay; - -use error::{Error, PayloadError}; -use extensions::Extensions; -use http::{StatusCode, Version}; -use payload::{Payload, PayloadStatus, PayloadWriter}; -use uri::Url; - -use super::error::{HttpDispatchError, ServerError}; -use super::h2writer::H2Writer; -use super::input::PayloadType; -use super::settings::ServiceConfig; -use super::{HttpHandler, HttpHandlerTask, IoStream, Writer}; - -bitflags! { - struct Flags: u8 { - const DISCONNECTED = 0b0000_0010; - } -} - -/// HTTP/2 Transport -pub(crate) struct Http2 -where - T: AsyncRead + AsyncWrite + 'static, - H: HttpHandler + 'static, -{ - flags: Flags, - settings: ServiceConfig, - addr: Option, - state: State>, - tasks: VecDeque>, - keepalive_timer: Option, - extensions: Option>, -} - -enum State { - Handshake(Handshake), - Connection(Connection), - Empty, -} - -impl Http2 -where - T: IoStream + 'static, - H: HttpHandler + 'static, -{ - pub fn new( - settings: ServiceConfig, io: T, buf: Bytes, keepalive_timer: Option, - ) -> Self { - let addr = io.peer_addr(); - let extensions = io.extensions(); - Http2 { - flags: Flags::empty(), - tasks: VecDeque::new(), - state: State::Handshake(server::handshake(IoWrapper { - unread: if buf.is_empty() { None } else { Some(buf) }, - inner: io, - })), - addr, - settings, - extensions, - keepalive_timer, - } - } - - pub(crate) fn shutdown(&mut self) { - self.state = State::Empty; - self.tasks.clear(); - self.keepalive_timer.take(); - } - - pub fn settings(&self) -> &ServiceConfig { - &self.settings - } - - pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { - // server - if let State::Connection(ref mut conn) = self.state { - // keep-alive timer - if let Some(ref mut timeout) = self.keepalive_timer { - match timeout.poll() { - Ok(Async::Ready(_)) => { - trace!("Keep-alive timeout, close connection"); - return Ok(Async::Ready(())); - } - Ok(Async::NotReady) => (), - Err(_) => unreachable!(), - } - } - - loop { - let mut not_ready = true; - let disconnected = self.flags.contains(Flags::DISCONNECTED); - - // check in-flight connections - for item in &mut self.tasks { - // read payload - if !disconnected { - item.poll_payload(); - } - - if !item.flags.contains(EntryFlags::EOF) { - if disconnected { - item.flags.insert(EntryFlags::EOF); - } else { - let retry = item.payload.need_read() == PayloadStatus::Read; - loop { - match item.task.poll_io(&mut item.stream) { - Ok(Async::Ready(ready)) => { - if ready { - item.flags.insert( - EntryFlags::EOF | EntryFlags::FINISHED, - ); - } else { - item.flags.insert(EntryFlags::EOF); - } - not_ready = false; - } - Ok(Async::NotReady) => { - if item.payload.need_read() - == PayloadStatus::Read - && !retry - { - continue; - } - } - Err(err) => { - error!("Unhandled error: {}", err); - item.flags.insert( - EntryFlags::EOF - | EntryFlags::ERROR - | EntryFlags::WRITE_DONE, - ); - item.stream.reset(Reason::INTERNAL_ERROR); - } - } - break; - } - } - } - - if item.flags.contains(EntryFlags::EOF) - && !item.flags.contains(EntryFlags::FINISHED) - { - match item.task.poll_completed() { - Ok(Async::NotReady) => (), - Ok(Async::Ready(_)) => { - item.flags.insert( - EntryFlags::FINISHED | EntryFlags::WRITE_DONE, - ); - } - Err(err) => { - item.flags.insert( - EntryFlags::ERROR - | EntryFlags::WRITE_DONE - | EntryFlags::FINISHED, - ); - error!("Unhandled error: {}", err); - } - } - } - - if item.flags.contains(EntryFlags::FINISHED) - && !item.flags.contains(EntryFlags::WRITE_DONE) - && !disconnected - { - match item.stream.poll_completed(false) { - Ok(Async::NotReady) => (), - Ok(Async::Ready(_)) => { - not_ready = false; - item.flags.insert(EntryFlags::WRITE_DONE); - } - Err(_) => { - item.flags.insert(EntryFlags::ERROR); - } - } - } - } - - // cleanup finished tasks - while !self.tasks.is_empty() { - if self.tasks[0].flags.contains(EntryFlags::FINISHED) - && self.tasks[0].flags.contains(EntryFlags::WRITE_DONE) - || self.tasks[0].flags.contains(EntryFlags::ERROR) - { - self.tasks.pop_front(); - } else { - break; - } - } - - // get request - if !self.flags.contains(Flags::DISCONNECTED) { - match conn.poll() { - Ok(Async::Ready(None)) => { - not_ready = false; - self.flags.insert(Flags::DISCONNECTED); - for entry in &mut self.tasks { - entry.task.disconnected() - } - } - Ok(Async::Ready(Some((req, resp)))) => { - not_ready = false; - let (parts, body) = req.into_parts(); - - // stop keepalive timer - self.keepalive_timer.take(); - - self.tasks.push_back(Entry::new( - parts, - body, - resp, - self.addr, - self.settings.clone(), - self.extensions.clone(), - )); - } - Ok(Async::NotReady) => { - // start keep-alive timer - if self.tasks.is_empty() { - if self.settings.keep_alive_enabled() { - if self.keepalive_timer.is_none() { - if let Some(ka) = self.settings.keep_alive() { - trace!("Start keep-alive timer"); - let mut timeout = - Delay::new(Instant::now() + ka); - // register timeout - let _ = timeout.poll(); - self.keepalive_timer = Some(timeout); - } - } - } else { - // keep-alive disable, drop connection - return conn.poll_close().map_err(|e| e.into()); - } - } else { - // keep-alive unset, rely on operating system - return Ok(Async::NotReady); - } - } - Err(err) => { - trace!("Connection error: {}", err); - self.flags.insert(Flags::DISCONNECTED); - for entry in &mut self.tasks { - entry.task.disconnected() - } - self.keepalive_timer.take(); - } - } - } - - if not_ready { - if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED) - { - return conn.poll_close().map_err(|e| e.into()); - } else { - return Ok(Async::NotReady); - } - } - } - } - - // handshake - self.state = if let State::Handshake(ref mut handshake) = self.state { - match handshake.poll() { - Ok(Async::Ready(conn)) => State::Connection(conn), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => { - trace!("Error handling connection: {}", err); - return Err(err.into()); - } - } - } else { - mem::replace(&mut self.state, State::Empty) - }; - - self.poll() - } -} - -bitflags! { - struct EntryFlags: u8 { - const EOF = 0b0000_0001; - const REOF = 0b0000_0010; - const ERROR = 0b0000_0100; - const FINISHED = 0b0000_1000; - const WRITE_DONE = 0b0001_0000; - } -} - -enum EntryPipe { - Task(H::Task), - Error(Box), -} - -impl EntryPipe { - fn disconnected(&mut self) { - match *self { - EntryPipe::Task(ref mut task) => task.disconnected(), - EntryPipe::Error(ref mut task) => task.disconnected(), - } - } - fn poll_io(&mut self, io: &mut Writer) -> Poll { - match *self { - EntryPipe::Task(ref mut task) => task.poll_io(io), - EntryPipe::Error(ref mut task) => task.poll_io(io), - } - } - fn poll_completed(&mut self) -> Poll<(), Error> { - match *self { - EntryPipe::Task(ref mut task) => task.poll_completed(), - EntryPipe::Error(ref mut task) => task.poll_completed(), - } - } -} - -struct Entry { - task: EntryPipe, - payload: PayloadType, - recv: RecvStream, - stream: H2Writer, - flags: EntryFlags, -} - -impl Entry { - fn new( - parts: Parts, recv: RecvStream, resp: SendResponse, - addr: Option, settings: ServiceConfig, - extensions: Option>, - ) -> Entry - where - H: HttpHandler + 'static, - { - // Payload and Content-Encoding - let (psender, payload) = Payload::new(false); - - let mut msg = settings.get_request(); - { - let inner = msg.inner_mut(); - inner.url = Url::new(parts.uri); - inner.method = parts.method; - inner.version = parts.version; - inner.headers = parts.headers; - inner.stream_extensions = extensions; - *inner.payload.borrow_mut() = Some(payload); - inner.addr = addr; - } - - // Payload sender - let psender = PayloadType::new(msg.headers(), psender); - - // start request processing - let task = match settings.handler().handle(msg) { - Ok(task) => EntryPipe::Task(task), - Err(_) => EntryPipe::Error(ServerError::err( - Version::HTTP_2, - StatusCode::NOT_FOUND, - )), - }; - - Entry { - task, - recv, - payload: psender, - stream: H2Writer::new(resp, settings), - flags: EntryFlags::empty(), - } - } - - fn poll_payload(&mut self) { - while !self.flags.contains(EntryFlags::REOF) - && self.payload.need_read() == PayloadStatus::Read - { - match self.recv.poll() { - Ok(Async::Ready(Some(chunk))) => { - let l = chunk.len(); - self.payload.feed_data(chunk); - if let Err(err) = self.recv.release_capacity().release_capacity(l) { - self.payload.set_error(PayloadError::Http2(err)); - break; - } - } - Ok(Async::Ready(None)) => { - self.flags.insert(EntryFlags::REOF); - self.payload.feed_eof(); - } - Ok(Async::NotReady) => break, - Err(err) => { - self.payload.set_error(PayloadError::Http2(err)); - break; - } - } - } - } -} - -struct IoWrapper { - unread: Option, - inner: T, -} - -impl Read for IoWrapper { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - if let Some(mut bytes) = self.unread.take() { - let size = cmp::min(buf.len(), bytes.len()); - buf[..size].copy_from_slice(&bytes[..size]); - if bytes.len() > size { - bytes.split_to(size); - self.unread = Some(bytes); - } - Ok(size) - } else { - self.inner.read(buf) - } - } -} - -impl Write for IoWrapper { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.inner.write(buf) - } - fn flush(&mut self) -> io::Result<()> { - self.inner.flush() - } -} - -impl AsyncRead for IoWrapper { - unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { - self.inner.prepare_uninitialized_buffer(buf) - } -} - -impl AsyncWrite for IoWrapper { - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.inner.shutdown() - } - fn write_buf(&mut self, buf: &mut B) -> Poll { - self.inner.write_buf(buf) - } -} diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs deleted file mode 100644 index 51d4dce6f..000000000 --- a/src/server/h2writer.rs +++ /dev/null @@ -1,259 +0,0 @@ -#![cfg_attr( - feature = "cargo-clippy", - allow(clippy::redundant_field_names) -)] - -use std::{cmp, io}; - -use bytes::{Bytes, BytesMut}; -use futures::{Async, Poll}; -use http2::server::SendResponse; -use http2::{Reason, SendStream}; -use modhttp::Response; - -use super::helpers; -use super::message::Request; -use super::output::{Output, ResponseInfo, ResponseLength}; -use super::settings::ServiceConfig; -use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; -use body::{Binary, Body}; -use header::ContentEncoding; -use http::header::{ - HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, -}; -use http::{HttpTryFrom, Method, Version}; -use httpresponse::HttpResponse; - -const CHUNK_SIZE: usize = 16_384; - -bitflags! { - struct Flags: u8 { - const STARTED = 0b0000_0001; - const DISCONNECTED = 0b0000_0010; - const EOF = 0b0000_0100; - const RESERVED = 0b0000_1000; - } -} - -pub(crate) struct H2Writer { - respond: SendResponse, - stream: Option>, - flags: Flags, - written: u64, - buffer: Output, - buffer_capacity: usize, - settings: ServiceConfig, -} - -impl H2Writer { - pub fn new(respond: SendResponse, settings: ServiceConfig) -> H2Writer { - H2Writer { - stream: None, - flags: Flags::empty(), - written: 0, - buffer: Output::Buffer(settings.get_bytes()), - buffer_capacity: 0, - respond, - settings, - } - } - - pub fn reset(&mut self, reason: Reason) { - if let Some(mut stream) = self.stream.take() { - stream.send_reset(reason) - } - } -} - -impl Drop for H2Writer { - fn drop(&mut self) { - self.settings.release_bytes(self.buffer.take()); - } -} - -impl Writer for H2Writer { - fn written(&self) -> u64 { - self.written - } - - #[inline] - fn set_date(&mut self) { - self.settings.set_date(self.buffer.as_mut(), true) - } - - #[inline] - fn buffer(&mut self) -> &mut BytesMut { - self.buffer.as_mut() - } - - fn start( - &mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding, - ) -> io::Result { - // prepare response - self.flags.insert(Flags::STARTED); - let mut info = ResponseInfo::new(req.inner.method == Method::HEAD); - self.buffer.for_server(&mut info, &req.inner, msg, encoding); - - let mut has_date = false; - let mut resp = Response::new(()); - *resp.status_mut() = msg.status(); - *resp.version_mut() = Version::HTTP_2; - for (key, value) in msg.headers().iter() { - match *key { - // http2 specific - CONNECTION | TRANSFER_ENCODING => continue, - CONTENT_ENCODING => if encoding != ContentEncoding::Identity { - continue; - }, - CONTENT_LENGTH => match info.length { - ResponseLength::None => (), - _ => continue, - }, - DATE => has_date = true, - _ => (), - } - resp.headers_mut().append(key, value.clone()); - } - - // set date header - if !has_date { - let mut bytes = BytesMut::with_capacity(29); - self.settings.set_date(&mut bytes, false); - resp.headers_mut() - .insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); - } - - // content length - match info.length { - ResponseLength::Zero => { - resp.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); - self.flags.insert(Flags::EOF); - } - ResponseLength::Length(len) => { - let mut val = BytesMut::new(); - helpers::convert_usize(len, &mut val); - let l = val.len(); - resp.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(), - ); - } - ResponseLength::Length64(len) => { - let l = format!("{}", len); - resp.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::try_from(l.as_str()).unwrap()); - } - _ => (), - } - if let Some(ce) = info.content_encoding { - resp.headers_mut() - .insert(CONTENT_ENCODING, HeaderValue::try_from(ce).unwrap()); - } - - trace!("Response: {:?}", resp); - - match self - .respond - .send_response(resp, self.flags.contains(Flags::EOF)) - { - Ok(stream) => self.stream = Some(stream), - Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "err")), - } - - let body = msg.replace_body(Body::Empty); - if let Body::Binary(bytes) = body { - if bytes.is_empty() { - Ok(WriterState::Done) - } else { - self.flags.insert(Flags::EOF); - self.buffer.write(bytes.as_ref())?; - if let Some(ref mut stream) = self.stream { - self.flags.insert(Flags::RESERVED); - stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE)); - } - Ok(WriterState::Pause) - } - } else { - msg.replace_body(body); - self.buffer_capacity = msg.write_buffer_capacity(); - Ok(WriterState::Done) - } - } - - fn write(&mut self, payload: &Binary) -> io::Result { - if !self.flags.contains(Flags::DISCONNECTED) { - if self.flags.contains(Flags::STARTED) { - // TODO: add warning, write after EOF - self.buffer.write(payload.as_ref())?; - } else { - // might be response for EXCEPT - error!("Not supported"); - } - } - - if self.buffer.len() > self.buffer_capacity { - Ok(WriterState::Pause) - } else { - Ok(WriterState::Done) - } - } - - fn write_eof(&mut self) -> io::Result { - self.flags.insert(Flags::EOF); - if !self.buffer.write_eof()? { - Err(io::Error::new( - io::ErrorKind::Other, - "Last payload item, but eof is not reached", - )) - } else if self.buffer.len() > MAX_WRITE_BUFFER_SIZE { - Ok(WriterState::Pause) - } else { - Ok(WriterState::Done) - } - } - - fn poll_completed(&mut self, _shutdown: bool) -> Poll<(), io::Error> { - if !self.flags.contains(Flags::STARTED) { - return Ok(Async::NotReady); - } - - if let Some(ref mut stream) = self.stream { - // reserve capacity - if !self.flags.contains(Flags::RESERVED) && !self.buffer.is_empty() { - self.flags.insert(Flags::RESERVED); - stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE)); - } - - loop { - match stream.poll_capacity() { - Ok(Async::NotReady) => return Ok(Async::NotReady), - Ok(Async::Ready(None)) => return Ok(Async::Ready(())), - Ok(Async::Ready(Some(cap))) => { - let len = self.buffer.len(); - let bytes = self.buffer.split_to(cmp::min(cap, len)); - let eof = - self.buffer.is_empty() && self.flags.contains(Flags::EOF); - self.written += bytes.len() as u64; - - if let Err(e) = stream.send_data(bytes.freeze(), eof) { - return Err(io::Error::new(io::ErrorKind::Other, e)); - } else if !self.buffer.is_empty() { - let cap = cmp::min(self.buffer.len(), CHUNK_SIZE); - stream.reserve_capacity(cap); - } else { - if eof { - stream.reserve_capacity(0); - continue; - } - self.flags.remove(Flags::RESERVED); - return Ok(Async::Ready(())); - } - } - Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)), - } - } - } - Ok(Async::Ready(())) - } -} diff --git a/src/server/handler.rs b/src/server/handler.rs deleted file mode 100644 index 33e50ac34..000000000 --- a/src/server/handler.rs +++ /dev/null @@ -1,208 +0,0 @@ -use futures::{Async, Future, Poll}; - -use super::message::Request; -use super::Writer; -use error::Error; - -/// Low level http request handler -#[allow(unused_variables)] -pub trait HttpHandler: 'static { - /// Request handling task - type Task: HttpHandlerTask; - - /// Handle request - fn handle(&self, req: Request) -> Result; -} - -impl HttpHandler for Box>> { - type Task = Box; - - fn handle(&self, req: Request) -> Result, Request> { - self.as_ref().handle(req) - } -} - -/// Low level http request handler -pub trait HttpHandlerTask { - /// Poll task, this method is used before or after *io* object is available - fn poll_completed(&mut self) -> Poll<(), Error> { - Ok(Async::Ready(())) - } - - /// Poll task when *io* object is available - fn poll_io(&mut self, io: &mut Writer) -> Poll; - - /// Connection is disconnected - fn disconnected(&mut self) {} -} - -impl HttpHandlerTask for Box { - fn poll_io(&mut self, io: &mut Writer) -> Poll { - self.as_mut().poll_io(io) - } -} - -pub(super) struct HttpHandlerTaskFut { - task: T, -} - -impl HttpHandlerTaskFut { - pub(crate) fn new(task: T) -> Self { - Self { task } - } -} - -impl Future for HttpHandlerTaskFut { - type Item = (); - type Error = (); - - fn poll(&mut self) -> Poll<(), ()> { - self.task.poll_completed().map_err(|_| ()) - } -} - -/// Conversion helper trait -pub trait IntoHttpHandler { - /// The associated type which is result of conversion. - type Handler: HttpHandler; - - /// Convert into `HttpHandler` object. - fn into_handler(self) -> Self::Handler; -} - -impl IntoHttpHandler for T { - type Handler = T; - - fn into_handler(self) -> Self::Handler { - self - } -} - -impl IntoHttpHandler for Vec { - type Handler = VecHttpHandler; - - fn into_handler(self) -> Self::Handler { - VecHttpHandler(self.into_iter().map(|item| item.into_handler()).collect()) - } -} - -#[doc(hidden)] -pub struct VecHttpHandler(Vec); - -impl HttpHandler for VecHttpHandler { - type Task = H::Task; - - fn handle(&self, mut req: Request) -> Result { - for h in &self.0 { - req = match h.handle(req) { - Ok(task) => return Ok(task), - Err(e) => e, - }; - } - Err(req) - } -} - -macro_rules! http_handler ({$EN:ident, $(($n:tt, $T:ident)),+} => { - impl<$($T: HttpHandler,)+> HttpHandler for ($($T,)+) { - type Task = $EN<$($T,)+>; - - fn handle(&self, mut req: Request) -> Result { - $( - req = match self.$n.handle(req) { - Ok(task) => return Ok($EN::$T(task)), - Err(e) => e, - }; - )+ - Err(req) - } - } - - #[doc(hidden)] - pub enum $EN<$($T: HttpHandler,)+> { - $($T ($T::Task),)+ - } - - impl<$($T: HttpHandler,)+> HttpHandlerTask for $EN<$($T,)+> - { - fn poll_completed(&mut self) -> Poll<(), Error> { - match self { - $($EN :: $T(ref mut task) => task.poll_completed(),)+ - } - } - - fn poll_io(&mut self, io: &mut Writer) -> Poll { - match self { - $($EN::$T(ref mut task) => task.poll_io(io),)+ - } - } - - /// Connection is disconnected - fn disconnected(&mut self) { - match self { - $($EN::$T(ref mut task) => task.disconnected(),)+ - } - } - } -}); - -http_handler!(HttpHandlerTask1, (0, A)); -http_handler!(HttpHandlerTask2, (0, A), (1, B)); -http_handler!(HttpHandlerTask3, (0, A), (1, B), (2, C)); -http_handler!(HttpHandlerTask4, (0, A), (1, B), (2, C), (3, D)); -http_handler!(HttpHandlerTask5, (0, A), (1, B), (2, C), (3, D), (4, E)); -http_handler!( - HttpHandlerTask6, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F) -); -http_handler!( - HttpHandlerTask7, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G) -); -http_handler!( - HttpHandlerTask8, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H) -); -http_handler!( - HttpHandlerTask9, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H), - (8, I) -); -http_handler!( - HttpHandlerTask10, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H), - (8, I), - (9, J) -); diff --git a/src/server/http.rs b/src/server/http.rs deleted file mode 100644 index 6a7790c13..000000000 --- a/src/server/http.rs +++ /dev/null @@ -1,584 +0,0 @@ -use std::{fmt, io, mem, net}; - -use actix::{Addr, System}; -use actix_net::server::Server; -use actix_net::service::NewService; -use actix_net::ssl; - -use net2::TcpBuilder; -use num_cpus; - -#[cfg(feature = "tls")] -use native_tls::TlsAcceptor; - -#[cfg(any(feature = "alpn", feature = "ssl"))] -use openssl::ssl::SslAcceptorBuilder; - -#[cfg(feature = "rust-tls")] -use rustls::ServerConfig; - -use super::acceptor::{AcceptorServiceFactory, DefaultAcceptor}; -use super::builder::{HttpServiceBuilder, ServiceProvider}; -use super::{IntoHttpHandler, KeepAlive}; - -struct Socket { - scheme: &'static str, - lst: net::TcpListener, - addr: net::SocketAddr, - handler: Box, -} - -/// An HTTP Server -/// -/// By default it serves HTTP2 when HTTPs is enabled, -/// in order to change it, use `ServerFlags` that can be provided -/// to acceptor service. -pub struct HttpServer -where - H: IntoHttpHandler + 'static, - F: Fn() -> H + Send + Clone, -{ - pub(super) factory: F, - pub(super) host: Option, - pub(super) keep_alive: KeepAlive, - pub(super) client_timeout: u64, - pub(super) client_shutdown: u64, - backlog: i32, - threads: usize, - exit: bool, - shutdown_timeout: u16, - no_http2: bool, - no_signals: bool, - maxconn: usize, - maxconnrate: usize, - sockets: Vec, -} - -impl HttpServer -where - H: IntoHttpHandler + 'static, - F: Fn() -> H + Send + Clone + 'static, -{ - /// Create new http server with application factory - pub fn new(factory: F) -> HttpServer { - HttpServer { - factory, - threads: num_cpus::get(), - host: None, - backlog: 2048, - keep_alive: KeepAlive::Timeout(5), - shutdown_timeout: 30, - exit: false, - no_http2: false, - no_signals: false, - maxconn: 25_600, - maxconnrate: 256, - client_timeout: 5000, - client_shutdown: 5000, - sockets: Vec::new(), - } - } - - /// Set number of workers to start. - /// - /// By default http server uses number of available logical cpu as threads - /// count. - pub fn workers(mut self, num: usize) -> Self { - self.threads = num; - self - } - - /// Set the maximum number of pending connections. - /// - /// This refers to the number of clients that can be waiting to be served. - /// Exceeding this number results in the client getting an error when - /// attempting to connect. It should only affect servers under significant - /// load. - /// - /// Generally set in the 64-2048 range. Default value is 2048. - /// - /// This method should be called before `bind()` method call. - pub fn backlog(mut self, num: i32) -> Self { - self.backlog = num; - self - } - - /// Sets the maximum per-worker number of concurrent connections. - /// - /// All socket listeners will stop accepting connections when this limit is reached - /// for each worker. - /// - /// By default max connections is set to a 25k. - pub fn maxconn(mut self, num: usize) -> Self { - self.maxconn = num; - self - } - - /// Sets the maximum per-worker concurrent connection establish process. - /// - /// All listeners will stop accepting connections when this limit is reached. It - /// can be used to limit the global SSL CPU usage. - /// - /// By default max connections is set to a 256. - pub fn maxconnrate(mut self, num: usize) -> Self { - self.maxconnrate = num; - self - } - - /// Set server keep-alive setting. - /// - /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(mut self, val: T) -> Self { - self.keep_alive = val.into(); - self - } - - /// Set server client timeout in milliseconds for first request. - /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } - - /// Set server connection shutdown timeout in milliseconds. - /// - /// Defines a timeout for shutdown connection. If a shutdown procedure does not complete - /// within this time, the request is dropped. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_shutdown(mut self, val: u64) -> Self { - self.client_shutdown = val; - self - } - - /// Set server host name. - /// - /// Host name is used by application router aa a hostname for url - /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. - /// html#method.host) documentation for more information. - pub fn server_hostname(mut self, val: String) -> Self { - self.host = Some(val); - self - } - - /// Stop actix system. - /// - /// `SystemExit` message stops currently running system. - pub fn system_exit(mut self) -> Self { - self.exit = true; - self - } - - /// Disable signal handling - pub fn disable_signals(mut self) -> Self { - self.no_signals = true; - self - } - - /// Timeout for graceful workers shutdown. - /// - /// After receiving a stop signal, workers have this much time to finish - /// serving requests. Workers still alive after the timeout are force - /// dropped. - /// - /// By default shutdown timeout sets to 30 seconds. - pub fn shutdown_timeout(mut self, sec: u16) -> Self { - self.shutdown_timeout = sec; - self - } - - /// Disable `HTTP/2` support - // #[doc(hidden)] - // #[deprecated( - // since = "0.7.4", - // note = "please use acceptor service with proper ServerFlags parama" - // )] - pub fn no_http2(mut self) -> Self { - self.no_http2 = true; - self - } - - /// Get addresses of bound sockets. - pub fn addrs(&self) -> Vec { - self.sockets.iter().map(|s| s.addr).collect() - } - - /// Get addresses of bound sockets and the scheme for it. - /// - /// This is useful when the server is bound from different sources - /// with some sockets listening on http and some listening on https - /// and the user should be presented with an enumeration of which - /// socket requires which protocol. - pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> { - self.sockets.iter().map(|s| (s.addr, s.scheme)).collect() - } - - /// Use listener for accepting incoming connection requests - /// - /// HttpServer does not change any configuration for TcpListener, - /// it needs to be configured before passing it to listen() method. - pub fn listen(mut self, lst: net::TcpListener) -> Self { - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - lst, - addr, - scheme: "http", - handler: Box::new(HttpServiceBuilder::new( - self.factory.clone(), - DefaultAcceptor, - )), - }); - - self - } - - #[doc(hidden)] - /// Use listener for accepting incoming connection requests - pub fn listen_with(mut self, lst: net::TcpListener, acceptor: A) -> Self - where - A: AcceptorServiceFactory, - ::InitError: fmt::Debug, - { - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - lst, - addr, - scheme: "https", - handler: Box::new(HttpServiceBuilder::new(self.factory.clone(), acceptor)), - }); - - self - } - - #[cfg(feature = "tls")] - /// Use listener for accepting incoming tls connection requests - /// - /// HttpServer does not change any configuration for TcpListener, - /// it needs to be configured before passing it to listen() method. - pub fn listen_tls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { - use actix_net::service::NewServiceExt; - - self.listen_with(lst, move || { - ssl::NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()) - }) - } - - #[cfg(any(feature = "alpn", feature = "ssl"))] - /// Use listener for accepting incoming tls connection requests - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_ssl( - self, lst: net::TcpListener, builder: SslAcceptorBuilder, - ) -> io::Result { - use super::{openssl_acceptor_with_flags, ServerFlags}; - use actix_net::service::NewServiceExt; - - let flags = if self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; - - let acceptor = openssl_acceptor_with_flags(builder, flags)?; - Ok(self.listen_with(lst, move || { - ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ()) - })) - } - - #[cfg(feature = "rust-tls")] - /// Use listener for accepting incoming tls connection requests - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_rustls(self, lst: net::TcpListener, config: ServerConfig) -> Self { - use super::{RustlsAcceptor, ServerFlags}; - use actix_net::service::NewServiceExt; - - // alpn support - let flags = if self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; - - self.listen_with(lst, move || { - RustlsAcceptor::with_flags(config.clone(), flags).map_err(|_| ()) - }) - } - - /// The socket address to bind - /// - /// To bind multiple addresses this method can be called multiple times. - pub fn bind(mut self, addr: S) -> io::Result { - let sockets = self.bind2(addr)?; - - for lst in sockets { - self = self.listen(lst); - } - - Ok(self) - } - - /// Start listening for incoming connections with supplied acceptor. - #[doc(hidden)] - #[cfg_attr( - feature = "cargo-clippy", - allow(clippy::needless_pass_by_value) - )] - pub fn bind_with(mut self, addr: S, acceptor: A) -> io::Result - where - S: net::ToSocketAddrs, - A: AcceptorServiceFactory, - ::InitError: fmt::Debug, - { - let sockets = self.bind2(addr)?; - - for lst in sockets { - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - lst, - addr, - scheme: "https", - handler: Box::new(HttpServiceBuilder::new( - self.factory.clone(), - acceptor.clone(), - )), - }); - } - - Ok(self) - } - - fn bind2( - &self, addr: S, - ) -> io::Result> { - let mut err = None; - let mut succ = false; - let mut sockets = Vec::new(); - for addr in addr.to_socket_addrs()? { - match create_tcp_listener(addr, self.backlog) { - Ok(lst) => { - succ = true; - sockets.push(lst); - } - Err(e) => err = Some(e), - } - } - - if !succ { - if let Some(e) = err.take() { - Err(e) - } else { - Err(io::Error::new( - io::ErrorKind::Other, - "Can not bind to address.", - )) - } - } else { - Ok(sockets) - } - } - - #[cfg(feature = "tls")] - /// The ssl socket address to bind - /// - /// To bind multiple addresses this method can be called multiple times. - pub fn bind_tls( - self, addr: S, acceptor: TlsAcceptor, - ) -> io::Result { - use actix_net::service::NewServiceExt; - use actix_net::ssl::NativeTlsAcceptor; - - self.bind_with(addr, move || { - NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()) - }) - } - - #[cfg(any(feature = "alpn", feature = "ssl"))] - /// Start listening for incoming tls connections. - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_ssl(self, addr: S, builder: SslAcceptorBuilder) -> io::Result - where - S: net::ToSocketAddrs, - { - use super::{openssl_acceptor_with_flags, ServerFlags}; - use actix_net::service::NewServiceExt; - - // alpn support - let flags = if self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; - - let acceptor = openssl_acceptor_with_flags(builder, flags)?; - self.bind_with(addr, move || { - ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ()) - }) - } - - #[cfg(feature = "rust-tls")] - /// Start listening for incoming tls connections. - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_rustls( - self, addr: S, builder: ServerConfig, - ) -> io::Result { - use super::{RustlsAcceptor, ServerFlags}; - use actix_net::service::NewServiceExt; - - // alpn support - let flags = if self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; - - self.bind_with(addr, move || { - RustlsAcceptor::with_flags(builder.clone(), flags).map_err(|_| ()) - }) - } -} - -impl H + Send + Clone> HttpServer { - /// Start listening for incoming connections. - /// - /// This method starts number of http workers in separate threads. - /// For each address this method starts separate thread which does - /// `accept()` in a loop. - /// - /// This methods panics if no socket addresses get bound. - /// - /// This method requires to run within properly configured `Actix` system. - /// - /// ```rust - /// extern crate actix_web; - /// use actix_web::{actix, server, App, HttpResponse}; - /// - /// fn main() { - /// let sys = actix::System::new("example"); // <- create Actix system - /// - /// server::new(|| App::new().resource("/", |r| r.h(|_: &_| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0") - /// .expect("Can not bind to 127.0.0.1:0") - /// .start(); - /// # actix::System::current().stop(); - /// sys.run(); // <- Run actix system, this method starts all async processes - /// } - /// ``` - pub fn start(mut self) -> Addr { - ssl::max_concurrent_ssl_connect(self.maxconnrate); - - let mut srv = Server::new() - .workers(self.threads) - .maxconn(self.maxconn) - .shutdown_timeout(self.shutdown_timeout); - - srv = if self.exit { srv.system_exit() } else { srv }; - srv = if self.no_signals { - srv.disable_signals() - } else { - srv - }; - - let sockets = mem::replace(&mut self.sockets, Vec::new()); - - for socket in sockets { - let host = self - .host - .as_ref() - .map(|h| h.to_owned()) - .unwrap_or_else(|| format!("{}", socket.addr)); - let (secure, client_shutdown) = if socket.scheme == "https" { - (true, self.client_shutdown) - } else { - (false, 0) - }; - srv = socket.handler.register( - srv, - socket.lst, - host, - socket.addr, - self.keep_alive, - secure, - self.client_timeout, - client_shutdown, - ); - } - srv.start() - } - - /// Spawn new thread and start listening for incoming connections. - /// - /// This method spawns new thread and starts new actix system. Other than - /// that it is similar to `start()` method. This method blocks. - /// - /// This methods panics if no socket addresses get bound. - /// - /// ```rust,ignore - /// # extern crate futures; - /// # extern crate actix_web; - /// # use futures::Future; - /// use actix_web::*; - /// - /// fn main() { - /// HttpServer::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0") - /// .expect("Can not bind to 127.0.0.1:0") - /// .run(); - /// } - /// ``` - pub fn run(self) { - let sys = System::new("http-server"); - self.start(); - sys.run(); - } - - /// Register current http server as actix-net's server service - pub fn register(self, mut srv: Server) -> Server { - for socket in self.sockets { - let host = self - .host - .as_ref() - .map(|h| h.to_owned()) - .unwrap_or_else(|| format!("{}", socket.addr)); - let (secure, client_shutdown) = if socket.scheme == "https" { - (true, self.client_shutdown) - } else { - (false, 0) - }; - srv = socket.handler.register( - srv, - socket.lst, - host, - socket.addr, - self.keep_alive, - secure, - self.client_timeout, - client_shutdown, - ); - } - srv - } -} - -fn create_tcp_listener( - addr: net::SocketAddr, backlog: i32, -) -> io::Result { - let builder = match addr { - net::SocketAddr::V4(_) => TcpBuilder::new_v4()?, - net::SocketAddr::V6(_) => TcpBuilder::new_v6()?, - }; - builder.reuse_address(true)?; - builder.bind(addr)?; - Ok(builder.listen(backlog)?) -} diff --git a/src/server/incoming.rs b/src/server/incoming.rs deleted file mode 100644 index b13bba2a7..000000000 --- a/src/server/incoming.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Support for `Stream`, deprecated! -use std::{io, net}; - -use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message}; -use futures::{Future, Stream}; -use tokio_io::{AsyncRead, AsyncWrite}; - -use super::channel::{HttpChannel, WrapperStream}; -use super::handler::{HttpHandler, IntoHttpHandler}; -use super::http::HttpServer; -use super::settings::{ServerSettings, ServiceConfig}; - -impl Message for WrapperStream { - type Result = (); -} - -impl HttpServer -where - H: IntoHttpHandler, - F: Fn() -> H + Send + Clone, -{ - #[doc(hidden)] - #[deprecated(since = "0.7.8")] - /// Start listening for incoming connections from a stream. - /// - /// This method uses only one thread for handling incoming connections. - pub fn start_incoming(self, stream: S, secure: bool) - where - S: Stream + 'static, - T: AsyncRead + AsyncWrite + 'static, - { - // set server settings - let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); - let apps = (self.factory)().into_handler(); - let settings = ServiceConfig::new( - apps, - self.keep_alive, - self.client_timeout, - self.client_shutdown, - ServerSettings::new(addr, "127.0.0.1:8080", secure), - ); - - // start server - HttpIncoming::create(move |ctx| { - ctx.add_message_stream(stream.map_err(|_| ()).map(WrapperStream::new)); - HttpIncoming { settings } - }); - } -} - -struct HttpIncoming { - settings: ServiceConfig, -} - -impl Actor for HttpIncoming { - type Context = Context; -} - -impl Handler> for HttpIncoming -where - T: AsyncRead + AsyncWrite, - H: HttpHandler, -{ - type Result = (); - - fn handle(&mut self, msg: WrapperStream, _: &mut Context) -> Self::Result { - Arbiter::spawn(HttpChannel::new(self.settings.clone(), msg).map_err(|_| ())); - } -} diff --git a/src/server/mod.rs b/src/server/mod.rs index ce3f2fbf6..7d64a6e20 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -117,33 +117,20 @@ use tokio_tcp::TcpStream; pub use actix_net::server::{PauseServer, ResumeServer, StopServer}; -pub(crate) mod acceptor; -pub(crate) mod builder; -mod channel; mod error; -pub(crate) mod h1; +// pub(crate) mod h1; #[doc(hidden)] pub mod h1codec; #[doc(hidden)] pub mod h1decoder; -mod h1writer; -mod h2; -mod h2writer; -mod handler; pub(crate) mod helpers; -mod http; -pub(crate) mod incoming; pub(crate) mod input; pub(crate) mod message; pub(crate) mod output; -pub(crate) mod service; +// pub(crate) mod service; pub(crate) mod settings; -mod ssl; -pub use self::handler::*; -pub use self::http::HttpServer; pub use self::message::Request; -pub use self::ssl::*; pub use self::error::{AcceptorError, HttpDispatchError}; pub use self::settings::ServerSettings; @@ -151,14 +138,11 @@ pub use self::settings::ServerSettings; #[doc(hidden)] pub mod h1disp; -#[doc(hidden)] -pub use self::acceptor::AcceptorTimeout; - #[doc(hidden)] pub use self::settings::{ServiceConfig, ServiceConfigBuilder}; -#[doc(hidden)] -pub use self::service::{H1Service, HttpService, StreamConfiguration}; +//#[doc(hidden)] +//pub use self::service::{H1Service, HttpService, StreamConfiguration}; #[doc(hidden)] pub use self::helpers::write_content_length; @@ -174,53 +158,11 @@ pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; const LW_BUFFER_SIZE: usize = 4096; const HW_BUFFER_SIZE: usize = 32_768; -/// Create new http server with application factory. -/// -/// This is shortcut for `server::HttpServer::new()` method. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{actix, server, App, HttpResponse}; -/// -/// fn main() { -/// let sys = actix::System::new("example"); // <- create Actix system -/// -/// server::new( -/// || App::new() -/// .resource("/", |r| r.f(|_| HttpResponse::Ok()))) -/// .bind("127.0.0.1:59090").unwrap() -/// .start(); -/// -/// # actix::System::current().stop(); -/// sys.run(); -/// } -/// ``` -pub fn new(factory: F) -> HttpServer -where - F: Fn() -> H + Send + Clone + 'static, - H: IntoHttpHandler + 'static, -{ - HttpServer::new(factory) -} - -#[doc(hidden)] -bitflags! { - ///Flags that can be used to configure HTTP Server. - pub struct ServerFlags: u8 { - ///Use HTTP1 protocol - const HTTP1 = 0b0000_0001; - ///Use HTTP2 protocol - const HTTP2 = 0b0000_0010; - } -} - #[derive(Debug, PartialEq, Clone, Copy)] /// Server keep-alive setting pub enum KeepAlive { /// Keep alive in seconds Timeout(usize), - /// Use `SO_KEEPALIVE` socket option, value in seconds - Tcp(usize), /// Relay on OS to shutdown tcp connection Os, /// Disabled @@ -243,41 +185,9 @@ impl From> for KeepAlive { } } -#[doc(hidden)] -#[derive(Debug)] -pub enum WriterState { - Done, - Pause, -} - -#[doc(hidden)] -/// Stream writer -pub trait Writer { - /// number of bytes written to the stream - fn written(&self) -> u64; - - #[doc(hidden)] - fn set_date(&mut self); - - #[doc(hidden)] - fn buffer(&mut self) -> &mut BytesMut; - - fn start( - &mut self, req: &Request, resp: &mut HttpResponse, encoding: ContentEncoding, - ) -> io::Result; - - fn write(&mut self, payload: &Binary) -> io::Result; - - fn write_eof(&mut self) -> io::Result; - - fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error>; -} - #[doc(hidden)] /// Low-level io stream operations pub trait IoStream: AsyncRead + AsyncWrite + 'static { - fn shutdown(&mut self, how: Shutdown) -> io::Result<()>; - /// Returns the socket address of the remote peer of this TCP connection. fn peer_addr(&self) -> Option { None @@ -289,54 +199,10 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static { fn set_linger(&mut self, dur: Option) -> io::Result<()>; fn set_keepalive(&mut self, dur: Option) -> io::Result<()>; - - fn read_available(&mut self, buf: &mut BytesMut) -> Poll<(bool, bool), io::Error> { - let mut read_some = false; - loop { - if buf.remaining_mut() < LW_BUFFER_SIZE { - buf.reserve(HW_BUFFER_SIZE); - } - - let read = unsafe { self.read(buf.bytes_mut()) }; - match read { - Ok(n) => { - if n == 0 { - return Ok(Async::Ready((read_some, true))); - } else { - read_some = true; - unsafe { - buf.advance_mut(n); - } - } - } - Err(e) => { - return if e.kind() == io::ErrorKind::WouldBlock { - if read_some { - Ok(Async::Ready((read_some, false))) - } else { - Ok(Async::NotReady) - } - } else { - Err(e) - }; - } - } - } - } - - /// Extra io stream extensions - fn extensions(&self) -> Option> { - None - } } #[cfg(all(unix, feature = "uds"))] impl IoStream for ::tokio_uds::UnixStream { - #[inline] - fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { - ::tokio_uds::UnixStream::shutdown(self, how) - } - #[inline] fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> { Ok(()) @@ -354,11 +220,6 @@ impl IoStream for ::tokio_uds::UnixStream { } impl IoStream for TcpStream { - #[inline] - fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { - TcpStream::shutdown(self, how) - } - #[inline] fn peer_addr(&self) -> Option { TcpStream::peer_addr(self).ok() diff --git a/src/server/output.rs b/src/server/output.rs index 1da7e9025..143ba4029 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -254,7 +254,7 @@ impl Output { } return; } - Body::Streaming(_) | Body::Actor(_) => { + Body::Streaming(_) => { if resp.upgrade() { if version == Version::HTTP_2 { error!("Connection upgrade is forbidden for HTTP/2"); diff --git a/src/server/settings.rs b/src/server/settings.rs index 9df1e457f..f21283590 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -15,7 +15,6 @@ use time; use tokio_current_thread::spawn; use tokio_timer::{sleep, Delay}; -use super::channel::Node; use super::message::{Request, RequestPool}; use super::KeepAlive; use body::Body; @@ -128,35 +127,33 @@ impl ServerSettings { const DATE_VALUE_LENGTH: usize = 29; /// Http service configuration -pub struct ServiceConfig(Rc>); +pub struct ServiceConfig(Rc); -struct Inner { - handler: H, +struct Inner { keep_alive: Option, client_timeout: u64, client_shutdown: u64, ka_enabled: bool, bytes: Rc, messages: &'static RequestPool, - node: RefCell>, date: UnsafeCell<(bool, Date)>, } -impl Clone for ServiceConfig { +impl Clone for ServiceConfig { fn clone(&self) -> Self { ServiceConfig(self.0.clone()) } } -impl ServiceConfig { +impl ServiceConfig { /// Create instance of `ServiceConfig` pub(crate) fn new( - handler: H, keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64, + keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64, settings: ServerSettings, - ) -> ServiceConfig { + ) -> ServiceConfig { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), - KeepAlive::Os | KeepAlive::Tcp(_) => (0, true), + KeepAlive::Os => (0, true), KeepAlive::Disabled => (0, false), }; let keep_alive = if ka_enabled && keep_alive > 0 { @@ -166,29 +163,19 @@ impl ServiceConfig { }; ServiceConfig(Rc::new(Inner { - handler, keep_alive, ka_enabled, client_timeout, client_shutdown, bytes: Rc::new(SharedBytesPool::new()), messages: RequestPool::pool(settings), - node: RefCell::new(Node::head()), date: UnsafeCell::new((false, Date::new())), })) } /// Create worker settings builder. - pub fn build(handler: H) -> ServiceConfigBuilder { - ServiceConfigBuilder::new(handler) - } - - pub(crate) fn head(&self) -> RefMut> { - self.0.node.borrow_mut() - } - - pub(crate) fn handler(&self) -> &H { - &self.0.handler + pub fn build() -> ServiceConfigBuilder { + ServiceConfigBuilder::new() } #[inline] @@ -226,7 +213,7 @@ impl ServiceConfig { } } -impl ServiceConfig { +impl ServiceConfig { #[inline] /// Client timeout for first request. pub fn client_timer(&self) -> Option { @@ -329,8 +316,7 @@ impl ServiceConfig { /// /// This type can be used to construct an instance of `ServiceConfig` through a /// builder-like pattern. -pub struct ServiceConfigBuilder { - handler: H, +pub struct ServiceConfigBuilder { keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64, @@ -339,11 +325,10 @@ pub struct ServiceConfigBuilder { secure: bool, } -impl ServiceConfigBuilder { +impl ServiceConfigBuilder { /// Create instance of `ServiceConfigBuilder` - pub fn new(handler: H) -> ServiceConfigBuilder { + pub fn new() -> ServiceConfigBuilder { ServiceConfigBuilder { - handler, keep_alive: KeepAlive::Timeout(5), client_timeout: 5000, client_shutdown: 5000, @@ -426,12 +411,11 @@ impl ServiceConfigBuilder { } /// Finish service configuration and create `ServiceConfig` object. - pub fn finish(self) -> ServiceConfig { + pub fn finish(self) -> ServiceConfig { let settings = ServerSettings::new(self.addr, &self.host, self.secure); let client_shutdown = if self.secure { self.client_shutdown } else { 0 }; ServiceConfig::new( - self.handler, self.keep_alive, self.client_timeout, client_shutdown, diff --git a/src/server/ssl/mod.rs b/src/server/ssl/mod.rs deleted file mode 100644 index c09573fe3..000000000 --- a/src/server/ssl/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -#[cfg(any(feature = "alpn", feature = "ssl"))] -mod openssl; -#[cfg(any(feature = "alpn", feature = "ssl"))] -pub use self::openssl::{openssl_acceptor_with_flags, OpensslAcceptor}; - -#[cfg(feature = "tls")] -mod nativetls; - -#[cfg(feature = "rust-tls")] -mod rustls; -#[cfg(feature = "rust-tls")] -pub use self::rustls::RustlsAcceptor; diff --git a/src/server/ssl/nativetls.rs b/src/server/ssl/nativetls.rs deleted file mode 100644 index a9797ffb3..000000000 --- a/src/server/ssl/nativetls.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::net::{Shutdown, SocketAddr}; -use std::{io, time}; - -use actix_net::ssl::TlsStream; - -use server::IoStream; - -impl IoStream for TlsStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = self.get_mut().shutdown(); - Ok(()) - } - - #[inline] - fn peer_addr(&self) -> Option { - self.get_ref().get_ref().peer_addr() - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - self.get_mut().get_mut().set_nodelay(nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_linger(dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_keepalive(dur) - } -} diff --git a/src/server/ssl/openssl.rs b/src/server/ssl/openssl.rs deleted file mode 100644 index 9d370f8be..000000000 --- a/src/server/ssl/openssl.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::net::{Shutdown, SocketAddr}; -use std::{io, time}; - -use actix_net::ssl; -use openssl::ssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_openssl::SslStream; - -use server::{IoStream, ServerFlags}; - -/// Support `SSL` connections via openssl package -/// -/// `ssl` feature enables `OpensslAcceptor` type -pub struct OpensslAcceptor { - _t: ssl::OpensslAcceptor, -} - -impl OpensslAcceptor { - /// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support. - pub fn new(builder: SslAcceptorBuilder) -> io::Result> { - OpensslAcceptor::with_flags(builder, ServerFlags::HTTP1 | ServerFlags::HTTP2) - } - - /// Create `OpensslAcceptor` with custom server flags. - pub fn with_flags( - builder: SslAcceptorBuilder, flags: ServerFlags, - ) -> io::Result> { - let acceptor = openssl_acceptor_with_flags(builder, flags)?; - - Ok(ssl::OpensslAcceptor::new(acceptor)) - } -} - -/// Configure `SslAcceptorBuilder` with custom server flags. -pub fn openssl_acceptor_with_flags( - mut builder: SslAcceptorBuilder, flags: ServerFlags, -) -> io::Result { - let mut protos = Vec::new(); - if flags.contains(ServerFlags::HTTP1) { - protos.extend(b"\x08http/1.1"); - } - if flags.contains(ServerFlags::HTTP2) { - protos.extend(b"\x02h2"); - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(AlpnError::NOACK) - } - }); - } - - if !protos.is_empty() { - builder.set_alpn_protos(&protos)?; - } - - Ok(builder.build()) -} - -impl IoStream for SslStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = self.get_mut().shutdown(); - Ok(()) - } - - #[inline] - fn peer_addr(&self) -> Option { - self.get_ref().get_ref().peer_addr() - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - self.get_mut().get_mut().set_nodelay(nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_linger(dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_keepalive(dur) - } -} diff --git a/src/server/ssl/rustls.rs b/src/server/ssl/rustls.rs deleted file mode 100644 index a53a53a98..000000000 --- a/src/server/ssl/rustls.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::net::{Shutdown, SocketAddr}; -use std::{io, time}; - -use actix_net::ssl; //::RustlsAcceptor; -use rustls::{ClientSession, ServerConfig, ServerSession}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_rustls::TlsStream; - -use server::{IoStream, ServerFlags}; - -/// Support `SSL` connections via rustls package -/// -/// `rust-tls` feature enables `RustlsAcceptor` type -pub struct RustlsAcceptor { - _t: ssl::RustlsAcceptor, -} - -impl RustlsAcceptor { - /// Create `RustlsAcceptor` with custom server flags. - pub fn with_flags( - mut config: ServerConfig, flags: ServerFlags, - ) -> ssl::RustlsAcceptor { - let mut protos = Vec::new(); - if flags.contains(ServerFlags::HTTP2) { - protos.push("h2".to_string()); - } - if flags.contains(ServerFlags::HTTP1) { - protos.push("http/1.1".to_string()); - } - if !protos.is_empty() { - config.set_protocols(&protos); - } - - ssl::RustlsAcceptor::new(config) - } -} - -impl IoStream for TlsStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = ::shutdown(self); - Ok(()) - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - self.get_mut().0.set_nodelay(nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_linger(dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_keepalive(dur) - } -} - -impl IoStream for TlsStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = ::shutdown(self); - Ok(()) - } - - #[inline] - fn peer_addr(&self) -> Option { - self.get_ref().0.peer_addr() - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - self.get_mut().0.set_nodelay(nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_linger(dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_keepalive(dur) - } -} diff --git a/src/with.rs b/src/with.rs deleted file mode 100644 index c6d54dee8..000000000 --- a/src/with.rs +++ /dev/null @@ -1,383 +0,0 @@ -use futures::{Async, Future, Poll}; -use std::marker::PhantomData; -use std::rc::Rc; - -use error::Error; -use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; - -trait FnWith: 'static { - fn call_with(self: &Self, T) -> R; -} - -impl R + 'static> FnWith for F { - fn call_with(self: &Self, arg: T) -> R { - (*self)(arg) - } -} - -#[doc(hidden)] -pub trait WithFactory: 'static -where - T: FromRequest, - R: Responder, -{ - fn create(self) -> With; - - fn create_with_config(self, T::Config) -> With; -} - -#[doc(hidden)] -pub trait WithAsyncFactory: 'static -where - T: FromRequest, - R: Future, - I: Responder, - E: Into, -{ - fn create(self) -> WithAsync; - - fn create_with_config(self, T::Config) -> WithAsync; -} - -#[doc(hidden)] -pub struct With -where - T: FromRequest, - S: 'static, -{ - hnd: Rc>, - cfg: Rc, - _s: PhantomData, -} - -impl With -where - T: FromRequest, - S: 'static, -{ - pub fn new R + 'static>(f: F, cfg: T::Config) -> Self { - With { - cfg: Rc::new(cfg), - hnd: Rc::new(f), - _s: PhantomData, - } - } -} - -impl Handler for With -where - R: Responder + 'static, - T: FromRequest + 'static, - S: 'static, -{ - type Result = AsyncResult; - - fn handle(&self, req: &HttpRequest) -> Self::Result { - let mut fut = WithHandlerFut { - req: req.clone(), - started: false, - hnd: Rc::clone(&self.hnd), - cfg: self.cfg.clone(), - fut1: None, - fut2: None, - }; - - match fut.poll() { - Ok(Async::Ready(resp)) => AsyncResult::ok(resp), - Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), - Err(e) => AsyncResult::err(e), - } - } -} - -struct WithHandlerFut -where - R: Responder, - T: FromRequest + 'static, - S: 'static, -{ - started: bool, - hnd: Rc>, - cfg: Rc, - req: HttpRequest, - fut1: Option>>, - fut2: Option>>, -} - -impl Future for WithHandlerFut -where - R: Responder + 'static, - T: FromRequest + 'static, - S: 'static, -{ - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut2 { - return fut.poll(); - } - - let item = if !self.started { - self.started = true; - let reply = T::from_request(&self.req, self.cfg.as_ref()).into(); - match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.fut1 = Some(fut); - return self.poll(); - } - } - } else { - match self.fut1.as_mut().unwrap().poll()? { - Async::Ready(item) => item, - Async::NotReady => return Ok(Async::NotReady), - } - }; - - let item = match self.hnd.as_ref().call_with(item).respond_to(&self.req) { - Ok(item) => item.into(), - Err(e) => return Err(e.into()), - }; - - match item.into() { - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => { - self.fut2 = Some(fut); - self.poll() - } - } - } -} - -#[doc(hidden)] -pub struct WithAsync -where - R: Future, - I: Responder, - E: Into, - T: FromRequest, - S: 'static, -{ - hnd: Rc>, - cfg: Rc, - _s: PhantomData, -} - -impl WithAsync -where - R: Future, - I: Responder, - E: Into, - T: FromRequest, - S: 'static, -{ - pub fn new R + 'static>(f: F, cfg: T::Config) -> Self { - WithAsync { - cfg: Rc::new(cfg), - hnd: Rc::new(f), - _s: PhantomData, - } - } -} - -impl Handler for WithAsync -where - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - S: 'static, -{ - type Result = AsyncResult; - - fn handle(&self, req: &HttpRequest) -> Self::Result { - let mut fut = WithAsyncHandlerFut { - req: req.clone(), - started: false, - hnd: Rc::clone(&self.hnd), - cfg: Rc::clone(&self.cfg), - fut1: None, - fut2: None, - fut3: None, - }; - - match fut.poll() { - Ok(Async::Ready(resp)) => AsyncResult::ok(resp), - Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), - Err(e) => AsyncResult::err(e), - } - } -} - -struct WithAsyncHandlerFut -where - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - S: 'static, -{ - started: bool, - hnd: Rc>, - cfg: Rc, - req: HttpRequest, - fut1: Option>>, - fut2: Option, - fut3: Option>>, -} - -impl Future for WithAsyncHandlerFut -where - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - S: 'static, -{ - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut3 { - return fut.poll(); - } - - if self.fut2.is_some() { - return match self.fut2.as_mut().unwrap().poll() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(r)) => match r.respond_to(&self.req) { - Ok(r) => match r.into().into() { - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => { - self.fut3 = Some(fut); - self.poll() - } - }, - Err(e) => Err(e.into()), - }, - Err(e) => Err(e.into()), - }; - } - - let item = if !self.started { - self.started = true; - let reply = T::from_request(&self.req, self.cfg.as_ref()).into(); - match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.fut1 = Some(fut); - return self.poll(); - } - } - } else { - match self.fut1.as_mut().unwrap().poll()? { - Async::Ready(item) => item, - Async::NotReady => return Ok(Async::NotReady), - } - }; - - self.fut2 = Some(self.hnd.as_ref().call_with(item)); - self.poll() - } -} - -macro_rules! with_factory_tuple ({$(($n:tt, $T:ident)),+} => { - impl<$($T,)+ State, Func, Res> WithFactory<($($T,)+), State, Res> for Func - where Func: Fn($($T,)+) -> Res + 'static, - $($T: FromRequest + 'static,)+ - Res: Responder + 'static, - State: 'static, - { - fn create(self) -> With<($($T,)+), State, Res> { - With::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+)) - } - - fn create_with_config(self, cfg: ($($T::Config,)+)) -> With<($($T,)+), State, Res> { - With::new(move |($($n,)+)| (self)($($n,)+), cfg) - } - } -}); - -macro_rules! with_async_factory_tuple ({$(($n:tt, $T:ident)),+} => { - impl<$($T,)+ State, Func, Res, Item, Err> WithAsyncFactory<($($T,)+), State, Res, Item, Err> for Func - where Func: Fn($($T,)+) -> Res + 'static, - $($T: FromRequest + 'static,)+ - Res: Future, - Item: Responder + 'static, - Err: Into, - State: 'static, - { - fn create(self) -> WithAsync<($($T,)+), State, Res, Item, Err> { - WithAsync::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+)) - } - - fn create_with_config(self, cfg: ($($T::Config,)+)) -> WithAsync<($($T,)+), State, Res, Item, Err> { - WithAsync::new(move |($($n,)+)| (self)($($n,)+), cfg) - } - } -}); - -with_factory_tuple!((a, A)); -with_factory_tuple!((a, A), (b, B)); -with_factory_tuple!((a, A), (b, B), (c, C)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G)); -with_factory_tuple!( - (a, A), - (b, B), - (c, C), - (d, D), - (e, E), - (f, F), - (g, G), - (h, H) -); -with_factory_tuple!( - (a, A), - (b, B), - (c, C), - (d, D), - (e, E), - (f, F), - (g, G), - (h, H), - (i, I) -); - -with_async_factory_tuple!((a, A)); -with_async_factory_tuple!((a, A), (b, B)); -with_async_factory_tuple!((a, A), (b, B), (c, C)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G)); -with_async_factory_tuple!( - (a, A), - (b, B), - (c, C), - (d, D), - (e, E), - (f, F), - (g, G), - (h, H) -); -with_async_factory_tuple!( - (a, A), - (b, B), - (c, C), - (d, D), - (e, E), - (f, F), - (g, G), - (h, H), - (i, I) -); diff --git a/tests/test_client.rs b/tests/test_client.rs deleted file mode 100644 index 8c5d5819d..000000000 --- a/tests/test_client.rs +++ /dev/null @@ -1,508 +0,0 @@ -#![allow(deprecated)] -extern crate actix; -extern crate actix_web; -extern crate bytes; -extern crate flate2; -extern crate futures; -extern crate rand; -#[cfg(all(unix, feature = "uds"))] -extern crate tokio_uds; - -use std::io::{Read, Write}; -use std::{net, thread}; - -use bytes::Bytes; -use flate2::read::GzDecoder; -use futures::stream::once; -use futures::Future; -use rand::Rng; - -use actix_web::*; - -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; - -#[test] -fn test_simple() { - let mut srv = - test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().header("x-test", "111").finish().unwrap(); - let repr = format!("{:?}", request); - assert!(repr.contains("ClientRequest")); - assert!(repr.contains("x-test")); - - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - - let request = srv.post().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_connection_close() { - let mut srv = - test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().header("Connection", "close").finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_with_query_parameter() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| match req.query().get("qp") { - Some(_) => HttpResponse::Ok().finish(), - None => HttpResponse::BadRequest().finish(), - }) - }); - - let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap(); - - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_no_decompress() { - let mut srv = - test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - - // POST - let request = srv.post().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - - let bytes = srv.execute(response.body()).unwrap(); - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_client_gzip_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Gzip) - .body(STR) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_client_gzip_encoding_large() { - let data = STR.repeat(10); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_client_gzip_encoding_large_random() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(100_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(unix, feature = "uds"))] -#[test] -fn test_compatible_with_unix_socket_stream() { - let (stream, _) = tokio_uds::UnixStream::pair().unwrap(); - let _ = client::Connection::from_stream(stream); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_client_brotli_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .client(http::Method::POST, "/") - .content_encoding(http::ContentEncoding::Br) - .body(STR) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_client_brotli_encoding_large_random() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(70_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(move |bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .client(http::Method::POST, "/") - .content_encoding(http::ContentEncoding::Br) - .body(data.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_client_deflate_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Deflate) - .body(STR) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_client_deflate_encoding_large_random() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(70_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Deflate) - .body(data.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_client_streaming_explicit() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .map_err(Error::from) - .and_then(|body| { - Ok(HttpResponse::Ok() - .chunked() - .content_encoding(http::ContentEncoding::Identity) - .body(body)) - }).responder() - }) - }); - - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - - let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_streaming_implicit() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_client_cookie_handling() { - use actix_web::http::Cookie; - fn err() -> Error { - use std::io::{Error as IoError, ErrorKind}; - // stub some generic error - Error::from(IoError::from(ErrorKind::NotFound)) - } - let cookie1 = Cookie::build("cookie1", "value1").finish(); - let cookie2 = Cookie::build("cookie2", "value2") - .domain("www.example.org") - .path("/") - .secure(true) - .http_only(true) - .finish(); - // Q: are all these clones really necessary? A: Yes, possibly - let cookie1b = cookie1.clone(); - let cookie2b = cookie2.clone(); - let mut srv = test::TestServer::new(move |app| { - let cookie1 = cookie1b.clone(); - let cookie2 = cookie2b.clone(); - app.handler(move |req: &HttpRequest| { - // Check cookies were sent correctly - req.cookie("cookie1") - .ok_or_else(err) - .and_then(|c1| { - if c1.value() == "value1" { - Ok(()) - } else { - Err(err()) - } - }).and_then(|()| req.cookie("cookie2").ok_or_else(err)) - .and_then(|c2| { - if c2.value() == "value2" { - Ok(()) - } else { - Err(err()) - } - }) - // Send some cookies back - .map(|_| { - HttpResponse::Ok() - .cookie(cookie1.clone()) - .cookie(cookie2.clone()) - .finish() - }) - }) - }); - - let request = srv - .get() - .cookie(cookie1.clone()) - .cookie(cookie2.clone()) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - let c1 = response.cookie("cookie1").expect("Missing cookie1"); - assert_eq!(c1, cookie1); - let c2 = response.cookie("cookie2").expect("Missing cookie2"); - assert_eq!(c2, cookie2); -} - -#[test] -fn test_default_headers() { - let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().finish().unwrap(); - let repr = format!("{:?}", request); - assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\"")); - assert!(repr.contains(concat!( - "\"user-agent\": \"actix-web/", - env!("CARGO_PKG_VERSION"), - "\"" - ))); - - let request_override = srv - .get() - .header("User-Agent", "test") - .header("Accept-Encoding", "over_test") - .finish() - .unwrap(); - let repr_override = format!("{:?}", request_override); - assert!(repr_override.contains("\"user-agent\": \"test\"")); - assert!(repr_override.contains("\"accept-encoding\": \"over_test\"")); - assert!(!repr_override.contains("\"accept-encoding\": \"gzip, deflate\"")); - assert!(!repr_override.contains(concat!( - "\"user-agent\": \"Actix-web/", - env!("CARGO_PKG_VERSION"), - "\"" - ))); -} - -#[test] -fn client_read_until_eof() { - let addr = test::TestServer::unused_addr(); - - thread::spawn(move || { - let lst = net::TcpListener::bind(addr).unwrap(); - - for stream in lst.incoming() { - let mut stream = stream.unwrap(); - let mut b = [0; 1000]; - let _ = stream.read(&mut b).unwrap(); - let _ = stream - .write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!"); - } - }); - - let mut sys = actix::System::new("test"); - - // client request - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = sys.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"welcome!")); -} diff --git a/tests/test_custom_pipeline.rs b/tests/test_custom_pipeline.rs deleted file mode 100644 index 6b5df00e3..000000000 --- a/tests/test_custom_pipeline.rs +++ /dev/null @@ -1,81 +0,0 @@ -extern crate actix; -extern crate actix_net; -extern crate actix_web; - -use std::{thread, time}; - -use actix::System; -use actix_net::server::Server; -use actix_net::service::NewServiceExt; -use actix_web::server::{HttpService, KeepAlive, ServiceConfig, StreamConfiguration}; -use actix_web::{client, http, test, App, HttpRequest}; - -#[test] -fn test_custom_pipeline() { - let addr = test::TestServer::unused_addr(); - - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - let app = App::new() - .route("/", http::Method::GET, |_: HttpRequest| "OK") - .finish(); - let settings = ServiceConfig::build(app) - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_shutdown(1000) - .server_hostname("localhost") - .server_address(addr) - .finish(); - - StreamConfiguration::new() - .nodelay(true) - .tcp_keepalive(Some(time::Duration::from_secs(10))) - .and_then(HttpService::new(settings)) - }).unwrap() - .run(); - }); - - let mut sys = System::new("test"); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } -} - -#[test] -fn test_h1() { - use actix_web::server::H1Service; - - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - let app = App::new() - .route("/", http::Method::GET, |_: HttpRequest| "OK") - .finish(); - let settings = ServiceConfig::build(app) - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_shutdown(1000) - .server_hostname("localhost") - .server_address(addr) - .finish(); - - H1Service::new(settings) - }).unwrap() - .run(); - }); - - let mut sys = System::new("test"); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } -} diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs index 7e8e9a42c..77b6d202f 100644 --- a/tests/test_h1v2.rs +++ b/tests/test_h1v2.rs @@ -1,4 +1,5 @@ extern crate actix; +extern crate actix_http; extern crate actix_net; extern crate actix_web; extern crate futures; @@ -8,12 +9,12 @@ use std::thread; use actix::System; use actix_net::server::Server; use actix_net::service::{IntoNewService, IntoService}; +use actix_web::{client, test}; use futures::future; -use actix_web::server::h1disp::Http1Dispatcher; -use actix_web::server::KeepAlive; -use actix_web::server::ServiceConfig; -use actix_web::{client, test, App, Error, HttpRequest, HttpResponse}; +use actix_http::server::h1disp::Http1Dispatcher; +use actix_http::server::{KeepAlive, ServiceConfig}; +use actix_http::{Error, HttpResponse}; #[test] fn test_h1_v2() { @@ -21,10 +22,7 @@ fn test_h1_v2() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - let app = App::new() - .route("/", http::Method::GET, |_: HttpRequest| "OK") - .finish(); - let settings = ServiceConfig::build(app) + let settings = ServiceConfig::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) .client_shutdown(1000) diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs deleted file mode 100644 index 3ea709c92..000000000 --- a/tests/test_handlers.rs +++ /dev/null @@ -1,677 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate bytes; -extern crate futures; -extern crate h2; -extern crate http; -extern crate tokio_timer; -#[macro_use] -extern crate serde_derive; -extern crate serde_json; - -use std::io; -use std::time::{Duration, Instant}; - -use actix_web::*; -use bytes::Bytes; -use futures::Future; -use http::StatusCode; -use serde_json::Value; -use tokio_timer::Delay; - -#[derive(Deserialize)] -struct PParam { - username: String, -} - -#[test] -fn test_path_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.with(|p: Path| format!("Welcome {}!", p.username)) - }); - }); - - // client request - let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); -} - -#[test] -fn test_async_handler() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|p: Path| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| Ok(format!("Welcome {}!", p.username))) - .responder() - }) - }); - }); - - // client request - let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); -} - -#[test] -fn test_query_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/index.html", |r| { - r.with(|p: Query| format!("Welcome {}!", p.username)) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/index.html?username=test")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); - - // client request - let request = srv.get().uri(srv.url("/index.html")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[derive(Deserialize, Debug)] -pub enum ResponseType { - Token, - Code, -} - -#[derive(Debug, Deserialize)] -pub struct AuthRequest { - id: u64, - response_type: ResponseType, -} - -#[test] -fn test_query_enum_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/index.html", |r| { - r.with(|p: Query| format!("{:?}", p.into_inner())) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/index.html?id=64&response_type=Code")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!( - bytes, - Bytes::from_static(b"AuthRequest { id: 64, response_type: Code }") - ); - - let request = srv - .get() - .uri(srv.url("/index.html?id=64&response_type=Co")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - - let request = srv - .get() - .uri(srv.url("/index.html?response_type=Code")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_async_extractor_async() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|data: Json| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| Ok(format!("{}", data.0))) - .responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"{\"test\":1}")); -} - -#[derive(Deserialize, Serialize)] -struct FormData { - username: String, -} - -#[test] -fn test_form_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route() - .with(|form: Form| format!("{}", form.username)) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .form(FormData { - username: "test".to_string(), - }).unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"test")); -} - -#[test] -fn test_form_extractor2() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with_config( - |form: Form| format!("{}", form.username), - |cfg| { - cfg.0.error_handler(|err, _| { - error::InternalError::from_response( - err, - HttpResponse::Conflict().finish(), - ).into() - }); - }, - ); - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .header("content-type", "application/x-www-form-urlencoded") - .body("918237129hdk:D:D:D:D:D:DjASHDKJhaswkjeq") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_client_error()); -} - -#[test] -fn test_path_and_query_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|(p, q): (Path, Query)| { - format!("Welcome {} - {}!", p.username, q.username) - }) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html?username=test2")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_path_and_query_extractor2() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route() - .with(|(_r, p, q): (HttpRequest, Path, Query)| { - format!("Welcome {} - {}!", p.username, q.username) - }) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html?username=test2")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_path_and_query_extractor2_async() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with( - |(p, _q, data): (Path, Query, Json)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }, - ) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); -} - -#[test] -fn test_path_and_query_extractor3_async() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|(p, data): (Path, Json)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_path_and_query_extractor4_async() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|(data, p): (Json, Path)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_path_and_query_extractor2_async2() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with( - |(p, data, _q): (Path, Json, Query)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }, - ) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_path_and_query_extractor2_async3() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route() - .with(|data: Json, p: Path, _: Query| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_path_and_query_extractor2_async4() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route() - .with(|data: (Json, Path, Query)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0)) - }).responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_scope_and_path_extractor() { - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/sc", |scope| { - scope.resource("/{num}/index.html", |r| { - r.route() - .with(|p: Path<(usize,)>| format!("Welcome {}!", p.0)) - }) - }) - }); - - // client request - let request = srv - .get() - .uri(srv.url("/sc/10/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome 10!")); - - // client request - let request = srv - .get() - .uri(srv.url("/sc/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); -} - -#[test] -fn test_nested_scope_and_path_extractor() { - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/sc", |scope| { - scope.nested("/{num}", |scope| { - scope.resource("/{num}/index.html", |r| { - r.route().with(|p: Path<(usize, usize)>| { - format!("Welcome {} {}!", p.0, p.1) - }) - }) - }) - }) - }); - - // client request - let request = srv - .get() - .uri(srv.url("/sc/10/12/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome 10 12!")); - - // client request - let request = srv - .get() - .uri(srv.url("/sc/10/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); -} - -#[cfg(actix_impl_trait)] -fn test_impl_trait( - data: (Json, Path, Query), -) -> impl Future { - Delay::new(Instant::now() + Duration::from_millis(10)) - .map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout")) - .and_then(move |_| Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0))) -} - -#[cfg(actix_impl_trait)] -fn test_impl_trait_err( - _data: (Json, Path, Query), -) -> impl Future { - Delay::new(Instant::now() + Duration::from_millis(10)) - .map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout")) - .and_then(move |_| Err(io::Error::new(io::ErrorKind::Other, "other"))) -} - -#[cfg(actix_impl_trait)] -#[test] -fn test_path_and_query_extractor2_async4_impl_trait() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with_async(test_impl_trait) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[cfg(actix_impl_trait)] -#[test] -fn test_path_and_query_extractor2_async4_impl_trait_err() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with_async(test_impl_trait_err) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); -} - -#[test] -fn test_non_ascii_route() { - let mut srv = test::TestServer::new(|app| { - app.resource("/中文/index.html", |r| r.f(|_| "success")); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/中文/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"success")); -} - -#[test] -fn test_unsafe_path_route() { - let mut srv = test::TestServer::new(|app| { - app.resource("/test/{url}", |r| { - r.f(|r| format!("success: {}", &r.match_info()["url"])) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/test/http%3A%2F%2Fexample.com")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!( - bytes, - Bytes::from_static(b"success: http:%2F%2Fexample.com") - ); -} diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs deleted file mode 100644 index 6cb6ee363..000000000 --- a/tests/test_middleware.rs +++ /dev/null @@ -1,1055 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate futures; -extern crate tokio_timer; - -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; -use std::thread; -use std::time::{Duration, Instant}; - -use actix_web::error::{Error, ErrorInternalServerError}; -use actix_web::*; -use futures::{future, Future}; -use tokio_timer::Delay; - -struct MiddlewareTest { - start: Arc, - response: Arc, - finish: Arc, -} - -impl middleware::Middleware for MiddlewareTest { - fn start(&self, _: &HttpRequest) -> Result { - self.start - .store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - Ok(middleware::Started::Done) - } - - fn response( - &self, _: &HttpRequest, resp: HttpResponse, - ) -> Result { - self.response - .store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - Ok(middleware::Response::Done(resp)) - } - - fn finish(&self, _: &HttpRequest, _: &HttpResponse) -> middleware::Finished { - self.finish - .store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - middleware::Finished::Done - } -} - -#[test] -fn test_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_resource_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_scope_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_middleware_async_handler() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new() - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/", |r| { - r.route().a(|_| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(|_| Ok(HttpResponse::Ok())) - }) - }) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_async_handler() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", |r| { - r.middleware(mw); - r.route().a(|_| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(|_| Ok(HttpResponse::Ok())) - }) - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_async_handler() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| { - r.route().a(|_| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(|_| Ok(HttpResponse::Ok())) - }) - }) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -fn index_test_middleware_async_error(_: &HttpRequest) -> FutureResponse { - future::result(Err(error::ErrorBadRequest("TEST"))).responder() -} - -#[test] -fn test_middleware_async_error() { - let req = Arc::new(AtomicUsize::new(0)); - let resp = Arc::new(AtomicUsize::new(0)); - let fin = Arc::new(AtomicUsize::new(0)); - - let act_req = Arc::clone(&req); - let act_resp = Arc::clone(&resp); - let act_fin = Arc::clone(&fin); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_req), - response: Arc::clone(&act_resp), - finish: Arc::clone(&act_fin), - }).handler(index_test_middleware_async_error) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - assert_eq!(req.load(Ordering::Relaxed), 1); - assert_eq!(resp.load(Ordering::Relaxed), 1); - assert_eq!(fin.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_async_error() { - let req = Arc::new(AtomicUsize::new(0)); - let resp = Arc::new(AtomicUsize::new(0)); - let fin = Arc::new(AtomicUsize::new(0)); - - let act_req = Arc::clone(&req); - let act_resp = Arc::clone(&resp); - let act_fin = Arc::clone(&fin); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_req), - response: Arc::clone(&act_resp), - finish: Arc::clone(&act_fin), - }).resource("/test", |r| r.f(index_test_middleware_async_error)) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - assert_eq!(req.load(Ordering::Relaxed), 1); - assert_eq!(resp.load(Ordering::Relaxed), 1); - assert_eq!(fin.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_async_error() { - let req = Arc::new(AtomicUsize::new(0)); - let resp = Arc::new(AtomicUsize::new(0)); - let fin = Arc::new(AtomicUsize::new(0)); - - let act_req = Arc::clone(&req); - let act_resp = Arc::clone(&resp); - let act_fin = Arc::clone(&fin); - - let mut srv = test::TestServer::with_factory(move || { - let mw = MiddlewareAsyncTest { - start: Arc::clone(&act_req), - response: Arc::clone(&act_resp), - finish: Arc::clone(&act_fin), - }; - - App::new().resource("/test", move |r| { - r.middleware(mw); - r.f(index_test_middleware_async_error); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - assert_eq!(req.load(Ordering::Relaxed), 1); - assert_eq!(resp.load(Ordering::Relaxed), 1); - assert_eq!(fin.load(Ordering::Relaxed), 1); -} - -struct MiddlewareAsyncTest { - start: Arc, - response: Arc, - finish: Arc, -} - -impl middleware::Middleware for MiddlewareAsyncTest { - fn start(&self, _: &HttpRequest) -> Result { - let to = Delay::new(Instant::now() + Duration::from_millis(10)); - - let start = Arc::clone(&self.start); - Ok(middleware::Started::Future(Box::new( - to.from_err().and_then(move |_| { - start.fetch_add(1, Ordering::Relaxed); - Ok(None) - }), - ))) - } - - fn response( - &self, _: &HttpRequest, resp: HttpResponse, - ) -> Result { - let to = Delay::new(Instant::now() + Duration::from_millis(10)); - - let response = Arc::clone(&self.response); - Ok(middleware::Response::Future(Box::new( - to.from_err().and_then(move |_| { - response.fetch_add(1, Ordering::Relaxed); - Ok(resp) - }), - ))) - } - - fn finish(&self, _: &HttpRequest, _: &HttpResponse) -> middleware::Finished { - let to = Delay::new(Instant::now() + Duration::from_millis(10)); - - let finish = Arc::clone(&self.finish); - middleware::Finished::Future(Box::new(to.from_err().and_then(move |_| { - finish.fetch_add(1, Ordering::Relaxed); - Ok(()) - }))) - } -} - -#[test] -fn test_async_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_async_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new() - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(50)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_sync_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new() - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(50)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_scope_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_async_scope_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_async_scope_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_resource_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - - thread::sleep(Duration::from_millis(40)); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_async_resource_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - let mw2 = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw1); - r.middleware(mw2); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(40)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_sync_resource_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - let mw2 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw1); - r.middleware(mw2); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(40)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -struct MiddlewareWithErr; - -impl middleware::Middleware for MiddlewareWithErr { - fn start(&self, _: &HttpRequest) -> Result { - Err(ErrorInternalServerError("middleware error")) - } -} - -struct MiddlewareAsyncWithErr; - -impl middleware::Middleware for MiddlewareAsyncWithErr { - fn start(&self, _: &HttpRequest) -> Result { - Ok(middleware::Started::Future(Box::new(future::err( - ErrorInternalServerError("middleware error"), - )))) - } -} - -#[test] -fn test_middleware_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new() - .middleware(mw1) - .middleware(MiddlewareWithErr) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_middleware_async_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new() - .middleware(mw1) - .middleware(MiddlewareAsyncWithErr) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().scope("/scope", |scope| { - scope - .middleware(mw1) - .middleware(MiddlewareWithErr) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_async_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().scope("/scope", |scope| { - scope - .middleware(mw1) - .middleware(MiddlewareAsyncWithErr) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw1); - r.middleware(MiddlewareWithErr); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_async_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw1); - r.middleware(MiddlewareAsyncWithErr); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[cfg(feature = "session")] -#[test] -fn test_session_storage_middleware() { - use actix_web::middleware::session::{ - CookieSessionBackend, RequestSession, SessionStorage, - }; - - const SIMPLE_NAME: &'static str = "simple"; - const SIMPLE_PAYLOAD: &'static str = "kantan"; - const COMPLEX_NAME: &'static str = "test"; - const COMPLEX_PAYLOAD: &'static str = "url=https://test.com&generate_204"; - //TODO: investigate how to handle below input - //const COMPLEX_PAYLOAD: &'static str = "FJc%26continue_url%3Dhttp%253A%252F%252Fconnectivitycheck.gstatic.com%252Fgenerate_204"; - - let mut srv = test::TestServer::with_factory(move || { - App::new() - .middleware(SessionStorage::new( - CookieSessionBackend::signed(&[0; 32]).secure(false), - )).resource("/index", move |r| { - r.f(|req| { - let res = req.session().set(COMPLEX_NAME, COMPLEX_PAYLOAD); - assert!(res.is_ok()); - let value = req.session().get::(COMPLEX_NAME); - assert!(value.is_ok()); - let value = value.unwrap(); - assert!(value.is_some()); - assert_eq!(value.unwrap(), COMPLEX_PAYLOAD); - - let res = req.session().set(SIMPLE_NAME, SIMPLE_PAYLOAD); - assert!(res.is_ok()); - let value = req.session().get::(SIMPLE_NAME); - assert!(value.is_ok()); - let value = value.unwrap(); - assert!(value.is_some()); - assert_eq!(value.unwrap(), SIMPLE_PAYLOAD); - - HttpResponse::Ok() - }) - }).resource("/expect_cookie", move |r| { - r.f(|req| { - let _cookies = req.cookies().expect("To get cookies"); - - let value = req.session().get::(SIMPLE_NAME); - assert!(value.is_ok()); - let value = value.unwrap(); - assert!(value.is_some()); - assert_eq!(value.unwrap(), SIMPLE_PAYLOAD); - - let value = req.session().get::(COMPLEX_NAME); - assert!(value.is_ok()); - let value = value.unwrap(); - assert!(value.is_some()); - assert_eq!(value.unwrap(), COMPLEX_PAYLOAD); - - HttpResponse::Ok() - }) - }) - }); - - let request = srv.get().uri(srv.url("/index")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - - assert!(response.headers().contains_key("set-cookie")); - let set_cookie = response.headers().get("set-cookie"); - assert!(set_cookie.is_some()); - let set_cookie = set_cookie.unwrap().to_str().expect("Convert to str"); - - let request = srv - .get() - .uri(srv.url("/expect_cookie")) - .header("cookie", set_cookie.split(';').next().unwrap()) - .finish() - .unwrap(); - - srv.execute(request.send()).unwrap(); -} diff --git a/tests/test_server.rs b/tests/test_server.rs deleted file mode 100644 index 477d3e64b..000000000 --- a/tests/test_server.rs +++ /dev/null @@ -1,1357 +0,0 @@ -extern crate actix; -extern crate actix_net; -extern crate actix_web; -#[cfg(feature = "brotli")] -extern crate brotli2; -extern crate bytes; -extern crate flate2; -extern crate futures; -extern crate h2; -extern crate http as modhttp; -extern crate rand; -extern crate tokio; -extern crate tokio_current_thread; -extern crate tokio_current_thread as current_thread; -extern crate tokio_reactor; -extern crate tokio_tcp; - -#[cfg(feature = "tls")] -extern crate native_tls; -#[cfg(feature = "ssl")] -extern crate openssl; -#[cfg(feature = "rust-tls")] -extern crate rustls; - -use std::io::{Read, Write}; -use std::sync::Arc; -use std::{thread, time}; - -#[cfg(feature = "brotli")] -use brotli2::write::{BrotliDecoder, BrotliEncoder}; -use bytes::{Bytes, BytesMut}; -use flate2::read::GzDecoder; -use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder}; -use flate2::Compression; -use futures::stream::once; -use futures::{Future, Stream}; -use h2::client as h2client; -use modhttp::Request; -use rand::distributions::Alphanumeric; -use rand::Rng; -use tokio::runtime::current_thread::Runtime; -use tokio_current_thread::spawn; -use tokio_tcp::TcpStream; - -use actix_web::*; - -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; - -#[test] -#[cfg(unix)] -fn test_start() { - use actix::System; - use std::sync::mpsc; - - let _ = test::TestServer::unused_addr(); - let (tx, rx) = mpsc::channel(); - - thread::spawn(|| { - System::run(move || { - let srv = server::new(|| { - vec![App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - })] - }); - - let srv = srv.bind("127.0.0.1:0").unwrap(); - let addr = srv.addrs()[0]; - let srv_addr = srv.start(); - let _ = tx.send((addr, srv_addr, System::current())); - }); - }); - let (addr, srv_addr, sys) = rx.recv().unwrap(); - System::set_current(sys.clone()); - - let mut rt = Runtime::new().unwrap(); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } - - // pause - let _ = srv_addr.send(server::PauseServer).wait(); - thread::sleep(time::Duration::from_millis(200)); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .timeout(time::Duration::from_millis(200)) - .finish() - .unwrap(); - assert!(rt.block_on(req.send()).is_err()); - } - - // resume - let _ = srv_addr.send(server::ResumeServer).wait(); - thread::sleep(time::Duration::from_millis(200)); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } - - let _ = sys.stop(); -} - -#[test] -#[cfg(unix)] -fn test_shutdown() { - use actix::System; - use std::net; - use std::sync::mpsc; - - let _ = test::TestServer::unused_addr(); - let (tx, rx) = mpsc::channel(); - - thread::spawn(|| { - System::run(move || { - let srv = server::new(|| { - vec![App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - })] - }); - - let srv = srv.bind("127.0.0.1:0").unwrap(); - let addr = srv.addrs()[0]; - let srv_addr = srv.shutdown_timeout(1).start(); - let _ = tx.send((addr, srv_addr, System::current())); - }); - }); - let (addr, srv_addr, sys) = rx.recv().unwrap(); - System::set_current(sys.clone()); - - let mut rt = Runtime::new().unwrap(); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()).unwrap(); - srv_addr.do_send(server::StopServer { graceful: true }); - assert!(response.status().is_success()); - } - - thread::sleep(time::Duration::from_millis(1000)); - assert!(net::TcpStream::connect(addr).is_err()); - - let _ = sys.stop(); -} - -#[test] -#[cfg(unix)] -fn test_panic() { - use actix::System; - use std::sync::mpsc; - - let _ = test::TestServer::unused_addr(); - let (tx, rx) = mpsc::channel(); - - thread::spawn(|| { - System::run(move || { - let srv = server::new(|| { - App::new() - .resource("/panic", |r| { - r.method(http::Method::GET).f(|_| -> &'static str { - panic!("error"); - }); - }).resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }) - }).workers(1); - - let srv = srv.bind("127.0.0.1:0").unwrap(); - let addr = srv.addrs()[0]; - srv.start(); - let _ = tx.send((addr, System::current())); - }); - }); - let (addr, sys) = rx.recv().unwrap(); - System::set_current(sys.clone()); - - let mut rt = Runtime::new().unwrap(); - { - let req = client::ClientRequest::get(format!("http://{}/panic", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()); - assert!(response.is_err()); - } - - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()); - assert!(response.is_err()); - } - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } - - let _ = sys.stop(); -} - -#[test] -fn test_simple() { - let mut srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok())); - let req = srv.get().finish().unwrap(); - let response = srv.execute(req.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_headers() { - let data = STR.repeat(10); - let srv_data = Arc::new(data.clone()); - let mut srv = test::TestServer::new(move |app| { - let data = srv_data.clone(); - app.handler(move |_| { - let mut builder = HttpResponse::Ok(); - for idx in 0..90 { - builder.header( - format!("X-TEST-{}", idx).as_str(), - "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); - } - builder.body(data.as_ref()) - }) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_body() { - let mut srv = - test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_gzip() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(STR) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_gzip_large() { - let data = STR.repeat(10); - let srv_data = Arc::new(data.clone()); - - let mut srv = test::TestServer::new(move |app| { - let data = srv_data.clone(); - app.handler(move |_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.as_ref()) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from(data)); -} - -#[test] -fn test_body_gzip_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(70_000) - .collect::(); - let srv_data = Arc::new(data.clone()); - - let mut srv = test::TestServer::new(move |app| { - let data = srv_data.clone(); - app.handler(move |_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.as_ref()) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(dec.len(), data.len()); - assert_eq!(Bytes::from(dec), Bytes::from(data)); -} - -#[test] -fn test_body_chunked_implicit() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_body_br_streaming() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode br - let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_head_empty() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| HttpResponse::Ok().content_length(STR.len() as u64).finish()) - }); - - let request = srv.head().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert!(bytes.is_empty()); -} - -#[test] -fn test_head_binary() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .content_length(100) - .body(STR) - }) - }); - - let request = srv.head().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert!(bytes.is_empty()); -} - -#[test] -fn test_head_binary2() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(STR) - }) - }); - - let request = srv.head().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } -} - -#[test] -fn test_body_length() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .content_length(STR.len() as u64) - .content_encoding(http::ContentEncoding::Identity) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_chunked_explicit() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .chunked() - .content_encoding(http::ContentEncoding::Gzip) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_identity() { - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - let enc2 = enc.clone(); - - let mut srv = test::TestServer::new(move |app| { - let enc3 = enc2.clone(); - app.handler(move |_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc3.clone()) - }) - }); - - // client request - let request = srv - .get() - .header("accept-encoding", "deflate") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode deflate - assert_eq!(bytes, Bytes::from(STR)); -} - -#[test] -fn test_body_deflate() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(STR) - }) - }); - - // client request - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode deflate - let mut e = ZlibDecoder::new(Vec::new()); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_body_brotli() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(STR) - }) - }); - - // client request - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode brotli - let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_gzip_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "gzip") - .body(enc.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_gzip_encoding_large() { - let data = STR.repeat(10); - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "gzip") - .body(enc.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_reading_gzip_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(60_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "gzip") - .body(enc.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_reading_deflate_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_reading_deflate_encoding_large() { - let data = STR.repeat(10); - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_reading_deflate_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_brotli_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "br") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_brotli_encoding_large() { - let data = STR.repeat(10); - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "br") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(feature = "brotli", feature = "ssl"))] -#[test] -fn test_brotli_encoding_large_ssl() { - use actix::{Actor, System}; - use openssl::ssl::{ - SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode, - }; - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - let data = STR.repeat(10); - let srv = test::TestServer::build().ssl(builder).start(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - let mut rt = System::new("test"); - - // client connector - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let conn = client::ClientConnector::with_connector(builder.build()).start(); - - // body - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = client::ClientRequest::build() - .uri(srv.url("/")) - .method(http::Method::POST) - .header(http::header::CONTENT_ENCODING, "br") - .with_connector(conn) - .body(enc) - .unwrap(); - let response = rt.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = rt.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(feature = "rust-tls", feature = "ssl"))] -#[test] -fn test_reading_deflate_encoding_large_random_ssl() { - use actix::{Actor, System}; - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - use rustls::internal::pemfile::{certs, rsa_private_keys}; - use rustls::{NoClientAuth, ServerConfig}; - use std::fs::File; - use std::io::BufReader; - - // load ssl keys - let mut config = ServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = rsa_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); - - let srv = test::TestServer::build().rustls(config).start(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut rt = System::new("test"); - - // client connector - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let conn = client::ClientConnector::with_connector(builder.build()).start(); - - // encode data - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = client::ClientRequest::build() - .uri(srv.url("/")) - .method(http::Method::POST) - .header(http::header::CONTENT_ENCODING, "deflate") - .with_connector(conn) - .body(enc) - .unwrap(); - let response = rt.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = rt.block_on(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(feature = "tls", feature = "ssl"))] -#[test] -fn test_reading_deflate_encoding_large_random_tls() { - use native_tls::{Identity, TlsAcceptor}; - use openssl::ssl::{ - SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode, - }; - use std::fs::File; - use std::sync::mpsc; - - use actix::{Actor, System}; - let (tx, rx) = mpsc::channel(); - - // load ssl keys - let mut file = File::open("tests/identity.pfx").unwrap(); - let mut identity = vec![]; - file.read_to_end(&mut identity).unwrap(); - let identity = Identity::from_pkcs12(&identity, "1").unwrap(); - let acceptor = TlsAcceptor::new(identity).unwrap(); - - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); - - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - System::run(move || { - server::new(|| { - App::new().handler("/", |req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }).bind_tls(addr, acceptor) - .unwrap() - .start(); - let _ = tx.send(System::current()); - }); - }); - let sys = rx.recv().unwrap(); - - let mut rt = System::new("test"); - - // client connector - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let conn = client::ClientConnector::with_connector(builder.build()).start(); - - // encode data - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = client::ClientRequest::build() - .uri(format!("https://{}/", addr)) - .method(http::Method::POST) - .header(http::header::CONTENT_ENCODING, "deflate") - .with_connector(conn) - .body(enc) - .unwrap(); - let response = rt.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = rt.block_on(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); - - let _ = sys.stop(); -} - -#[test] -fn test_h2() { - let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - let addr = srv.addr(); - thread::sleep(time::Duration::from_millis(500)); - - let mut core = Runtime::new().unwrap(); - let tcp = TcpStream::connect(&addr); - - let tcp = tcp - .then(|res| h2client::handshake(res.unwrap())) - .then(move |res| { - let (mut client, h2) = res.unwrap(); - - let request = Request::builder() - .uri(format!("https://{}/", addr).as_str()) - .body(()) - .unwrap(); - let (response, _) = client.send_request(request, false).unwrap(); - - // Spawn a task to run the conn... - spawn(h2.map_err(|e| println!("GOT ERR={:?}", e))); - - response.and_then(|response| { - assert_eq!(response.status(), http::StatusCode::OK); - - let (_, body) = response.into_parts(); - - body.fold(BytesMut::new(), |mut b, c| -> Result<_, h2::Error> { - b.extend(c); - Ok(b) - }) - }) - }); - let _res = core.block_on(tcp); - // assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_application() { - let mut srv = test::TestServer::with_factory(|| { - App::new().resource("/", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_default_404_handler_response() { - let mut srv = test::TestServer::with_factory(|| { - App::new() - .prefix("/app") - .resource("", |r| r.f(|_| HttpResponse::Ok())) - .resource("/", |r| r.f(|_| HttpResponse::Ok())) - }); - let addr = srv.addr(); - - let mut buf = [0; 24]; - let request = TcpStream::connect(&addr) - .and_then(|sock| { - tokio::io::write_all(sock, "HEAD / HTTP/1.1\r\nHost: localhost\r\n\r\n") - .and_then(|(sock, _)| tokio::io::read_exact(sock, &mut buf)) - .and_then(|(_, buf)| Ok(buf)) - }).map_err(|e| panic!("{:?}", e)); - let response = srv.execute(request).unwrap(); - let rep = String::from_utf8_lossy(&response[..]); - assert!(rep.contains("HTTP/1.1 404 Not Found")); -} - -#[test] -fn test_server_cookies() { - use actix_web::http; - - let mut srv = test::TestServer::with_factory(|| { - App::new().resource("/", |r| { - r.f(|_| { - HttpResponse::Ok() - .cookie( - http::CookieBuilder::new("first", "first_value") - .http_only(true) - .finish(), - ).cookie(http::Cookie::new("second", "first_value")) - .cookie(http::Cookie::new("second", "second_value")) - .finish() - }) - }) - }); - - let first_cookie = http::CookieBuilder::new("first", "first_value") - .http_only(true) - .finish(); - let second_cookie = http::Cookie::new("second", "second_value"); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - let cookies = response.cookies().expect("To have cookies"); - assert_eq!(cookies.len(), 2); - if cookies[0] == first_cookie { - assert_eq!(cookies[1], second_cookie); - } else { - assert_eq!(cookies[0], second_cookie); - assert_eq!(cookies[1], first_cookie); - } - - let first_cookie = first_cookie.to_string(); - let second_cookie = second_cookie.to_string(); - //Check that we have exactly two instances of raw cookie headers - let cookies = response - .headers() - .get_all(http::header::SET_COOKIE) - .iter() - .map(|header| header.to_str().expect("To str").to_string()) - .collect::>(); - assert_eq!(cookies.len(), 2); - if cookies[0] == first_cookie { - assert_eq!(cookies[1], second_cookie); - } else { - assert_eq!(cookies[0], second_cookie); - assert_eq!(cookies[1], first_cookie); - } -} - -#[test] -fn test_slow_request() { - use actix::System; - use std::net; - use std::sync::mpsc; - let (tx, rx) = mpsc::channel(); - - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - System::run(move || { - let srv = server::new(|| { - vec![App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - })] - }); - - let srv = srv.bind(addr).unwrap(); - srv.client_timeout(200).start(); - let _ = tx.send(System::current()); - }); - }); - let sys = rx.recv().unwrap(); - - thread::sleep(time::Duration::from_millis(200)); - - let mut stream = net::TcpStream::connect(addr).unwrap(); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); - - let mut stream = net::TcpStream::connect(addr).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); - - sys.stop(); -} - -#[test] -fn test_malformed_request() { - use actix::System; - use std::net; - use std::sync::mpsc; - let (tx, rx) = mpsc::channel(); - - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - System::run(move || { - let srv = server::new(|| { - App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }) - }); - - let _ = srv.bind(addr).unwrap().start(); - let _ = tx.send(System::current()); - }); - }); - let sys = rx.recv().unwrap(); - thread::sleep(time::Duration::from_millis(200)); - - let mut stream = net::TcpStream::connect(addr).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP1.1\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 400 Bad Request")); - - sys.stop(); -} - -#[test] -fn test_app_404() { - let mut srv = test::TestServer::with_factory(|| { - App::new().prefix("/prefix").resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }) - }); - - let request = srv.client(http::Method::GET, "/prefix/").finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - let request = srv.client(http::Method::GET, "/").finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::NOT_FOUND); -} - -#[test] -#[cfg(feature = "ssl")] -fn test_ssl_handshake_timeout() { - use actix::System; - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - use std::net; - use std::sync::mpsc; - - let (tx, rx) = mpsc::channel(); - let addr = test::TestServer::unused_addr(); - - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - thread::spawn(move || { - System::run(move || { - let srv = server::new(|| { - App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }) - }); - - srv.bind_ssl(addr, builder) - .unwrap() - .workers(1) - .client_timeout(200) - .start(); - let _ = tx.send(System::current()); - }); - }); - let sys = rx.recv().unwrap(); - - let mut stream = net::TcpStream::connect(addr).unwrap(); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.is_empty()); - - let _ = sys.stop(); -} diff --git a/tests/test_ws.rs b/tests/test_ws.rs deleted file mode 100644 index 5a0ce204f..000000000 --- a/tests/test_ws.rs +++ /dev/null @@ -1,394 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate bytes; -extern crate futures; -extern crate http; -extern crate rand; - -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; -use std::{thread, time}; - -use bytes::Bytes; -use futures::Stream; -use rand::distributions::Alphanumeric; -use rand::Rng; - -#[cfg(feature = "ssl")] -extern crate openssl; -#[cfg(feature = "rust-tls")] -extern crate rustls; - -use actix::prelude::*; -use actix_web::*; - -struct Ws; - -impl Actor for Ws { - type Context = ws::WebsocketContext; -} - -impl StreamHandler for Ws { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(reason) => ctx.close(reason), - _ => (), - } - } -} - -#[test] -fn test_simple() { - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (reader, mut writer) = srv.ws().unwrap(); - - writer.text("text"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); - - writer.binary(b"text".as_ref()); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Binary(Bytes::from_static(b"text").into())) - ); - - writer.ping("ping"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Pong("ping".to_owned()))); - - writer.close(Some(ws::CloseCode::Normal.into())); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - ); -} - -// websocket resource helper function -fn start_ws_resource(req: &HttpRequest) -> Result { - ws::start(req, Ws) -} - -#[test] -fn test_simple_path() { - const PATH: &str = "/v1/ws/"; - - // Create a websocket at a specific path. - let mut srv = test::TestServer::new(|app| { - app.resource(PATH, |r| r.route().f(start_ws_resource)); - }); - // fetch the sockets for the resource at a given path. - let (reader, mut writer) = srv.ws_at(PATH).unwrap(); - - writer.text("text"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); - - writer.binary(b"text".as_ref()); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Binary(Bytes::from_static(b"text").into())) - ); - - writer.ping("ping"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Pong("ping".to_owned()))); - - writer.close(Some(ws::CloseCode::Normal.into())); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - ); -} - -#[test] -fn test_empty_close_code() { - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (reader, mut writer) = srv.ws().unwrap(); - - writer.close(None); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Close(None))); -} - -#[test] -fn test_close_description() { - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (reader, mut writer) = srv.ws().unwrap(); - - let close_reason: ws::CloseReason = - (ws::CloseCode::Normal, "close description").into(); - writer.close(Some(close_reason.clone())); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Close(Some(close_reason)))); -} - -#[test] -fn test_large_text() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(65_536) - .collect::(); - - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (mut reader, mut writer) = srv.ws().unwrap(); - - for _ in 0..100 { - writer.text(data.clone()); - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, Some(ws::Message::Text(data.clone()))); - } -} - -#[test] -fn test_large_bin() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(65_536) - .collect::(); - - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (mut reader, mut writer) = srv.ws().unwrap(); - - for _ in 0..100 { - writer.binary(data.clone()); - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, Some(ws::Message::Binary(Binary::from(data.clone())))); - } -} - -#[test] -fn test_client_frame_size() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(131_072) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req| -> Result { - let mut resp = ws::handshake(req)?; - let stream = ws::WsStream::new(req.payload()).max_size(131_072); - - let body = ws::WebsocketContext::create(req.clone(), Ws, stream); - Ok(resp.body(body)) - }) - }); - let (reader, mut writer) = srv.ws().unwrap(); - - writer.binary(data.clone()); - match srv.execute(reader.into_future()).err().unwrap().0 { - ws::ProtocolError::Overflow => (), - _ => panic!(), - } -} - -struct Ws2 { - count: usize, - bin: bool, -} - -impl Actor for Ws2 { - type Context = ws::WebsocketContext; - - fn started(&mut self, ctx: &mut Self::Context) { - self.send(ctx); - } -} - -impl Ws2 { - fn send(&mut self, ctx: &mut ws::WebsocketContext) { - if self.bin { - ctx.binary(Vec::from("0".repeat(65_536))); - } else { - ctx.text("0".repeat(65_536)); - } - ctx.drain() - .and_then(|_, act, ctx| { - act.count += 1; - if act.count != 10_000 { - act.send(ctx); - } - actix::fut::ok(()) - }).wait(ctx); - } -} - -impl StreamHandler for Ws2 { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(reason) => ctx.close(reason), - _ => (), - } - } -} - -#[test] -fn test_server_send_text() { - let data = Some(ws::Message::Text("0".repeat(65_536))); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: false, - }, - ) - }) - }); - let (mut reader, _writer) = srv.ws().unwrap(); - - for _ in 0..10_000 { - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, data); - } -} - -#[test] -fn test_server_send_bin() { - let data = Some(ws::Message::Binary(Binary::from("0".repeat(65_536)))); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: true, - }, - ) - }) - }); - let (mut reader, _writer) = srv.ws().unwrap(); - - for _ in 0..10_000 { - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, data); - } -} - -#[test] -#[cfg(feature = "ssl")] -fn test_ws_server_ssl() { - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - let mut srv = test::TestServer::build().ssl(builder).start(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: false, - }, - ) - }) - }); - let (mut reader, _writer) = srv.ws().unwrap(); - - let data = Some(ws::Message::Text("0".repeat(65_536))); - for _ in 0..10_000 { - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, data); - } -} - -#[test] -#[cfg(feature = "rust-tls")] -fn test_ws_server_rust_tls() { - use rustls::internal::pemfile::{certs, rsa_private_keys}; - use rustls::{NoClientAuth, ServerConfig}; - use std::fs::File; - use std::io::BufReader; - - // load ssl keys - let mut config = ServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = rsa_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - - let mut srv = test::TestServer::build().rustls(config).start(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: false, - }, - ) - }) - }); - - let (mut reader, _writer) = srv.ws().unwrap(); - - let data = Some(ws::Message::Text("0".repeat(65_536))); - for _ in 0..10_000 { - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, data); - } -} - -struct WsStopped(Arc); - -impl Actor for WsStopped { - type Context = ws::WebsocketContext; - - fn stopped(&mut self, _: &mut Self::Context) { - self.0.fetch_add(1, Ordering::Relaxed); - } -} - -impl StreamHandler for WsStopped { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Text(text) => ctx.text(text), - _ => (), - } - } -} - -#[test] -fn test_ws_stopped() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); - - let mut srv = test::TestServer::new(move |app| { - let num3 = num2.clone(); - app.handler(move |req| ws::start(req, WsStopped(num3.clone()))) - }); - { - let (reader, mut writer) = srv.ws().unwrap(); - writer.text("text"); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); - } - thread::sleep(time::Duration::from_millis(1000)); - - assert_eq!(num.load(Ordering::Relaxed), 1); -} From b15b2dda22dda5d13c58c457ec3ada773c03d5b8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 17:34:57 -0700 Subject: [PATCH 003/208] remove ServerSettings --- Cargo.toml | 10 +- src/error.rs | 4 - src/info.rs | 220 ----------------------------------------- src/lib.rs | 12 --- src/server/error.rs | 17 ---- src/server/message.rs | 46 ++------- src/server/mod.rs | 7 +- src/server/settings.rs | 119 +--------------------- 8 files changed, 10 insertions(+), 425 deletions(-) delete mode 100644 src/info.rs diff --git a/Cargo.toml b/Cargo.toml index 258301daa..60f14485f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,14 +66,11 @@ actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" -h2 = "0.1" -htmlescape = "0.3" http = "^0.1.8" httparse = "1.3" log = "0.4" mime = "0.3" mime_guess = "2.0.0-alpha" -num_cpus = "1.0" percent-encoding = "1.0" rand = "0.5" regex = "1.0" @@ -85,8 +82,6 @@ time = "0.1" encoding = "0.2" language-tags = "0.2" lazy_static = "1.0" -lazycell = "1.0.0" -parking_lot = "0.6" serde_urlencoded = "^0.5.3" url = { version="1.7", features=["query_encoding"] } cookie = { version="0.11", features=["percent-encode"] } @@ -96,14 +91,10 @@ flate2 = { version="^1.0.2", optional = true, default-features = false } failure = "^0.1.2" # io -mio = "^0.6.13" net2 = "0.2" bytes = "0.4" byteorder = "1.2" futures = "0.1" -futures-cpupool = "0.1" -slab = "0.4" -tokio = "0.1" tokio-codec = "0.1" tokio-io = "0.1" tokio-tcp = "0.1" @@ -132,6 +123,7 @@ tokio-uds = { version="0.2", optional = true } actix-web = "0.7" env_logger = "0.5" serde_derive = "1.0" +tokio = "0.1" [build-dependencies] version_check = "0.1" diff --git a/src/error.rs b/src/error.rs index 724803805..ff2388de8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,7 +11,6 @@ use failure::{self, Backtrace, Fail}; use futures::Canceled; use http::uri::InvalidUri; use http::{header, Error as HttpError, StatusCode}; -use http2::Error as Http2Error; use httparse; use serde::de::value::Error as DeError; use serde_json::error::Error as JsonError; @@ -350,9 +349,6 @@ pub enum PayloadError { /// Io error #[fail(display = "{}", _0)] Io(#[cause] IoError), - /// Http2 error - #[fail(display = "{}", _0)] - Http2(#[cause] Http2Error), } impl From for PayloadError { diff --git a/src/info.rs b/src/info.rs deleted file mode 100644 index 5a2f21805..000000000 --- a/src/info.rs +++ /dev/null @@ -1,220 +0,0 @@ -use http::header::{self, HeaderName}; -use server::Request; - -const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for"; -const X_FORWARDED_HOST: &[u8] = b"x-forwarded-host"; -const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto"; - -/// `HttpRequest` connection information -#[derive(Clone, Default)] -pub struct ConnectionInfo { - scheme: String, - host: String, - remote: Option, - peer: Option, -} - -impl ConnectionInfo { - /// Create *ConnectionInfo* instance for a request. - #[cfg_attr( - feature = "cargo-clippy", - allow(clippy::cyclomatic_complexity) - )] - pub fn update(&mut self, req: &Request) { - let mut host = None; - let mut scheme = None; - let mut remote = None; - let mut peer = None; - - // load forwarded header - for hdr in req.headers().get_all(header::FORWARDED) { - if let Ok(val) = hdr.to_str() { - for pair in val.split(';') { - for el in pair.split(',') { - let mut items = el.trim().splitn(2, '='); - if let Some(name) = items.next() { - if let Some(val) = items.next() { - match &name.to_lowercase() as &str { - "for" => if remote.is_none() { - remote = Some(val.trim()); - }, - "proto" => if scheme.is_none() { - scheme = Some(val.trim()); - }, - "host" => if host.is_none() { - host = Some(val.trim()); - }, - _ => (), - } - } - } - } - } - } - } - - // scheme - if scheme.is_none() { - if let Some(h) = req - .headers() - .get(HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap()) - { - if let Ok(h) = h.to_str() { - scheme = h.split(',').next().map(|v| v.trim()); - } - } - if scheme.is_none() { - scheme = req.uri().scheme_part().map(|a| a.as_str()); - if scheme.is_none() && req.server_settings().secure() { - scheme = Some("https") - } - } - } - - // host - if host.is_none() { - if let Some(h) = req - .headers() - .get(HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap()) - { - if let Ok(h) = h.to_str() { - host = h.split(',').next().map(|v| v.trim()); - } - } - if host.is_none() { - if let Some(h) = req.headers().get(header::HOST) { - host = h.to_str().ok(); - } - if host.is_none() { - host = req.uri().authority_part().map(|a| a.as_str()); - if host.is_none() { - host = Some(req.server_settings().host()); - } - } - } - } - - // remote addr - if remote.is_none() { - if let Some(h) = req - .headers() - .get(HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap()) - { - if let Ok(h) = h.to_str() { - remote = h.split(',').next().map(|v| v.trim()); - } - } - if remote.is_none() { - // get peeraddr from socketaddr - peer = req.peer_addr().map(|addr| format!("{}", addr)); - } - } - - self.scheme = scheme.unwrap_or("http").to_owned(); - self.host = host.unwrap_or("localhost").to_owned(); - self.remote = remote.map(|s| s.to_owned()); - self.peer = peer; - } - - /// Scheme of the request. - /// - /// Scheme is resolved through the following headers, in this order: - /// - /// - Forwarded - /// - X-Forwarded-Proto - /// - Uri - #[inline] - pub fn scheme(&self) -> &str { - &self.scheme - } - - /// Hostname of the request. - /// - /// Hostname is resolved through the following headers, in this order: - /// - /// - Forwarded - /// - X-Forwarded-Host - /// - Host - /// - Uri - /// - Server hostname - pub fn host(&self) -> &str { - &self.host - } - - /// Remote IP of client initiated HTTP request. - /// - /// The IP is resolved through the following headers, in this order: - /// - /// - Forwarded - /// - X-Forwarded-For - /// - peer name of opened socket - #[inline] - pub fn remote(&self) -> Option<&str> { - if let Some(ref r) = self.remote { - Some(r) - } else if let Some(ref peer) = self.peer { - Some(peer) - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test::TestRequest; - - #[test] - fn test_forwarded() { - let req = TestRequest::default().request(); - let mut info = ConnectionInfo::default(); - info.update(&req); - assert_eq!(info.scheme(), "http"); - assert_eq!(info.host(), "localhost:8080"); - - let req = TestRequest::default() - .header( - header::FORWARDED, - "for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org", - ).request(); - - let mut info = ConnectionInfo::default(); - info.update(&req); - assert_eq!(info.scheme(), "https"); - assert_eq!(info.host(), "rust-lang.org"); - assert_eq!(info.remote(), Some("192.0.2.60")); - - let req = TestRequest::default() - .header(header::HOST, "rust-lang.org") - .request(); - - let mut info = ConnectionInfo::default(); - info.update(&req); - assert_eq!(info.scheme(), "http"); - assert_eq!(info.host(), "rust-lang.org"); - assert_eq!(info.remote(), None); - - let req = TestRequest::default() - .header(X_FORWARDED_FOR, "192.0.2.60") - .request(); - let mut info = ConnectionInfo::default(); - info.update(&req); - assert_eq!(info.remote(), Some("192.0.2.60")); - - let req = TestRequest::default() - .header(X_FORWARDED_HOST, "192.0.2.60") - .request(); - let mut info = ConnectionInfo::default(); - info.update(&req); - assert_eq!(info.host(), "192.0.2.60"); - assert_eq!(info.remote(), None); - - let req = TestRequest::default() - .header(X_FORWARDED_PROTO, "https") - .request(); - let mut info = ConnectionInfo::default(); - info.update(&req); - assert_eq!(info.scheme(), "https"); - } -} diff --git a/src/lib.rs b/src/lib.rs index 6df1a770e..efd566187 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,20 +99,13 @@ extern crate lazy_static; #[macro_use] extern crate futures; extern crate cookie; -extern crate futures_cpupool; -extern crate htmlescape; extern crate http as modhttp; extern crate httparse; extern crate language_tags; -extern crate lazycell; extern crate mime; extern crate mime_guess; -extern crate mio; extern crate net2; -extern crate parking_lot; extern crate rand; -extern crate slab; -extern crate tokio; extern crate tokio_codec; extern crate tokio_current_thread; extern crate tokio_io; @@ -129,8 +122,6 @@ extern crate brotli2; extern crate encoding; #[cfg(feature = "flate2")] extern crate flate2; -extern crate h2 as http2; -extern crate num_cpus; extern crate serde_urlencoded; #[macro_use] extern crate percent_encoding; @@ -148,9 +139,7 @@ mod extensions; mod header; mod httpcodes; mod httpmessage; -//mod httprequest; mod httpresponse; -mod info; mod json; mod payload; mod uri; @@ -182,7 +171,6 @@ pub mod dev { pub use body::BodyStream; pub use httpmessage::{MessageBody, Readlines, UrlEncoded}; pub use httpresponse::HttpResponseBuilder; - pub use info::ConnectionInfo; pub use json::JsonBody; pub use payload::{Payload, PayloadBuffer}; } diff --git a/src/server/error.rs b/src/server/error.rs index d9e1239e1..7d5c67d1e 100644 --- a/src/server/error.rs +++ b/src/server/error.rs @@ -2,7 +2,6 @@ use std::fmt::{Debug, Display}; use std::io; use futures::{Async, Poll}; -use http2; use error::{Error, ParseError}; use http::{StatusCode, Version}; @@ -44,10 +43,6 @@ pub enum HttpDispatchError { // #[fail(display = "Connection shutdown timeout")] ShutdownTimeout, - /// HTTP2 error - // #[fail(display = "HTTP2 error: {}", _0)] - Http2(http2::Error), - /// Payload is not consumed // #[fail(display = "Task is completed but request's payload is not consumed")] PayloadIsNotConsumed, @@ -65,12 +60,6 @@ pub enum HttpDispatchError { Unknown, } -// impl From for HttpDispatchError { -// fn from(err: E) -> Self { -// HttpDispatchError::App(err) -// } -// } - impl From for HttpDispatchError { fn from(err: ParseError) -> Self { HttpDispatchError::Parse(err) @@ -82,9 +71,3 @@ impl From for HttpDispatchError { HttpDispatchError::Io(err) } } - -impl From for HttpDispatchError { - fn from(err: http2::Error) -> Self { - HttpDispatchError::Http2(err) - } -} diff --git a/src/server/message.rs b/src/server/message.rs index 74ec5f17c..c39302bab 100644 --- a/src/server/message.rs +++ b/src/server/message.rs @@ -8,9 +8,7 @@ use http::{header, HeaderMap, Method, Uri, Version}; use extensions::Extensions; use httpmessage::HttpMessage; -use info::ConnectionInfo; use payload::Payload; -use server::ServerSettings; use uri::Url as InnerUrl; bitflags! { @@ -33,9 +31,7 @@ pub(crate) struct InnerRequest { pub(crate) headers: HeaderMap, pub(crate) extensions: RefCell, pub(crate) addr: Option, - pub(crate) info: RefCell, pub(crate) payload: RefCell>, - pub(crate) settings: ServerSettings, pub(crate) stream_extensions: Option>, pool: &'static RequestPool, } @@ -70,18 +66,16 @@ impl HttpMessage for Request { impl Request { /// Create new RequestContext instance - pub(crate) fn new(pool: &'static RequestPool, settings: ServerSettings) -> Request { + pub(crate) fn new(pool: &'static RequestPool) -> Request { Request { inner: Rc::new(InnerRequest { pool, - settings, method: Method::GET, url: InnerUrl::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), flags: Cell::new(MessageFlags::empty()), addr: None, - info: RefCell::new(ConnectionInfo::default()), payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), stream_extensions: None, @@ -144,9 +138,6 @@ impl Request { /// /// Peer address is actual socket address, if proxy is used in front of /// actix http server, then peer address would be address of this proxy. - /// - /// To get client connection information `connection_info()` method should - /// be used. pub fn peer_addr(&self) -> Option { self.inner().addr } @@ -179,31 +170,12 @@ impl Request { self.inner().method == Method::CONNECT } - /// Get *ConnectionInfo* for the correct request. - pub fn connection_info(&self) -> Ref { - if self.inner().flags.get().contains(MessageFlags::CONN_INFO) { - self.inner().info.borrow() - } else { - let mut flags = self.inner().flags.get(); - flags.insert(MessageFlags::CONN_INFO); - self.inner().flags.set(flags); - self.inner().info.borrow_mut().update(self); - self.inner().info.borrow() - } - } - /// Io stream extensions #[inline] pub fn stream_extensions(&self) -> Option<&Extensions> { self.inner().stream_extensions.as_ref().map(|e| e.as_ref()) } - /// Server settings - #[inline] - pub fn server_settings(&self) -> &ServerSettings { - &self.inner().settings - } - pub(crate) fn clone(&self) -> Self { Request { inner: self.inner.clone(), @@ -241,24 +213,18 @@ impl fmt::Debug for Request { } } -pub struct RequestPool(RefCell>>, RefCell); +pub struct RequestPool(RefCell>>); thread_local!(static POOL: &'static RequestPool = RequestPool::create()); impl RequestPool { fn create() -> &'static RequestPool { - let pool = RequestPool( - RefCell::new(VecDeque::with_capacity(128)), - RefCell::new(ServerSettings::default()), - ); + let pool = RequestPool(RefCell::new(VecDeque::with_capacity(128))); Box::leak(Box::new(pool)) } - pub(crate) fn pool(settings: ServerSettings) -> &'static RequestPool { - POOL.with(|p| { - *p.1.borrow_mut() = settings; - *p - }) + pub(crate) fn pool() -> &'static RequestPool { + POOL.with(|p| *p) } #[inline] @@ -266,7 +232,7 @@ impl RequestPool { if let Some(msg) = pool.0.borrow_mut().pop_front() { Request { inner: msg } } else { - Request::new(pool, pool.1.borrow().clone()) + Request::new(pool) } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 7d64a6e20..be172e646 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -130,10 +130,8 @@ pub(crate) mod output; // pub(crate) mod service; pub(crate) mod settings; -pub use self::message::Request; - pub use self::error::{AcceptorError, HttpDispatchError}; -pub use self::settings::ServerSettings; +pub use self::message::Request; #[doc(hidden)] pub mod h1disp; @@ -141,9 +139,6 @@ pub mod h1disp; #[doc(hidden)] pub use self::settings::{ServiceConfig, ServiceConfigBuilder}; -//#[doc(hidden)] -//pub use self::service::{H1Service, HttpService, StreamConfiguration}; - #[doc(hidden)] pub use self::helpers::write_content_length; diff --git a/src/server/settings.rs b/src/server/settings.rs index f21283590..b8b7e51f0 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -7,10 +7,7 @@ use std::{env, fmt, net}; use bytes::BytesMut; use futures::{future, Future}; -use futures_cpupool::CpuPool; use http::StatusCode; -use lazycell::LazyCell; -use parking_lot::Mutex; use time; use tokio_current_thread::spawn; use tokio_timer::{sleep, Delay}; @@ -20,109 +17,6 @@ use super::KeepAlive; use body::Body; use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; -/// Env variable for default cpu pool size -const ENV_CPU_POOL_VAR: &str = "ACTIX_CPU_POOL"; - -lazy_static! { - pub(crate) static ref DEFAULT_CPUPOOL: Mutex = { - let default = match env::var(ENV_CPU_POOL_VAR) { - Ok(val) => { - if let Ok(val) = val.parse() { - val - } else { - error!("Can not parse ACTIX_CPU_POOL value"); - 20 - } - } - Err(_) => 20, - }; - Mutex::new(CpuPool::new(default)) - }; -} - -/// Various server settings -pub struct ServerSettings { - addr: net::SocketAddr, - secure: bool, - host: String, - cpu_pool: LazyCell, - responses: &'static HttpResponsePool, -} - -impl Clone for ServerSettings { - fn clone(&self) -> Self { - ServerSettings { - addr: self.addr, - secure: self.secure, - host: self.host.clone(), - cpu_pool: LazyCell::new(), - responses: HttpResponsePool::get_pool(), - } - } -} - -impl Default for ServerSettings { - fn default() -> Self { - ServerSettings { - addr: "127.0.0.1:8080".parse().unwrap(), - secure: false, - host: "localhost:8080".to_owned(), - responses: HttpResponsePool::get_pool(), - cpu_pool: LazyCell::new(), - } - } -} - -impl ServerSettings { - /// Crate server settings instance - pub(crate) fn new( - addr: net::SocketAddr, host: &str, secure: bool, - ) -> ServerSettings { - let host = host.to_owned(); - let cpu_pool = LazyCell::new(); - let responses = HttpResponsePool::get_pool(); - ServerSettings { - addr, - secure, - host, - cpu_pool, - responses, - } - } - - /// Returns the socket address of the local half of this TCP connection - pub fn local_addr(&self) -> net::SocketAddr { - self.addr - } - - /// Returns true if connection is secure(https) - pub fn secure(&self) -> bool { - self.secure - } - - /// Returns host header value - pub fn host(&self) -> &str { - &self.host - } - - /// Returns default `CpuPool` for server - pub fn cpu_pool(&self) -> &CpuPool { - self.cpu_pool.borrow_with(|| DEFAULT_CPUPOOL.lock().clone()) - } - - #[inline] - pub(crate) fn get_response(&self, status: StatusCode, body: Body) -> HttpResponse { - HttpResponsePool::get_response(&self.responses, status, body) - } - - #[inline] - pub(crate) fn get_response_builder( - &self, status: StatusCode, - ) -> HttpResponseBuilder { - HttpResponsePool::get_builder(&self.responses, status) - } -} - // "Sun, 06 Nov 1994 08:49:37 GMT".len() const DATE_VALUE_LENGTH: usize = 29; @@ -149,7 +43,6 @@ impl ServiceConfig { /// Create instance of `ServiceConfig` pub(crate) fn new( keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64, - settings: ServerSettings, ) -> ServiceConfig { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), @@ -168,7 +61,7 @@ impl ServiceConfig { client_timeout, client_shutdown, bytes: Rc::new(SharedBytesPool::new()), - messages: RequestPool::pool(settings), + messages: RequestPool::pool(), date: UnsafeCell::new((false, Date::new())), })) } @@ -211,9 +104,7 @@ impl ServiceConfig { // Unsafe: WorkerSetting is !Sync and !Send unsafe { (*self.0.date.get()).0 = false }; } -} -impl ServiceConfig { #[inline] /// Client timeout for first request. pub fn client_timer(&self) -> Option { @@ -412,15 +303,9 @@ impl ServiceConfigBuilder { /// Finish service configuration and create `ServiceConfig` object. pub fn finish(self) -> ServiceConfig { - let settings = ServerSettings::new(self.addr, &self.host, self.secure); let client_shutdown = if self.secure { self.client_shutdown } else { 0 }; - ServiceConfig::new( - self.keep_alive, - self.client_timeout, - client_shutdown, - settings, - ) + ServiceConfig::new(self.keep_alive, self.client_timeout, client_shutdown) } } From 4ca711909b07b882f57fc76c395da37378bee649 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 20:02:10 -0700 Subject: [PATCH 004/208] refactor types --- src/{server/settings.rs => config.rs} | 50 +- src/error.rs | 136 +-- src/{server/h1codec.rs => h1/codec.rs} | 32 +- src/{server/h1decoder.rs => h1/decoder.rs} | 16 +- src/{server/h1disp.rs => h1/dispatcher.rs} | 80 +- src/h1/mod.rs | 9 + src/h1/service.rs | 125 ++ src/{server => }/helpers.rs | 0 src/lib.rs | 9 +- src/{server/message.rs => request.rs} | 34 +- src/server/error.rs | 73 -- src/server/h1.rs | 1289 -------------------- src/server/mod.rs | 21 +- src/server/output.rs | 2 +- tests/test_h1v2.rs | 20 +- 15 files changed, 273 insertions(+), 1623 deletions(-) rename src/{server/settings.rs => config.rs} (89%) rename src/{server/h1codec.rs => h1/codec.rs} (93%) rename src/{server/h1decoder.rs => h1/decoder.rs} (97%) rename src/{server/h1disp.rs => h1/dispatcher.rs} (86%) create mode 100644 src/h1/mod.rs create mode 100644 src/h1/service.rs rename src/{server => }/helpers.rs (100%) rename src/{server/message.rs => request.rs} (87%) delete mode 100644 src/server/error.rs delete mode 100644 src/server/h1.rs diff --git a/src/server/settings.rs b/src/config.rs similarity index 89% rename from src/server/settings.rs rename to src/config.rs index b8b7e51f0..508cd5dde 100644 --- a/src/server/settings.rs +++ b/src/config.rs @@ -12,10 +12,10 @@ use time; use tokio_current_thread::spawn; use tokio_timer::{sleep, Delay}; -use super::message::{Request, RequestPool}; -use super::KeepAlive; use body::Body; use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; +use request::{Request, RequestPool}; +use server::KeepAlive; // "Sun, 06 Nov 1994 08:49:37 GMT".len() const DATE_VALUE_LENGTH: usize = 29; @@ -28,8 +28,6 @@ struct Inner { client_timeout: u64, client_shutdown: u64, ka_enabled: bool, - bytes: Rc, - messages: &'static RequestPool, date: UnsafeCell<(bool, Date)>, } @@ -60,8 +58,6 @@ impl ServiceConfig { ka_enabled, client_timeout, client_shutdown, - bytes: Rc::new(SharedBytesPool::new()), - messages: RequestPool::pool(), date: UnsafeCell::new((false, Date::new())), })) } @@ -83,23 +79,6 @@ impl ServiceConfig { self.0.ka_enabled } - pub(crate) fn get_bytes(&self) -> BytesMut { - self.0.bytes.get_bytes() - } - - pub(crate) fn release_bytes(&self, bytes: BytesMut) { - self.0.bytes.release_bytes(bytes) - } - - pub(crate) fn get_request(&self) -> Request { - RequestPool::get(self.0.messages) - } - - #[doc(hidden)] - pub fn request_pool(&self) -> &'static RequestPool { - self.0.messages - } - fn update_date(&self) { // Unsafe: WorkerSetting is !Sync and !Send unsafe { (*self.0.date.get()).0 = false }; @@ -341,31 +320,6 @@ impl fmt::Write for Date { } } -#[derive(Debug)] -pub(crate) struct SharedBytesPool(RefCell>); - -impl SharedBytesPool { - pub fn new() -> SharedBytesPool { - SharedBytesPool(RefCell::new(VecDeque::with_capacity(128))) - } - - pub fn get_bytes(&self) -> BytesMut { - if let Some(bytes) = self.0.borrow_mut().pop_front() { - bytes - } else { - BytesMut::new() - } - } - - pub fn release_bytes(&self, mut bytes: BytesMut) { - let v = &mut self.0.borrow_mut(); - if v.len() < 128 { - bytes.clear(); - v.push_front(bytes); - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/error.rs b/src/error.rs index ff2388de8..e39dea9b2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -377,62 +377,56 @@ impl ResponseError for cookie::ParseError { } } -/// A set of errors that can occur during parsing multipart streams -#[derive(Fail, Debug)] -pub enum MultipartError { - /// Content-Type header is not found - #[fail(display = "No Content-type header found")] - NoContentType, - /// Can not parse Content-Type header - #[fail(display = "Can not parse Content-Type header")] - ParseContentType, - /// Multipart boundary is not found - #[fail(display = "Multipart boundary is not found")] - Boundary, - /// Multipart stream is incomplete - #[fail(display = "Multipart stream is incomplete")] - Incomplete, - /// Error during field parsing - #[fail(display = "{}", _0)] - Parse(#[cause] ParseError), - /// Payload error - #[fail(display = "{}", _0)] - Payload(#[cause] PayloadError), +#[derive(Debug)] +/// A set of errors that can occur during dispatching http requests +pub enum DispatchError { + /// Service error + // #[fail(display = "Application specific error: {}", _0)] + Service(E), + + /// An `io::Error` that occurred while trying to read or write to a network + /// stream. + // #[fail(display = "IO error: {}", _0)] + Io(io::Error), + + /// Http request parse error. + // #[fail(display = "Parse error: {}", _0)] + Parse(ParseError), + + /// The first request did not complete within the specified timeout. + // #[fail(display = "The first request did not complete within the specified timeout")] + SlowRequestTimeout, + + /// Shutdown timeout + // #[fail(display = "Connection shutdown timeout")] + ShutdownTimeout, + + /// Payload is not consumed + // #[fail(display = "Task is completed but request's payload is not consumed")] + PayloadIsNotConsumed, + + /// Malformed request + // #[fail(display = "Malformed request")] + MalformedRequest, + + /// Internal error + // #[fail(display = "Internal error")] + InternalError, + + /// Unknown error + // #[fail(display = "Unknown error")] + Unknown, } -impl From for MultipartError { - fn from(err: ParseError) -> MultipartError { - MultipartError::Parse(err) +impl From for DispatchError { + fn from(err: ParseError) -> Self { + DispatchError::Parse(err) } } -impl From for MultipartError { - fn from(err: PayloadError) -> MultipartError { - MultipartError::Payload(err) - } -} - -/// Return `BadRequest` for `MultipartError` -impl ResponseError for MultipartError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) - } -} - -/// Error during handling `Expect` header -#[derive(Fail, PartialEq, Debug)] -pub enum ExpectError { - /// Expect header value can not be converted to utf8 - #[fail(display = "Expect header value can not be converted to utf8")] - Encoding, - /// Unknown expect value - #[fail(display = "Unknown expect value")] - UnknownExpect, -} - -impl ResponseError for ExpectError { - fn error_response(&self) -> HttpResponse { - HttpResponse::with_body(StatusCode::EXPECTATION_FAILED, "Unknown Expect") +impl From for DispatchError { + fn from(err: io::Error) -> Self { + DispatchError::Io(err) } } @@ -565,28 +559,6 @@ impl From for ReadlinesError { } } -/// Errors which can occur when attempting to interpret a segment string as a -/// valid path segment. -#[derive(Fail, Debug, PartialEq)] -pub enum UriSegmentError { - /// The segment started with the wrapped invalid character. - #[fail(display = "The segment started with the wrapped invalid character")] - BadStart(char), - /// The segment contained the wrapped invalid character. - #[fail(display = "The segment contained the wrapped invalid character")] - BadChar(char), - /// The segment ended with the wrapped invalid character. - #[fail(display = "The segment ended with the wrapped invalid character")] - BadEnd(char), -} - -/// Return `BadRequest` for `UriSegmentError` -impl ResponseError for UriSegmentError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) - } -} - /// Errors which can occur when attempting to generate resource uri. #[derive(Fail, Debug, PartialEq)] pub enum UrlGenerationError { @@ -610,24 +582,6 @@ impl From for UrlGenerationError { } } -/// Errors which can occur when serving static files. -#[derive(Fail, Debug, PartialEq)] -pub enum StaticFileError { - /// Path is not a directory - #[fail(display = "Path is not a directory. Unable to serve static files")] - IsNotDirectory, - /// Cannot render directory - #[fail(display = "Unable to render directory without index file")] - IsDirectory, -} - -/// Return `NotFound` for `StaticFileError` -impl ResponseError for StaticFileError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::NOT_FOUND) - } -} - /// Helper type that can wrap any error and generate custom response. /// /// In following example any `io::Error` will be converted into "BAD REQUEST" diff --git a/src/server/h1codec.rs b/src/h1/codec.rs similarity index 93% rename from src/server/h1codec.rs rename to src/h1/codec.rs index ea56110d3..011883575 100644 --- a/src/server/h1codec.rs +++ b/src/h1/codec.rs @@ -4,37 +4,45 @@ use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; -use super::h1decoder::{H1Decoder, Message}; -use super::helpers; -use super::message::RequestPool; -use super::output::{ResponseInfo, ResponseLength}; +use super::decoder::H1Decoder; +pub use super::decoder::InMessage; use body::Body; use error::ParseError; +use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::Version; use httpresponse::HttpResponse; +use request::RequestPool; +use server::output::{ResponseInfo, ResponseLength}; -pub(crate) enum OutMessage { +pub enum OutMessage { Response(HttpResponse), Payload(Bytes), } -pub(crate) struct H1Codec { +/// HTTP/1 Codec +pub struct Codec { decoder: H1Decoder, encoder: H1Writer, } -impl H1Codec { - pub fn new(pool: &'static RequestPool) -> Self { - H1Codec { +impl Codec { + /// Create HTTP/1 codec + pub fn new() -> Self { + Codec::with_pool(RequestPool::pool()) + } + + /// Create HTTP/1 codec with request's pool + pub(crate) fn with_pool(pool: &'static RequestPool) -> Self { + Codec { decoder: H1Decoder::new(pool), encoder: H1Writer::new(), } } } -impl Decoder for H1Codec { - type Item = Message; +impl Decoder for Codec { + type Item = InMessage; type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { @@ -42,7 +50,7 @@ impl Decoder for H1Codec { } } -impl Encoder for H1Codec { +impl Encoder for Codec { type Item = OutMessage; type Error = io::Error; diff --git a/src/server/h1decoder.rs b/src/h1/decoder.rs similarity index 97% rename from src/server/h1decoder.rs rename to src/h1/decoder.rs index c6f0974aa..47cc5fdf1 100644 --- a/src/server/h1decoder.rs +++ b/src/h1/decoder.rs @@ -4,10 +4,10 @@ use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; use httparse; -use super::message::{MessageFlags, Request, RequestPool}; use error::ParseError; use http::header::{HeaderName, HeaderValue}; use http::{header, HttpTryFrom, Method, Uri, Version}; +use request::{MessageFlags, Request, RequestPool}; use uri::Url; const MAX_BUFFER_SIZE: usize = 131_072; @@ -19,7 +19,7 @@ pub(crate) struct H1Decoder { } #[derive(Debug)] -pub enum Message { +pub enum InMessage { Message(Request), MessageWithPayload(Request), Chunk(Bytes), @@ -34,14 +34,16 @@ impl H1Decoder { } } - pub fn decode(&mut self, src: &mut BytesMut) -> Result, ParseError> { + pub fn decode( + &mut self, src: &mut BytesMut, + ) -> Result, ParseError> { // read payload if self.decoder.is_some() { match self.decoder.as_mut().unwrap().decode(src)? { - Async::Ready(Some(bytes)) => return Ok(Some(Message::Chunk(bytes))), + Async::Ready(Some(bytes)) => return Ok(Some(InMessage::Chunk(bytes))), Async::Ready(None) => { self.decoder.take(); - return Ok(Some(Message::Eof)); + return Ok(Some(InMessage::Eof)); } Async::NotReady => return Ok(None), } @@ -51,9 +53,9 @@ impl H1Decoder { Async::Ready((msg, decoder)) => { self.decoder = decoder; if self.decoder.is_some() { - Ok(Some(Message::MessageWithPayload(msg))) + Ok(Some(InMessage::MessageWithPayload(msg))) } else { - Ok(Some(Message::Message(msg))) + Ok(Some(InMessage::Message(msg))) } } Async::NotReady => { diff --git a/src/server/h1disp.rs b/src/h1/dispatcher.rs similarity index 86% rename from src/server/h1disp.rs rename to src/h1/dispatcher.rs index b1c2c8a21..6e5672d35 100644 --- a/src/server/h1disp.rs +++ b/src/h1/dispatcher.rs @@ -9,21 +9,20 @@ use actix_net::service::Service; use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; use tokio_codec::Framed; // use tokio_current_thread::spawn; -use tokio_io::AsyncWrite; +use tokio_io::{AsyncRead, AsyncWrite}; // use tokio_timer::Delay; use error::{ParseError, PayloadError}; use payload::{Payload, PayloadStatus, PayloadWriter}; use body::Body; +use error::DispatchError; use httpresponse::HttpResponse; -use super::error::HttpDispatchError; -use super::h1codec::{H1Codec, OutMessage}; -use super::h1decoder::Message; -use super::input::PayloadType; -use super::message::{Request, RequestPool}; -use super::IoStream; +use request::{Request, RequestPool}; +use server::input::PayloadType; + +use super::codec::{Codec, InMessage, OutMessage}; const MAX_PIPELINED_MESSAGES: usize = 16; @@ -41,15 +40,14 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Http1Dispatcher +pub struct Dispatcher where S::Error: Debug + Display, { service: S, flags: Flags, - addr: Option, - framed: Framed, - error: Option>, + framed: Framed, + error: Option>, state: State, payload: Option, @@ -74,26 +72,24 @@ impl State { } } -impl Http1Dispatcher +impl Dispatcher where - T: IoStream, + T: AsyncRead + AsyncWrite, S: Service, S::Error: Debug + Display, { - pub fn new(stream: T, pool: &'static RequestPool, service: S) -> Self { - let addr = stream.peer_addr(); + /// Create http/1 dispatcher. + pub fn new(stream: T, service: S) -> Self { let flags = Flags::FLUSHED; - let codec = H1Codec::new(pool); - let framed = Framed::new(stream, codec); + let framed = Framed::new(stream, Codec::new()); - Http1Dispatcher { + Dispatcher { payload: None, state: State::None, error: None, messages: VecDeque::new(), service, flags, - addr, framed, } } @@ -141,7 +137,7 @@ where } /// Flush stream - fn poll_flush(&mut self) -> Poll<(), HttpDispatchError> { + fn poll_flush(&mut self) -> Poll<(), DispatchError> { if self.flags.contains(Flags::STARTED) && !self.flags.contains(Flags::FLUSHED) { match self.framed.poll_complete() { Ok(Async::NotReady) => Ok(Async::NotReady), @@ -153,7 +149,7 @@ where Ok(Async::Ready(_)) => { // if payload is not consumed we can not use connection if self.payload.is_some() && self.state.is_empty() { - return Err(HttpDispatchError::PayloadIsNotConsumed); + return Err(DispatchError::PayloadIsNotConsumed); } self.flags.insert(Flags::FLUSHED); Ok(Async::Ready(())) @@ -164,7 +160,7 @@ where } } - pub(self) fn poll_handler(&mut self) -> Result<(), HttpDispatchError> { + pub(self) fn poll_handler(&mut self) -> Result<(), DispatchError> { self.poll_io()?; let mut retry = self.can_read(); @@ -185,7 +181,7 @@ where } } Ok(Async::NotReady) => Some(Ok(State::Response(task))), - Err(err) => Some(Err(HttpDispatchError::App(err))), + Err(err) => Some(Err(DispatchError::Service(err))), } } else { None @@ -207,7 +203,7 @@ where Err(err) => { // it is not possible to recover from error // during pipe handling, so just drop connection - Some(Err(HttpDispatchError::App(err))) + Some(Err(DispatchError::Service(err))) } } } @@ -222,7 +218,7 @@ where *item = Some(msg); return Ok(()); } - Err(err) => Some(Err(HttpDispatchError::Io(err))), + Err(err) => Some(Err(DispatchError::Io(err))), } } State::SendResponseWithPayload(ref mut item) => { @@ -236,7 +232,7 @@ where *item = Some((msg, body)); return Ok(()); } - Err(err) => Some(Err(HttpDispatchError::Io(err))), + Err(err) => Some(Err(DispatchError::Io(err))), } } }; @@ -263,14 +259,11 @@ where Ok(()) } - fn one_message(&mut self, msg: Message) -> Result<(), HttpDispatchError> { + fn one_message(&mut self, msg: InMessage) -> Result<(), DispatchError> { self.flags.insert(Flags::STARTED); match msg { - Message::Message(mut msg) => { - // set remote addr - msg.inner_mut().addr = self.addr; - + InMessage::Message(msg) => { // handle request early if self.state.is_empty() { let mut task = self.service.call(msg); @@ -287,17 +280,14 @@ where Err(err) => { error!("Unhandled application error: {}", err); self.client_disconnected(false); - return Err(HttpDispatchError::App(err)); + return Err(DispatchError::Service(err)); } } } else { self.messages.push_back(msg); } } - Message::MessageWithPayload(mut msg) => { - // set remote addr - msg.inner_mut().addr = self.addr; - + InMessage::MessageWithPayload(msg) => { // payload let (ps, pl) = Payload::new(false); *msg.inner.payload.borrow_mut() = Some(pl); @@ -305,24 +295,24 @@ where self.messages.push_back(msg); } - Message::Chunk(chunk) => { + InMessage::Chunk(chunk) => { if let Some(ref mut payload) = self.payload { payload.feed_data(chunk); } else { error!("Internal server error: unexpected payload chunk"); self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); // self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); - self.error = Some(HttpDispatchError::InternalError); + self.error = Some(DispatchError::InternalError); } } - Message::Eof => { + InMessage::Eof => { if let Some(mut payload) = self.payload.take() { payload.feed_eof(); } else { error!("Internal server error: unexpected eof"); self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); // self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); - self.error = Some(HttpDispatchError::InternalError); + self.error = Some(DispatchError::InternalError); } } } @@ -330,7 +320,7 @@ where Ok(()) } - pub(self) fn poll_io(&mut self) -> Result> { + pub(self) fn poll_io(&mut self) -> Result> { let mut updated = false; if self.messages.len() < MAX_PIPELINED_MESSAGES { @@ -359,7 +349,7 @@ where // Malformed requests should be responded with 400 // self.push_response_entry(StatusCode::BAD_REQUEST); self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - self.error = Some(HttpDispatchError::MalformedRequest); + self.error = Some(DispatchError::MalformedRequest); break; } } @@ -370,14 +360,14 @@ where } } -impl Future for Http1Dispatcher +impl Future for Dispatcher where - T: IoStream, + T: AsyncRead + AsyncWrite, S: Service, S::Error: Debug + Display, { type Item = (); - type Error = HttpDispatchError; + type Error = DispatchError; #[inline] fn poll(&mut self) -> Poll<(), Self::Error> { diff --git a/src/h1/mod.rs b/src/h1/mod.rs new file mode 100644 index 000000000..245f2fc23 --- /dev/null +++ b/src/h1/mod.rs @@ -0,0 +1,9 @@ +//! HTTP/1 implementation +mod codec; +mod decoder; +mod dispatcher; +mod service; + +pub use self::codec::Codec; +pub use self::dispatcher::Dispatcher; +pub use self::service::{H1Service, H1ServiceHandler}; diff --git a/src/h1/service.rs b/src/h1/service.rs new file mode 100644 index 000000000..3017a3ef7 --- /dev/null +++ b/src/h1/service.rs @@ -0,0 +1,125 @@ +use std::fmt::{Debug, Display}; +use std::marker::PhantomData; +use std::time::Duration; + +use actix_net::service::{IntoNewService, NewService, Service}; +use futures::future::{ok, FutureResult}; +use futures::{Async, Future, Poll}; +use tokio_io::{AsyncRead, AsyncWrite}; + +use config::ServiceConfig; +use error::DispatchError; +use httpresponse::HttpResponse; +use request::Request; + +use super::dispatcher::Dispatcher; + +/// `NewService` implementation for HTTP1 transport +pub struct H1Service { + srv: S, + cfg: ServiceConfig, + _t: PhantomData, +} + +impl H1Service +where + S: NewService, +{ + /// Create new `HttpService` instance. + pub fn new>(cfg: ServiceConfig, service: F) -> Self { + H1Service { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } +} + +impl NewService for H1Service +where + T: AsyncRead + AsyncWrite, + S: NewService + Clone, + S::Service: Clone, + S::Error: Debug + Display, +{ + type Request = T; + type Response = (); + type Error = DispatchError; + type InitError = S::InitError; + type Service = H1ServiceHandler; + type Future = H1ServiceResponse; + + fn new_service(&self) -> Self::Future { + H1ServiceResponse { + fut: self.srv.new_service(), + cfg: Some(self.cfg.clone()), + _t: PhantomData, + } + } +} + +pub struct H1ServiceResponse { + fut: S::Future, + cfg: Option, + _t: PhantomData, +} + +impl Future for H1ServiceResponse +where + T: AsyncRead + AsyncWrite, + S: NewService, + S::Service: Clone, + S::Error: Debug + Display, +{ + type Item = H1ServiceHandler; + type Error = S::InitError; + + fn poll(&mut self) -> Poll { + let service = try_ready!(self.fut.poll()); + Ok(Async::Ready(H1ServiceHandler::new( + self.cfg.take().unwrap(), + service, + ))) + } +} + +/// `Service` implementation for HTTP1 transport +pub struct H1ServiceHandler { + srv: S, + cfg: ServiceConfig, + _t: PhantomData, +} + +impl H1ServiceHandler +where + S: Service + Clone, + S::Error: Debug + Display, +{ + fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { + H1ServiceHandler { + srv, + cfg, + _t: PhantomData, + } + } +} + +impl Service for H1ServiceHandler +where + T: AsyncRead + AsyncWrite, + S: Service + Clone, + S::Error: Debug + Display, +{ + type Request = T; + type Response = (); + type Error = DispatchError; + type Future = Dispatcher; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.srv.poll_ready().map_err(|e| DispatchError::Service(e)) + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + Dispatcher::new(req, self.srv.clone()) + } +} diff --git a/src/server/helpers.rs b/src/helpers.rs similarity index 100% rename from src/server/helpers.rs rename to src/helpers.rs diff --git a/src/lib.rs b/src/lib.rs index efd566187..ec86f0320 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,6 +135,7 @@ extern crate actix_net; extern crate serde_derive; mod body; +mod config; mod extensions; mod header; mod httpcodes; @@ -142,9 +143,12 @@ mod httpmessage; mod httpresponse; mod json; mod payload; +mod request; mod uri; pub mod error; +pub mod h1; +pub(crate) mod helpers; pub mod server; //pub mod test; //pub mod ws; @@ -152,10 +156,11 @@ pub use body::{Binary, Body}; pub use error::{Error, ResponseError, Result}; pub use extensions::Extensions; pub use httpmessage::HttpMessage; -//pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use json::Json; -pub use server::Request; +pub use request::Request; + +pub use self::config::{ServiceConfig, ServiceConfigBuilder}; pub mod dev { //! The `actix-web` prelude for library developers diff --git a/src/server/message.rs b/src/request.rs similarity index 87% rename from src/server/message.rs rename to src/request.rs index c39302bab..a75fda3a0 100644 --- a/src/server/message.rs +++ b/src/request.rs @@ -30,7 +30,6 @@ pub(crate) struct InnerRequest { pub(crate) flags: Cell, pub(crate) headers: HeaderMap, pub(crate) extensions: RefCell, - pub(crate) addr: Option, pub(crate) payload: RefCell>, pub(crate) stream_extensions: Option>, pool: &'static RequestPool, @@ -65,8 +64,13 @@ impl HttpMessage for Request { } impl Request { - /// Create new RequestContext instance - pub(crate) fn new(pool: &'static RequestPool) -> Request { + /// Create new Request instance + pub fn new() -> Request { + Request::with_pool(RequestPool::pool()) + } + + /// Create new Request instance with pool + pub(crate) fn with_pool(pool: &'static RequestPool) -> Request { Request { inner: Rc::new(InnerRequest { pool, @@ -75,7 +79,6 @@ impl Request { version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), flags: Cell::new(MessageFlags::empty()), - addr: None, payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), stream_extensions: None, @@ -134,14 +137,6 @@ impl Request { &mut self.inner_mut().headers } - /// Peer socket address - /// - /// Peer address is actual socket address, if proxy is used in front of - /// actix http server, then peer address would be address of this proxy. - pub fn peer_addr(&self) -> Option { - self.inner().addr - } - /// Checks if a connection should be kept alive. #[inline] pub fn keep_alive(&self) -> bool { @@ -170,12 +165,6 @@ impl Request { self.inner().method == Method::CONNECT } - /// Io stream extensions - #[inline] - pub fn stream_extensions(&self) -> Option<&Extensions> { - self.inner().stream_extensions.as_ref().map(|e| e.as_ref()) - } - pub(crate) fn clone(&self) -> Self { Request { inner: self.inner.clone(), @@ -213,7 +202,8 @@ impl fmt::Debug for Request { } } -pub struct RequestPool(RefCell>>); +/// Request's objects pool +pub(crate) struct RequestPool(RefCell>>); thread_local!(static POOL: &'static RequestPool = RequestPool::create()); @@ -223,16 +213,18 @@ impl RequestPool { Box::leak(Box::new(pool)) } - pub(crate) fn pool() -> &'static RequestPool { + /// Get default request's pool + pub fn pool() -> &'static RequestPool { POOL.with(|p| *p) } + /// Get Request object #[inline] pub fn get(pool: &'static RequestPool) -> Request { if let Some(msg) = pool.0.borrow_mut().pop_front() { Request { inner: msg } } else { - Request::new(pool) + Request::with_pool(pool) } } diff --git a/src/server/error.rs b/src/server/error.rs deleted file mode 100644 index 7d5c67d1e..000000000 --- a/src/server/error.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::fmt::{Debug, Display}; -use std::io; - -use futures::{Async, Poll}; - -use error::{Error, ParseError}; -use http::{StatusCode, Version}; - -/// Errors produced by `AcceptorError` service. -#[derive(Debug)] -pub enum AcceptorError { - /// The inner service error - Service(T), - - /// Io specific error - Io(io::Error), - - /// The request did not complete within the specified timeout. - Timeout, -} - -#[derive(Debug)] -/// A set of errors that can occur during dispatching http requests -pub enum HttpDispatchError { - /// Application error - // #[fail(display = "Application specific error: {}", _0)] - App(E), - - /// An `io::Error` that occurred while trying to read or write to a network - /// stream. - // #[fail(display = "IO error: {}", _0)] - Io(io::Error), - - /// Http request parse error. - // #[fail(display = "Parse error: {}", _0)] - Parse(ParseError), - - /// The first request did not complete within the specified timeout. - // #[fail(display = "The first request did not complete within the specified timeout")] - SlowRequestTimeout, - - /// Shutdown timeout - // #[fail(display = "Connection shutdown timeout")] - ShutdownTimeout, - - /// Payload is not consumed - // #[fail(display = "Task is completed but request's payload is not consumed")] - PayloadIsNotConsumed, - - /// Malformed request - // #[fail(display = "Malformed request")] - MalformedRequest, - - /// Internal error - // #[fail(display = "Internal error")] - InternalError, - - /// Unknown error - // #[fail(display = "Unknown error")] - Unknown, -} - -impl From for HttpDispatchError { - fn from(err: ParseError) -> Self { - HttpDispatchError::Parse(err) - } -} - -impl From for HttpDispatchError { - fn from(err: io::Error) -> Self { - HttpDispatchError::Io(err) - } -} diff --git a/src/server/h1.rs b/src/server/h1.rs deleted file mode 100644 index e2b4bf45e..000000000 --- a/src/server/h1.rs +++ /dev/null @@ -1,1289 +0,0 @@ -use std::collections::VecDeque; -use std::net::{Shutdown, SocketAddr}; -use std::time::{Duration, Instant}; - -use bytes::BytesMut; -use futures::{Async, Future, Poll}; -use tokio_current_thread::spawn; -use tokio_timer::Delay; - -use error::{Error, ParseError, PayloadError}; -use http::{StatusCode, Version}; -use payload::{Payload, PayloadStatus, PayloadWriter}; - -use super::error::{HttpDispatchError, ServerError}; -use super::h1decoder::{H1Decoder, Message}; -use super::h1writer::H1Writer; -use super::handler::{HttpHandler, HttpHandlerTask, HttpHandlerTaskFut}; -use super::input::PayloadType; -use super::message::Request; -use super::settings::ServiceConfig; -use super::{IoStream, Writer}; - -const MAX_PIPELINED_MESSAGES: usize = 16; - -bitflags! { - pub struct Flags: u8 { - const STARTED = 0b0000_0001; - const KEEPALIVE_ENABLED = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; - const SHUTDOWN = 0b0000_1000; - const READ_DISCONNECTED = 0b0001_0000; - const WRITE_DISCONNECTED = 0b0010_0000; - const POLLED = 0b0100_0000; - const FLUSHED = 0b1000_0000; - } -} - -/// Dispatcher for HTTP/1.1 protocol -pub struct Http1Dispatcher { - flags: Flags, - settings: ServiceConfig, - addr: Option, - stream: H1Writer, - decoder: H1Decoder, - payload: Option, - buf: BytesMut, - tasks: VecDeque>, - error: Option>, - ka_expire: Instant, - ka_timer: Option, -} - -enum Entry { - Task(H::Task), - Error(Box), -} - -impl Entry { - fn into_task(self) -> H::Task { - match self { - Entry::Task(task) => task, - Entry::Error(_) => panic!(), - } - } - fn disconnected(&mut self) { - match *self { - Entry::Task(ref mut task) => task.disconnected(), - Entry::Error(ref mut task) => task.disconnected(), - } - } - fn poll_io(&mut self, io: &mut Writer) -> Poll { - match *self { - Entry::Task(ref mut task) => task.poll_io(io), - Entry::Error(ref mut task) => task.poll_io(io), - } - } - fn poll_completed(&mut self) -> Poll<(), Error> { - match *self { - Entry::Task(ref mut task) => task.poll_completed(), - Entry::Error(ref mut task) => task.poll_completed(), - } - } -} - -impl Http1Dispatcher -where - T: IoStream, - H: HttpHandler + 'static, -{ - pub fn new( - settings: ServiceConfig, stream: T, buf: BytesMut, is_eof: bool, - keepalive_timer: Option, - ) -> Self { - let addr = stream.peer_addr(); - let (ka_expire, ka_timer) = if let Some(delay) = keepalive_timer { - (delay.deadline(), Some(delay)) - } else if let Some(delay) = settings.keep_alive_timer() { - (delay.deadline(), Some(delay)) - } else { - (settings.now(), None) - }; - - let flags = if is_eof { - Flags::READ_DISCONNECTED | Flags::FLUSHED - } else if settings.keep_alive_enabled() { - Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED | Flags::FLUSHED - } else { - Flags::empty() - }; - - Http1Dispatcher { - stream: H1Writer::new(stream, settings.clone()), - decoder: H1Decoder::new(settings.request_pool()), - payload: None, - tasks: VecDeque::new(), - error: None, - flags, - addr, - buf, - settings, - ka_timer, - ka_expire, - } - } - - pub(crate) fn for_error( - settings: ServiceConfig, stream: T, status: StatusCode, - mut keepalive_timer: Option, buf: BytesMut, - ) -> Self { - if let Some(deadline) = settings.client_timer_expire() { - let _ = keepalive_timer.as_mut().map(|delay| delay.reset(deadline)); - } - - let mut disp = Http1Dispatcher { - flags: Flags::STARTED | Flags::READ_DISCONNECTED | Flags::FLUSHED, - stream: H1Writer::new(stream, settings.clone()), - decoder: H1Decoder::new(settings.request_pool()), - payload: None, - tasks: VecDeque::new(), - error: None, - addr: None, - ka_timer: keepalive_timer, - ka_expire: settings.now(), - buf, - settings, - }; - disp.push_response_entry(status); - disp - } - - #[inline] - pub fn settings(&self) -> &ServiceConfig { - &self.settings - } - - #[inline] - pub(crate) fn io(&mut self) -> &mut T { - self.stream.get_mut() - } - - #[inline] - fn can_read(&self) -> bool { - if self.flags.contains(Flags::READ_DISCONNECTED) { - return false; - } - - if let Some(ref info) = self.payload { - info.need_read() == PayloadStatus::Read - } else { - true - } - } - - // if checked is set to true, delay disconnect until all tasks have finished. - fn client_disconnected(&mut self, checked: bool) { - self.flags.insert(Flags::READ_DISCONNECTED); - if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete); - } - - if !checked || self.tasks.is_empty() { - self.flags - .insert(Flags::WRITE_DISCONNECTED | Flags::FLUSHED); - self.stream.disconnected(); - - // notify all tasks - for mut task in self.tasks.drain(..) { - task.disconnected(); - match task.poll_completed() { - Ok(Async::NotReady) => { - // spawn not completed task, it does not require access to io - // at this point - spawn(HttpHandlerTaskFut::new(task.into_task())); - } - Ok(Async::Ready(_)) => (), - Err(err) => { - error!("Unhandled application error: {}", err); - } - } - } - } - } - - #[inline] - pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { - // check connection keep-alive - self.poll_keep_alive()?; - - // shutdown - if self.flags.contains(Flags::SHUTDOWN) { - if self.flags.contains(Flags::WRITE_DISCONNECTED) { - return Ok(Async::Ready(())); - } - return self.poll_flush(true); - } - - // process incoming requests - if !self.flags.contains(Flags::WRITE_DISCONNECTED) { - self.poll_handler()?; - - // flush stream - self.poll_flush(false)?; - - // deal with keep-alive and stream eof (client-side write shutdown) - if self.tasks.is_empty() && self.flags.contains(Flags::FLUSHED) { - // handle stream eof - if self - .flags - .intersects(Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED) - { - return Ok(Async::Ready(())); - } - // no keep-alive - if self.flags.contains(Flags::STARTED) - && (!self.flags.contains(Flags::KEEPALIVE_ENABLED) - || !self.flags.contains(Flags::KEEPALIVE)) - { - self.flags.insert(Flags::SHUTDOWN); - return self.poll(); - } - } - Ok(Async::NotReady) - } else if let Some(err) = self.error.take() { - Err(err) - } else { - Ok(Async::Ready(())) - } - } - - /// Flush stream - fn poll_flush(&mut self, shutdown: bool) -> Poll<(), HttpDispatchError> { - if shutdown || self.flags.contains(Flags::STARTED) { - match self.stream.poll_completed(shutdown) { - Ok(Async::NotReady) => { - // mark stream - if !self.stream.flushed() { - self.flags.remove(Flags::FLUSHED); - } - Ok(Async::NotReady) - } - Err(err) => { - debug!("Error sending data: {}", err); - self.client_disconnected(false); - Err(err.into()) - } - Ok(Async::Ready(_)) => { - // if payload is not consumed we can not use connection - if self.payload.is_some() && self.tasks.is_empty() { - return Err(HttpDispatchError::PayloadIsNotConsumed); - } - self.flags.insert(Flags::FLUSHED); - Ok(Async::Ready(())) - } - } - } else { - Ok(Async::Ready(())) - } - } - - /// keep-alive timer. returns `true` is keep-alive, otherwise drop - fn poll_keep_alive(&mut self) -> Result<(), HttpDispatchError> { - if let Some(ref mut timer) = self.ka_timer { - match timer.poll() { - Ok(Async::Ready(_)) => { - if timer.deadline() >= self.ka_expire { - // check for any outstanding request handling - if self.tasks.is_empty() { - // if we get timer during shutdown, just drop connection - if self.flags.contains(Flags::SHUTDOWN) { - let io = self.stream.get_mut(); - let _ = IoStream::set_linger( - io, - Some(Duration::from_secs(0)), - ); - let _ = IoStream::shutdown(io, Shutdown::Both); - return Err(HttpDispatchError::ShutdownTimeout); - } else if !self.flags.contains(Flags::STARTED) { - // timeout on first request (slow request) return 408 - trace!("Slow request timeout"); - self.flags - .insert(Flags::STARTED | Flags::READ_DISCONNECTED); - self.tasks.push_back(Entry::Error(ServerError::err( - Version::HTTP_11, - StatusCode::REQUEST_TIMEOUT, - ))); - } else { - trace!("Keep-alive timeout, close connection"); - self.flags.insert(Flags::SHUTDOWN); - - // start shutdown timer - if let Some(deadline) = - self.settings.client_shutdown_timer() - { - timer.reset(deadline) - } else { - return Ok(()); - } - } - } else if let Some(deadline) = self.settings.keep_alive_expire() - { - timer.reset(deadline) - } - } else { - timer.reset(self.ka_expire) - } - } - Ok(Async::NotReady) => (), - Err(e) => { - error!("Timer error {:?}", e); - return Err(HttpDispatchError::Unknown); - } - } - } - - Ok(()) - } - - #[inline] - /// read data from the stream - pub(self) fn poll_io(&mut self) -> Result> { - if !self.flags.contains(Flags::POLLED) { - self.flags.insert(Flags::POLLED); - if !self.buf.is_empty() { - let updated = self.parse()?; - return Ok(updated); - } - } - - // read io from socket - let mut updated = false; - if self.can_read() && self.tasks.len() < MAX_PIPELINED_MESSAGES { - match self.stream.get_mut().read_available(&mut self.buf) { - Ok(Async::Ready((read_some, disconnected))) => { - if read_some && self.parse()? { - updated = true; - } - if disconnected { - self.client_disconnected(true); - } - } - Ok(Async::NotReady) => (), - Err(err) => { - self.client_disconnected(false); - return Err(err.into()); - } - } - } - Ok(updated) - } - - pub(self) fn poll_handler(&mut self) -> Result<(), HttpDispatchError> { - self.poll_io()?; - let mut retry = self.can_read(); - - // process first pipelined response, only first task can do io operation in http/1 - while !self.tasks.is_empty() { - match self.tasks[0].poll_io(&mut self.stream) { - Ok(Async::Ready(ready)) => { - // override keep-alive state - if self.stream.keepalive() { - self.flags.insert(Flags::KEEPALIVE); - } else { - self.flags.remove(Flags::KEEPALIVE); - } - // prepare stream for next response - self.stream.reset(); - - let task = self.tasks.pop_front().unwrap(); - if !ready { - // task is done with io operations but still needs to do more work - spawn(HttpHandlerTaskFut::new(task.into_task())); - } - } - Ok(Async::NotReady) => { - // check if we need timer - if self.ka_timer.is_some() && self.stream.upgrade() { - self.ka_timer.take(); - } - - // if read-backpressure is enabled and we consumed some data. - // we may read more dataand retry - if !retry && self.can_read() && self.poll_io()? { - retry = self.can_read(); - continue; - } - break; - } - Err(err) => { - error!("Unhandled error1: {}", err); - // it is not possible to recover from error - // during pipe handling, so just drop connection - self.client_disconnected(false); - return Err(HttpDispatchError::App(err)); - } - } - } - - // check in-flight messages. all tasks must be alive, - // they need to produce response. if app returned error - // and we can not continue processing incoming requests. - let mut idx = 1; - while idx < self.tasks.len() { - let stop = match self.tasks[idx].poll_completed() { - Ok(Async::NotReady) => false, - Ok(Async::Ready(_)) => true, - Err(err) => { - self.error = Some(HttpDispatchError::App(err)); - true - } - }; - if stop { - // error in task handling or task is completed, - // so no response for this task which means we can not read more requests - // because pipeline sequence is broken. - // but we can safely complete existing tasks - self.flags.insert(Flags::READ_DISCONNECTED); - - for mut task in self.tasks.drain(idx..) { - task.disconnected(); - match task.poll_completed() { - Ok(Async::NotReady) => { - // spawn not completed task, it does not require access to io - // at this point - spawn(HttpHandlerTaskFut::new(task.into_task())); - } - Ok(Async::Ready(_)) => (), - Err(err) => { - error!("Unhandled application error: {}", err); - } - } - } - break; - } else { - idx += 1; - } - } - - Ok(()) - } - - fn push_response_entry(&mut self, status: StatusCode) { - self.tasks - .push_back(Entry::Error(ServerError::err(Version::HTTP_11, status))); - } - - fn handle_message( - &mut self, mut msg: Request, payload: bool, - ) -> Result<(), HttpDispatchError> { - self.flags.insert(Flags::STARTED); - - if payload { - let (ps, pl) = Payload::new(false); - *msg.inner.payload.borrow_mut() = Some(pl); - self.payload = Some(PayloadType::new(&msg.inner.headers, ps)); - } - - // stream extensions - msg.inner_mut().stream_extensions = self.stream.get_mut().extensions(); - - // set remote addr - msg.inner_mut().addr = self.addr; - - // search handler for request - match self.settings.handler().handle(msg) { - Ok(mut task) => { - if self.tasks.is_empty() { - match task.poll_io(&mut self.stream) { - Ok(Async::Ready(ready)) => { - // override keep-alive state - if self.stream.keepalive() { - self.flags.insert(Flags::KEEPALIVE); - } else { - self.flags.remove(Flags::KEEPALIVE); - } - // prepare stream for next response - self.stream.reset(); - - if !ready { - // task is done with io operations - // but still needs to do more work - spawn(HttpHandlerTaskFut::new(task)); - } - } - Ok(Async::NotReady) => (), - Err(err) => { - error!("Unhandled error: {}", err); - self.client_disconnected(false); - return Err(HttpDispatchError::App(err)); - } - } - } else { - self.tasks.push_back(Entry::Task(task)); - } - } - Err(_) => { - // handler is not found - self.push_response_entry(StatusCode::NOT_FOUND); - } - } - Ok(()) - } - - pub(self) fn parse(&mut self) -> Result> { - let mut updated = false; - - 'outer: loop { - match self.decoder.decode(&mut self.buf) { - Ok(Some(Message::Message(msg))) => { - updated = true; - self.handle_message(msg, false)?; - } - Ok(Some(Message::MessageWithPayload(msg))) => { - updated = true; - self.handle_message(msg, true)?; - } - Ok(Some(Message::Chunk(chunk))) => { - updated = true; - if let Some(ref mut payload) = self.payload { - payload.feed_data(chunk); - } else { - error!("Internal server error: unexpected payload chunk"); - self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); - self.error = Some(HttpDispatchError::InternalError); - break; - } - } - Ok(Some(Message::Eof)) => { - updated = true; - if let Some(mut payload) = self.payload.take() { - payload.feed_eof(); - } else { - error!("Internal server error: unexpected eof"); - self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); - self.error = Some(HttpDispatchError::InternalError); - break; - } - } - Ok(None) => { - if self.flags.contains(Flags::READ_DISCONNECTED) { - self.client_disconnected(true); - } - break; - } - Err(e) => { - if let Some(mut payload) = self.payload.take() { - let e = match e { - ParseError::Io(e) => PayloadError::Io(e), - _ => PayloadError::EncodingCorrupted, - }; - payload.set_error(e); - } - - // Malformed requests should be responded with 400 - self.push_response_entry(StatusCode::BAD_REQUEST); - self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - self.error = Some(HttpDispatchError::MalformedRequest); - break; - } - } - } - - if self.ka_timer.is_some() && updated { - if let Some(expire) = self.settings.keep_alive_expire() { - self.ka_expire = expire; - } - } - Ok(updated) - } -} - -#[cfg(test)] -mod tests { - use std::net::Shutdown; - use std::{cmp, io, time}; - - use actix::System; - use bytes::{Buf, Bytes, BytesMut}; - use futures::future; - use http::{Method, Version}; - use tokio_io::{AsyncRead, AsyncWrite}; - - use super::*; - use application::{App, HttpApplication}; - use error::ParseError; - use httpmessage::HttpMessage; - use server::h1decoder::Message; - use server::handler::IntoHttpHandler; - use server::settings::{ServerSettings, ServiceConfig}; - use server::{KeepAlive, Request}; - - fn wrk_settings() -> ServiceConfig { - ServiceConfig::::new( - App::new().into_handler(), - KeepAlive::Os, - 5000, - 2000, - ServerSettings::default(), - ) - } - - impl Message { - fn message(self) -> Request { - match self { - Message::Message(msg) => msg, - Message::MessageWithPayload(msg) => msg, - _ => panic!("error"), - } - } - fn is_payload(&self) -> bool { - match *self { - Message::MessageWithPayload(_) => true, - _ => panic!("error"), - } - } - fn chunk(self) -> Bytes { - match self { - Message::Chunk(chunk) => chunk, - _ => panic!("error"), - } - } - fn eof(&self) -> bool { - match *self { - Message::Eof => true, - _ => false, - } - } - } - - macro_rules! parse_ready { - ($e:expr) => {{ - let settings = wrk_settings(); - match H1Decoder::new(settings.request_pool()).decode($e) { - Ok(Some(msg)) => msg.message(), - Ok(_) => unreachable!("Eof during parsing http request"), - Err(err) => unreachable!("Error during parsing http request: {:?}", err), - } - }}; - } - - macro_rules! expect_parse_err { - ($e:expr) => {{ - let settings = wrk_settings(); - - match H1Decoder::new(settings.request_pool()).decode($e) { - Err(err) => match err { - ParseError::Io(_) => unreachable!("Parse error expected"), - _ => (), - }, - _ => unreachable!("Error expected"), - } - }}; - } - - struct Buffer { - buf: Bytes, - err: Option, - } - - impl Buffer { - fn new(data: &'static str) -> Buffer { - Buffer { - buf: Bytes::from(data), - err: None, - } - } - } - - impl AsyncRead for Buffer {} - impl io::Read for Buffer { - fn read(&mut self, dst: &mut [u8]) -> Result { - if self.buf.is_empty() { - if self.err.is_some() { - Err(self.err.take().unwrap()) - } else { - Err(io::Error::new(io::ErrorKind::WouldBlock, "")) - } - } else { - let size = cmp::min(self.buf.len(), dst.len()); - let b = self.buf.split_to(size); - dst[..size].copy_from_slice(&b); - Ok(size) - } - } - } - - impl IoStream for Buffer { - fn shutdown(&mut self, _: Shutdown) -> io::Result<()> { - Ok(()) - } - fn set_nodelay(&mut self, _: bool) -> io::Result<()> { - Ok(()) - } - fn set_linger(&mut self, _: Option) -> io::Result<()> { - Ok(()) - } - fn set_keepalive(&mut self, _: Option) -> io::Result<()> { - Ok(()) - } - } - impl io::Write for Buffer { - fn write(&mut self, buf: &[u8]) -> io::Result { - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } - } - impl AsyncWrite for Buffer { - fn shutdown(&mut self) -> Poll<(), io::Error> { - Ok(Async::Ready(())) - } - fn write_buf(&mut self, _: &mut B) -> Poll { - Ok(Async::NotReady) - } - } - - #[test] - fn test_req_parse_err() { - let mut sys = System::new("test"); - let _ = sys.block_on(future::lazy(|| { - let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); - let readbuf = BytesMut::new(); - let settings = wrk_settings(); - - let mut h1 = - Http1Dispatcher::new(settings.clone(), buf, readbuf, false, None); - assert!(h1.poll_io().is_ok()); - assert!(h1.poll_io().is_ok()); - assert!(h1.flags.contains(Flags::READ_DISCONNECTED)); - assert_eq!(h1.tasks.len(), 1); - future::ok::<_, ()>(()) - })); - } - - #[test] - fn test_parse() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(settings.request_pool()); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_parse_partial() { - let mut buf = BytesMut::from("PUT /test HTTP/1"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(settings.request_pool()); - match reader.decode(&mut buf) { - Ok(None) => (), - _ => unreachable!("Error"), - } - - buf.extend(b".1\r\n\r\n"); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::PUT); - assert_eq!(req.path(), "/test"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_parse_post() { - let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(settings.request_pool()); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_10); - assert_eq!(*req.method(), Method::POST); - assert_eq!(req.path(), "/test2"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_parse_body() { - let mut buf = - BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(settings.request_pool()); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"body" - ); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_parse_body_crlf() { - let mut buf = - BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(settings.request_pool()); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"body" - ); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_parse_partial_eof() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let settings = wrk_settings(); - let mut reader = H1Decoder::new(settings.request_pool()); - assert!(reader.decode(&mut buf).unwrap().is_none()); - - buf.extend(b"\r\n"); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_headers_split_field() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(settings.request_pool()); - assert!{ reader.decode(&mut buf).unwrap().is_none() } - - buf.extend(b"t"); - assert!{ reader.decode(&mut buf).unwrap().is_none() } - - buf.extend(b"es"); - assert!{ reader.decode(&mut buf).unwrap().is_none() } - - buf.extend(b"t: value\r\n\r\n"); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_headers_multi_value() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - Set-Cookie: c1=cookie1\r\n\ - Set-Cookie: c2=cookie2\r\n\r\n", - ); - let settings = wrk_settings(); - let mut reader = H1Decoder::new(settings.request_pool()); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - let req = msg.message(); - - let val: Vec<_> = req - .headers() - .get_all("Set-Cookie") - .iter() - .map(|v| v.to_str().unwrap().to_owned()) - .collect(); - assert_eq!(val[0], "c1=cookie1"); - assert_eq!(val[1], "c2=cookie2"); - } - - #[test] - fn test_conn_default_1_0() { - let mut buf = BytesMut::from("GET /test HTTP/1.0\r\n\r\n"); - let req = parse_ready!(&mut buf); - - assert!(!req.keep_alive()); - } - - #[test] - fn test_conn_default_1_1() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); - let req = parse_ready!(&mut buf); - - assert!(req.keep_alive()); - } - - #[test] - fn test_conn_close() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: close\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(!req.keep_alive()); - } - - #[test] - fn test_conn_close_1_0() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.0\r\n\ - connection: close\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(!req.keep_alive()); - } - - #[test] - fn test_conn_keep_alive_1_0() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.0\r\n\ - connection: keep-alive\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.keep_alive()); - } - - #[test] - fn test_conn_keep_alive_1_1() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: keep-alive\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.keep_alive()); - } - - #[test] - fn test_conn_other_1_0() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.0\r\n\ - connection: other\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(!req.keep_alive()); - } - - #[test] - fn test_conn_other_1_1() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: other\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.keep_alive()); - } - - #[test] - fn test_conn_upgrade() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - upgrade: websockets\r\n\ - connection: upgrade\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.upgrade()); - } - - #[test] - fn test_conn_upgrade_connect_method() { - let mut buf = BytesMut::from( - "CONNECT /test HTTP/1.1\r\n\ - content-type: text/plain\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.upgrade()); - } - - #[test] - fn test_request_chunked() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - if let Ok(val) = req.chunked() { - assert!(val); - } else { - unreachable!("Error"); - } - - // type in chunked - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chnked\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - if let Ok(val) = req.chunked() { - assert!(!val); - } else { - unreachable!("Error"); - } - } - - #[test] - fn test_headers_content_length_err_1() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - content-length: line\r\n\r\n", - ); - - expect_parse_err!(&mut buf) - } - - #[test] - fn test_headers_content_length_err_2() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - content-length: -1\r\n\r\n", - ); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_invalid_header() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - test line\r\n\r\n", - ); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_invalid_name() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - test[]: line\r\n\r\n", - ); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_http_request_bad_status_line() { - let mut buf = BytesMut::from("getpath \r\n\r\n"); - expect_parse_err!(&mut buf); - } - - #[test] - fn test_http_request_upgrade() { - let settings = wrk_settings(); - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: upgrade\r\n\ - upgrade: websocket\r\n\r\n\ - some raw data", - ); - let mut reader = H1Decoder::new(settings.request_pool()); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); - assert!(!req.keep_alive()); - assert!(req.upgrade()); - assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"some raw data" - ); - } - - #[test] - fn test_http_request_parser_utf8() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - x-test: тест\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert_eq!( - req.headers().get("x-test").unwrap().as_bytes(), - "тест".as_bytes() - ); - } - - #[test] - fn test_http_request_parser_two_slashes() { - let mut buf = BytesMut::from("GET //path HTTP/1.1\r\n\r\n"); - let req = parse_ready!(&mut buf); - - assert_eq!(req.path(), "//path"); - } - - #[test] - fn test_http_request_parser_bad_method() { - let mut buf = BytesMut::from("!12%()+=~$ /get HTTP/1.1\r\n\r\n"); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_http_request_parser_bad_version() { - let mut buf = BytesMut::from("GET //get HT/11\r\n\r\n"); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_http_request_chunked_payload() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let settings = wrk_settings(); - let mut reader = H1Decoder::new(settings.request_pool()); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); - assert!(req.chunked().unwrap()); - - buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); - assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"data" - ); - assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"line" - ); - assert!(reader.decode(&mut buf).unwrap().unwrap().eof()); - } - - #[test] - fn test_http_request_chunked_payload_and_next_message() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let settings = wrk_settings(); - let mut reader = H1Decoder::new(settings.request_pool()); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); - assert!(req.chunked().unwrap()); - - buf.extend( - b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ - POST /test2 HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n" - .iter(), - ); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"data"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"line"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.eof()); - - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req2 = msg.message(); - assert!(req2.chunked().unwrap()); - assert_eq!(*req2.method(), Method::POST); - assert!(req2.chunked().unwrap()); - } - - #[test] - fn test_http_request_chunked_payload_chunks() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(settings.request_pool()); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); - assert!(req.chunked().unwrap()); - - buf.extend(b"4\r\n1111\r\n"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"1111"); - - buf.extend(b"4\r\ndata\r"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"data"); - - buf.extend(b"\n4"); - assert!(reader.decode(&mut buf).unwrap().is_none()); - - buf.extend(b"\r"); - assert!(reader.decode(&mut buf).unwrap().is_none()); - buf.extend(b"\n"); - assert!(reader.decode(&mut buf).unwrap().is_none()); - - buf.extend(b"li"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"li"); - - //trailers - //buf.feed_data("test: test\r\n"); - //not_ready!(reader.parse(&mut buf, &mut readbuf)); - - buf.extend(b"ne\r\n0\r\n"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"ne"); - assert!(reader.decode(&mut buf).unwrap().is_none()); - - buf.extend(b"\r\n"); - assert!(reader.decode(&mut buf).unwrap().unwrap().eof()); - } - - #[test] - fn test_parse_chunked_payload_chunk_extension() { - let mut buf = BytesMut::from( - &"GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n"[..], - ); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(settings.request_pool()); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - assert!(msg.message().chunked().unwrap()); - - buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") - let chunk = reader.decode(&mut buf).unwrap().unwrap().chunk(); - assert_eq!(chunk, Bytes::from_static(b"data")); - let chunk = reader.decode(&mut buf).unwrap().unwrap().chunk(); - assert_eq!(chunk, Bytes::from_static(b"line")); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.eof()); - } -} diff --git a/src/server/mod.rs b/src/server/mod.rs index be172e646..068094c2a 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -117,30 +117,11 @@ use tokio_tcp::TcpStream; pub use actix_net::server::{PauseServer, ResumeServer, StopServer}; -mod error; -// pub(crate) mod h1; -#[doc(hidden)] -pub mod h1codec; -#[doc(hidden)] -pub mod h1decoder; -pub(crate) mod helpers; pub(crate) mod input; -pub(crate) mod message; pub(crate) mod output; -// pub(crate) mod service; -pub(crate) mod settings; - -pub use self::error::{AcceptorError, HttpDispatchError}; -pub use self::message::Request; #[doc(hidden)] -pub mod h1disp; - -#[doc(hidden)] -pub use self::settings::{ServiceConfig, ServiceConfigBuilder}; - -#[doc(hidden)] -pub use self::helpers::write_content_length; +pub use super::helpers::write_content_length; use body::Binary; use extensions::Extensions; diff --git a/src/server/output.rs b/src/server/output.rs index 143ba4029..f20bd3266 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -13,10 +13,10 @@ use flate2::Compression; use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; -use super::message::InnerRequest; use body::{Binary, Body}; use header::ContentEncoding; use httpresponse::HttpResponse; +use request::InnerRequest; // #[derive(Debug)] // pub(crate) struct RequestInfo { diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs index 77b6d202f..e32481bc2 100644 --- a/tests/test_h1v2.rs +++ b/tests/test_h1v2.rs @@ -12,9 +12,8 @@ use actix_net::service::{IntoNewService, IntoService}; use actix_web::{client, test}; use futures::future; -use actix_http::server::h1disp::Http1Dispatcher; -use actix_http::server::{KeepAlive, ServiceConfig}; -use actix_http::{Error, HttpResponse}; +use actix_http::server::KeepAlive; +use actix_http::{h1, Error, HttpResponse, ServiceConfig}; #[test] fn test_h1_v2() { @@ -30,17 +29,10 @@ fn test_h1_v2() { .server_address(addr) .finish(); - (move |io| { - let pool = settings.request_pool(); - Http1Dispatcher::new( - io, - pool, - (|req| { - println!("REQ: {:?}", req); - future::ok::<_, Error>(HttpResponse::Ok().finish()) - }).into_service(), - ) - }).into_new_service() + h1::H1Service::new(settings, |req| { + println!("REQ: {:?}", req); + future::ok::<_, Error>(HttpResponse::Ok().finish()) + }) }).unwrap() .run(); }); From 829dbae609205d9245c927c6234d346957a1f94f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 21:14:18 -0700 Subject: [PATCH 005/208] cleanups and tests --- Cargo.toml | 2 +- src/body.rs | 1 - src/config.rs | 17 +- src/error.rs | 38 +- src/h1/codec.rs | 5 +- src/h1/decoder.rs | 666 ++++++++++++++++++++- src/h1/dispatcher.rs | 14 +- src/h1/mod.rs | 2 +- src/h1/service.rs | 4 +- src/header/common/accept.rs | 18 +- src/header/common/accept_charset.rs | 18 +- src/header/common/accept_language.rs | 12 +- src/header/common/allow.rs | 19 +- src/header/common/cache_control.rs | 10 +- src/header/common/content_disposition.rs | 9 +- src/header/common/content_language.rs | 12 +- src/header/common/content_type.rs | 10 +- src/header/common/date.rs | 4 +- src/header/common/etag.rs | 8 +- src/header/common/expires.rs | 4 +- src/header/common/if_match.rs | 8 +- src/header/common/if_modified_since.rs | 4 +- src/header/common/if_none_match.rs | 8 +- src/header/common/if_range.rs | 14 +- src/header/common/if_unmodified_since.rs | 4 +- src/header/common/last_modified.rs | 4 +- src/httpmessage.rs | 6 +- src/httpresponse.rs | 121 ++-- src/json.rs | 47 +- src/lib.rs | 29 +- src/request.rs | 25 +- src/server/mod.rs | 17 +- src/server/output.rs | 1 + src/test.rs | 712 +++++++---------------- tests/test_h1v2.rs | 1 - 35 files changed, 1063 insertions(+), 811 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 60f14485f..86012175f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,6 +96,7 @@ bytes = "0.4" byteorder = "1.2" futures = "0.1" tokio-codec = "0.1" +tokio = "0.1" tokio-io = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" @@ -123,7 +124,6 @@ tokio-uds = { version="0.2", optional = true } actix-web = "0.7" env_logger = "0.5" serde_derive = "1.0" -tokio = "0.1" [build-dependencies] version_check = "0.1" diff --git a/src/body.rs b/src/body.rs index e689b704c..db06bef22 100644 --- a/src/body.rs +++ b/src/body.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use std::{fmt, mem}; use error::Error; -use httpresponse::HttpResponse; /// Type represent streaming body pub type BodyStream = Box>; diff --git a/src/config.rs b/src/config.rs index 508cd5dde..543e78acd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,20 +1,15 @@ -use std::cell::{RefCell, RefMut, UnsafeCell}; -use std::collections::VecDeque; +use std::cell::UnsafeCell; use std::fmt::Write; use std::rc::Rc; use std::time::{Duration, Instant}; -use std::{env, fmt, net}; +use std::{fmt, net}; use bytes::BytesMut; use futures::{future, Future}; -use http::StatusCode; use time; use tokio_current_thread::spawn; use tokio_timer::{sleep, Delay}; -use body::Body; -use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; -use request::{Request, RequestPool}; use server::KeepAlive; // "Sun, 06 Nov 1994 08:49:37 GMT".len() @@ -336,13 +331,7 @@ mod tests { let mut rt = current_thread::Runtime::new().unwrap(); let _ = rt.block_on(future::lazy(|| { - let settings = ServiceConfig::<()>::new( - (), - KeepAlive::Os, - 0, - 0, - ServerSettings::default(), - ); + let settings = ServiceConfig::new(KeepAlive::Os, 0, 0); let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); settings.set_date(&mut buf1, true); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); diff --git a/src/error.rs b/src/error.rs index e39dea9b2..21aabac49 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,7 +28,7 @@ use httpresponse::{HttpResponse, HttpResponseParts}; /// for actix web operations /// /// This typedef is generally used to avoid writing out -/// `actix_web::error::Error` directly and is otherwise a direct mapping to +/// `actix_http::error::Error` directly and is otherwise a direct mapping to /// `Result`. pub type Result = result::Result; @@ -589,13 +589,12 @@ impl From for UrlGenerationError { /// default. /// /// ```rust -/// # extern crate actix_web; -/// # use actix_web::*; -/// use actix_web::fs::NamedFile; +/// # extern crate actix_http; +/// # use std::io; +/// # use actix_http::*; /// -/// fn index(req: HttpRequest) -> Result { -/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?; -/// Ok(f) +/// fn index(req: Request) -> Result<&'static str> { +/// Err(error::ErrorBadRequest(io::Error::new(io::ErrorKind::Other, "error"))) /// } /// # fn main() {} /// ``` @@ -837,14 +836,6 @@ mod tests { use std::error::Error as StdError; use std::io; - #[test] - #[cfg(actix_nightly)] - fn test_nightly() { - let resp: HttpResponse = - IoError::new(io::ErrorKind::Other, "test").error_response(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - } - #[test] fn test_into_response() { let resp: HttpResponse = ParseError::Incomplete.error_response(); @@ -853,9 +844,6 @@ mod tests { let resp: HttpResponse = CookieParseError::EmptyName.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = MultipartError::Boundary.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into(); let resp: HttpResponse = err.error_response(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); @@ -899,14 +887,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } - #[test] - fn test_expect_error() { - let resp: HttpResponse = ExpectError::Encoding.error_response(); - assert_eq!(resp.status(), StatusCode::EXPECTATION_FAILED); - let resp: HttpResponse = ExpectError::UnknownExpect.error_response(); - assert_eq!(resp.status(), StatusCode::EXPECTATION_FAILED); - } - macro_rules! from { ($from:expr => $error:pat) => { match ParseError::from($from) { @@ -963,10 +943,8 @@ mod tests { #[test] fn test_internal_error() { - let err = InternalError::from_response( - ExpectError::Encoding, - HttpResponse::Ok().into(), - ); + let err = + InternalError::from_response(ParseError::Method, HttpResponse::Ok().into()); let resp: HttpResponse = err.error_response(); assert_eq!(resp.status(), StatusCode::OK); } diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 011883575..f1b526d52 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -15,8 +15,11 @@ use httpresponse::HttpResponse; use request::RequestPool; use server::output::{ResponseInfo, ResponseLength}; +/// Http response pub enum OutMessage { + /// Http response message Response(HttpResponse), + /// Payload chunk Payload(Bytes), } @@ -35,7 +38,7 @@ impl Codec { /// Create HTTP/1 codec with request's pool pub(crate) fn with_pool(pool: &'static RequestPool) -> Self { Codec { - decoder: H1Decoder::new(pool), + decoder: H1Decoder::with_pool(pool), encoder: H1Writer::new(), } } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 47cc5fdf1..66f24628c 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -18,16 +18,26 @@ pub(crate) struct H1Decoder { pool: &'static RequestPool, } +/// Incoming http/1 request #[derive(Debug)] pub enum InMessage { + /// Request Message(Request), + /// Request with payload MessageWithPayload(Request), + /// Payload chunk Chunk(Bytes), + /// End of payload Eof, } impl H1Decoder { - pub fn new(pool: &'static RequestPool) -> H1Decoder { + #[cfg(test)] + pub fn new() -> H1Decoder { + H1Decoder::with_pool(RequestPool::pool()) + } + + pub fn with_pool(pool: &'static RequestPool) -> H1Decoder { H1Decoder { pool, decoder: None, @@ -497,3 +507,657 @@ impl ChunkedState { } } } + +#[cfg(test)] +mod tests { + use std::net::Shutdown; + use std::{cmp, io, time}; + + use actix::System; + use bytes::{Buf, Bytes, BytesMut}; + use futures::{future, future::ok}; + use http::{Method, Version}; + use tokio_io::{AsyncRead, AsyncWrite}; + + use super::*; + use error::ParseError; + use h1::{Dispatcher, InMessage}; + use httpmessage::HttpMessage; + use request::Request; + use server::KeepAlive; + + impl InMessage { + fn message(self) -> Request { + match self { + InMessage::Message(msg) => msg, + InMessage::MessageWithPayload(msg) => msg, + _ => panic!("error"), + } + } + fn is_payload(&self) -> bool { + match *self { + InMessage::MessageWithPayload(_) => true, + _ => panic!("error"), + } + } + fn chunk(self) -> Bytes { + match self { + InMessage::Chunk(chunk) => chunk, + _ => panic!("error"), + } + } + fn eof(&self) -> bool { + match *self { + InMessage::Eof => true, + _ => false, + } + } + } + + macro_rules! parse_ready { + ($e:expr) => {{ + match H1Decoder::new().decode($e) { + Ok(Some(msg)) => msg.message(), + Ok(_) => unreachable!("Eof during parsing http request"), + Err(err) => unreachable!("Error during parsing http request: {:?}", err), + } + }}; + } + + macro_rules! expect_parse_err { + ($e:expr) => {{ + match H1Decoder::new().decode($e) { + Err(err) => match err { + ParseError::Io(_) => unreachable!("Parse error expected"), + _ => (), + }, + _ => unreachable!("Error expected"), + } + }}; + } + + struct Buffer { + buf: Bytes, + err: Option, + } + + impl Buffer { + fn new(data: &'static str) -> Buffer { + Buffer { + buf: Bytes::from(data), + err: None, + } + } + } + + impl AsyncRead for Buffer {} + impl io::Read for Buffer { + fn read(&mut self, dst: &mut [u8]) -> Result { + if self.buf.is_empty() { + if self.err.is_some() { + Err(self.err.take().unwrap()) + } else { + Err(io::Error::new(io::ErrorKind::WouldBlock, "")) + } + } else { + let size = cmp::min(self.buf.len(), dst.len()); + let b = self.buf.split_to(size); + dst[..size].copy_from_slice(&b); + Ok(size) + } + } + } + + impl io::Write for Buffer { + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + impl AsyncWrite for Buffer { + fn shutdown(&mut self) -> Poll<(), io::Error> { + Ok(Async::Ready(())) + } + fn write_buf(&mut self, _: &mut B) -> Poll { + Ok(Async::NotReady) + } + } + + // #[test] + // fn test_req_parse_err() { + // let mut sys = System::new("test"); + // let _ = sys.block_on(future::lazy(|| { + // let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); + // let readbuf = BytesMut::new(); + + // let mut h1 = Dispatcher::new(buf, |req| ok(HttpResponse::Ok().finish())); + // assert!(h1.poll_io().is_ok()); + // assert!(h1.poll_io().is_ok()); + // assert!(h1.flags.contains(Flags::READ_DISCONNECTED)); + // assert_eq!(h1.tasks.len(), 1); + // future::ok::<_, ()>(()) + // })); + // } + + #[test] + fn test_parse() { + let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); + + let mut reader = H1Decoder::new(); + match reader.decode(&mut buf) { + Ok(Some(msg)) => { + let req = msg.message(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); + } + Ok(_) | Err(_) => unreachable!("Error during parsing http request"), + } + } + + #[test] + fn test_parse_partial() { + let mut buf = BytesMut::from("PUT /test HTTP/1"); + + let mut reader = H1Decoder::new(); + match reader.decode(&mut buf) { + Ok(None) => (), + _ => unreachable!("Error"), + } + + buf.extend(b".1\r\n\r\n"); + match reader.decode(&mut buf) { + Ok(Some(msg)) => { + let mut req = msg.message(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::PUT); + assert_eq!(req.path(), "/test"); + } + Ok(_) | Err(_) => unreachable!("Error during parsing http request"), + } + } + + #[test] + fn test_parse_post() { + let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); + + let mut reader = H1Decoder::new(); + match reader.decode(&mut buf) { + Ok(Some(msg)) => { + let mut req = msg.message(); + assert_eq!(req.version(), Version::HTTP_10); + assert_eq!(*req.method(), Method::POST); + assert_eq!(req.path(), "/test2"); + } + Ok(_) | Err(_) => unreachable!("Error during parsing http request"), + } + } + + #[test] + fn test_parse_body() { + let mut buf = + BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); + + let mut reader = H1Decoder::new(); + match reader.decode(&mut buf) { + Ok(Some(msg)) => { + let mut req = msg.message(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); + assert_eq!( + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + b"body" + ); + } + Ok(_) | Err(_) => unreachable!("Error during parsing http request"), + } + } + + #[test] + fn test_parse_body_crlf() { + let mut buf = + BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); + + let mut reader = H1Decoder::new(); + match reader.decode(&mut buf) { + Ok(Some(msg)) => { + let mut req = msg.message(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); + assert_eq!( + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + b"body" + ); + } + Ok(_) | Err(_) => unreachable!("Error during parsing http request"), + } + } + + #[test] + fn test_parse_partial_eof() { + let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); + let mut reader = H1Decoder::new(); + assert!(reader.decode(&mut buf).unwrap().is_none()); + + buf.extend(b"\r\n"); + match reader.decode(&mut buf) { + Ok(Some(msg)) => { + let req = msg.message(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); + } + Ok(_) | Err(_) => unreachable!("Error during parsing http request"), + } + } + + #[test] + fn test_headers_split_field() { + let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); + + let mut reader = H1Decoder::new(); + assert!{ reader.decode(&mut buf).unwrap().is_none() } + + buf.extend(b"t"); + assert!{ reader.decode(&mut buf).unwrap().is_none() } + + buf.extend(b"es"); + assert!{ reader.decode(&mut buf).unwrap().is_none() } + + buf.extend(b"t: value\r\n\r\n"); + match reader.decode(&mut buf) { + Ok(Some(msg)) => { + let req = msg.message(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); + assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); + } + Ok(_) | Err(_) => unreachable!("Error during parsing http request"), + } + } + + #[test] + fn test_headers_multi_value() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + Set-Cookie: c1=cookie1\r\n\ + Set-Cookie: c2=cookie2\r\n\r\n", + ); + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + let req = msg.message(); + + let val: Vec<_> = req + .headers() + .get_all("Set-Cookie") + .iter() + .map(|v| v.to_str().unwrap().to_owned()) + .collect(); + assert_eq!(val[0], "c1=cookie1"); + assert_eq!(val[1], "c2=cookie2"); + } + + #[test] + fn test_conn_default_1_0() { + let mut buf = BytesMut::from("GET /test HTTP/1.0\r\n\r\n"); + let req = parse_ready!(&mut buf); + + assert!(!req.keep_alive()); + } + + #[test] + fn test_conn_default_1_1() { + let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); + let req = parse_ready!(&mut buf); + + assert!(req.keep_alive()); + } + + #[test] + fn test_conn_close() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + connection: close\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(!req.keep_alive()); + } + + #[test] + fn test_conn_close_1_0() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.0\r\n\ + connection: close\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(!req.keep_alive()); + } + + #[test] + fn test_conn_keep_alive_1_0() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.0\r\n\ + connection: keep-alive\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(req.keep_alive()); + } + + #[test] + fn test_conn_keep_alive_1_1() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + connection: keep-alive\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(req.keep_alive()); + } + + #[test] + fn test_conn_other_1_0() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.0\r\n\ + connection: other\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(!req.keep_alive()); + } + + #[test] + fn test_conn_other_1_1() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + connection: other\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(req.keep_alive()); + } + + #[test] + fn test_conn_upgrade() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + upgrade: websockets\r\n\ + connection: upgrade\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(req.upgrade()); + } + + #[test] + fn test_conn_upgrade_connect_method() { + let mut buf = BytesMut::from( + "CONNECT /test HTTP/1.1\r\n\ + content-type: text/plain\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(req.upgrade()); + } + + #[test] + fn test_request_chunked() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + if let Ok(val) = req.chunked() { + assert!(val); + } else { + unreachable!("Error"); + } + + // type in chunked + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chnked\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + if let Ok(val) = req.chunked() { + assert!(!val); + } else { + unreachable!("Error"); + } + } + + #[test] + fn test_headers_content_length_err_1() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + content-length: line\r\n\r\n", + ); + + expect_parse_err!(&mut buf) + } + + #[test] + fn test_headers_content_length_err_2() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + content-length: -1\r\n\r\n", + ); + + expect_parse_err!(&mut buf); + } + + #[test] + fn test_invalid_header() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + test line\r\n\r\n", + ); + + expect_parse_err!(&mut buf); + } + + #[test] + fn test_invalid_name() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + test[]: line\r\n\r\n", + ); + + expect_parse_err!(&mut buf); + } + + #[test] + fn test_http_request_bad_status_line() { + let mut buf = BytesMut::from("getpath \r\n\r\n"); + expect_parse_err!(&mut buf); + } + + #[test] + fn test_http_request_upgrade() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + connection: upgrade\r\n\ + upgrade: websocket\r\n\r\n\ + some raw data", + ); + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert!(msg.is_payload()); + let req = msg.message(); + assert!(!req.keep_alive()); + assert!(req.upgrade()); + assert_eq!( + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + b"some raw data" + ); + } + + #[test] + fn test_http_request_parser_utf8() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + x-test: тест\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert_eq!( + req.headers().get("x-test").unwrap().as_bytes(), + "тест".as_bytes() + ); + } + + #[test] + fn test_http_request_parser_two_slashes() { + let mut buf = BytesMut::from("GET //path HTTP/1.1\r\n\r\n"); + let req = parse_ready!(&mut buf); + + assert_eq!(req.path(), "//path"); + } + + #[test] + fn test_http_request_parser_bad_method() { + let mut buf = BytesMut::from("!12%()+=~$ /get HTTP/1.1\r\n\r\n"); + + expect_parse_err!(&mut buf); + } + + #[test] + fn test_http_request_parser_bad_version() { + let mut buf = BytesMut::from("GET //get HT/11\r\n\r\n"); + + expect_parse_err!(&mut buf); + } + + #[test] + fn test_http_request_chunked_payload() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n", + ); + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert!(msg.is_payload()); + let req = msg.message(); + assert!(req.chunked().unwrap()); + + buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); + assert_eq!( + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + b"data" + ); + assert_eq!( + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + b"line" + ); + assert!(reader.decode(&mut buf).unwrap().unwrap().eof()); + } + + #[test] + fn test_http_request_chunked_payload_and_next_message() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n", + ); + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert!(msg.is_payload()); + let req = msg.message(); + assert!(req.chunked().unwrap()); + + buf.extend( + b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ + POST /test2 HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n" + .iter(), + ); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"data"); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"line"); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert!(msg.eof()); + + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert!(msg.is_payload()); + let req2 = msg.message(); + assert!(req2.chunked().unwrap()); + assert_eq!(*req2.method(), Method::POST); + assert!(req2.chunked().unwrap()); + } + + #[test] + fn test_http_request_chunked_payload_chunks() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n", + ); + + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert!(msg.is_payload()); + let req = msg.message(); + assert!(req.chunked().unwrap()); + + buf.extend(b"4\r\n1111\r\n"); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"1111"); + + buf.extend(b"4\r\ndata\r"); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"data"); + + buf.extend(b"\n4"); + assert!(reader.decode(&mut buf).unwrap().is_none()); + + buf.extend(b"\r"); + assert!(reader.decode(&mut buf).unwrap().is_none()); + buf.extend(b"\n"); + assert!(reader.decode(&mut buf).unwrap().is_none()); + + buf.extend(b"li"); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"li"); + + //trailers + //buf.feed_data("test: test\r\n"); + //not_ready!(reader.parse(&mut buf, &mut readbuf)); + + buf.extend(b"ne\r\n0\r\n"); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"ne"); + assert!(reader.decode(&mut buf).unwrap().is_none()); + + buf.extend(b"\r\n"); + assert!(reader.decode(&mut buf).unwrap().unwrap().eof()); + } + + #[test] + fn test_parse_chunked_payload_chunk_extension() { + let mut buf = BytesMut::from( + &"GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n"[..], + ); + + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert!(msg.is_payload()); + assert!(msg.message().chunked().unwrap()); + + buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") + let chunk = reader.decode(&mut buf).unwrap().unwrap().chunk(); + assert_eq!(chunk, Bytes::from_static(b"data")); + let chunk = reader.decode(&mut buf).unwrap().unwrap().chunk(); + assert_eq!(chunk, Bytes::from_static(b"line")); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert!(msg.eof()); + } +} diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 6e5672d35..eda8ebf00 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -1,7 +1,6 @@ // #![allow(unused_imports, unused_variables, dead_code)] use std::collections::VecDeque; use std::fmt::{Debug, Display}; -use std::net::SocketAddr; // use std::time::{Duration, Instant}; use actix_net::service::Service; @@ -16,10 +15,10 @@ use error::{ParseError, PayloadError}; use payload::{Payload, PayloadStatus, PayloadWriter}; use body::Body; +use config::ServiceConfig; use error::DispatchError; use httpresponse::HttpResponse; - -use request::{Request, RequestPool}; +use request::Request; use server::input::PayloadType; use super::codec::{Codec, InMessage, OutMessage}; @@ -52,6 +51,8 @@ where state: State, payload: Option, messages: VecDeque, + + config: ServiceConfig, } enum State { @@ -79,7 +80,7 @@ where S::Error: Debug + Display, { /// Create http/1 dispatcher. - pub fn new(stream: T, service: S) -> Self { + pub fn new(stream: T, config: ServiceConfig, service: S) -> Self { let flags = Flags::FLUSHED; let framed = Framed::new(stream, Codec::new()); @@ -91,6 +92,7 @@ where service, flags, framed, + config, } } @@ -108,7 +110,7 @@ where } // if checked is set to true, delay disconnect until all tasks have finished. - fn client_disconnected(&mut self, checked: bool) { + fn client_disconnected(&mut self, _checked: bool) { self.flags.insert(Flags::READ_DISCONNECTED); if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete); @@ -187,7 +189,7 @@ where None }; }, - State::Payload(ref mut body) => unimplemented!(), + State::Payload(ref mut _body) => unimplemented!(), State::Response(ref mut fut) => { match fut.poll() { Ok(Async::Ready(res)) => { diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 245f2fc23..1a2bb0183 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -4,6 +4,6 @@ mod decoder; mod dispatcher; mod service; -pub use self::codec::Codec; +pub use self::codec::{Codec, InMessage, OutMessage}; pub use self::dispatcher::Dispatcher; pub use self::service::{H1Service, H1ServiceHandler}; diff --git a/src/h1/service.rs b/src/h1/service.rs index 3017a3ef7..436e77a55 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -1,9 +1,7 @@ use std::fmt::{Debug, Display}; use std::marker::PhantomData; -use std::time::Duration; use actix_net::service::{IntoNewService, NewService, Service}; -use futures::future::{ok, FutureResult}; use futures::{Async, Future, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; @@ -120,6 +118,6 @@ where } fn call(&mut self, req: Self::Request) -> Self::Future { - Dispatcher::new(req, self.srv.clone()) + Dispatcher::new(req, self.cfg.clone(), self.srv.clone()) } } diff --git a/src/header/common/accept.rs b/src/header/common/accept.rs index d736e53af..1ba321ce8 100644 --- a/src/header/common/accept.rs +++ b/src/header/common/accept.rs @@ -30,10 +30,10 @@ header! { /// /// # Examples /// ```rust - /// # extern crate actix_web; + /// # extern crate actix_http; /// extern crate mime; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{Accept, qitem}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{Accept, qitem}; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); @@ -47,10 +47,10 @@ header! { /// ``` /// /// ```rust - /// # extern crate actix_web; + /// # extern crate actix_http; /// extern crate mime; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{Accept, qitem}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{Accept, qitem}; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); @@ -64,10 +64,10 @@ header! { /// ``` /// /// ```rust - /// # extern crate actix_web; + /// # extern crate actix_http; /// extern crate mime; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{Accept, QualityItem, q, qitem}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{Accept, QualityItem, q, qitem}; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/accept_charset.rs b/src/header/common/accept_charset.rs index 674415fba..49a7237aa 100644 --- a/src/header/common/accept_charset.rs +++ b/src/header/common/accept_charset.rs @@ -22,9 +22,9 @@ header! { /// /// # Examples /// ```rust - /// # extern crate actix_web; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; + /// # extern crate actix_http; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); @@ -34,9 +34,9 @@ header! { /// # } /// ``` /// ```rust - /// # extern crate actix_web; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptCharset, Charset, q, QualityItem}; + /// # extern crate actix_http; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem}; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); @@ -49,9 +49,9 @@ header! { /// # } /// ``` /// ```rust - /// # extern crate actix_web; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; + /// # extern crate actix_http; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/accept_language.rs b/src/header/common/accept_language.rs index 12593e1ac..25fd97df4 100644 --- a/src/header/common/accept_language.rs +++ b/src/header/common/accept_language.rs @@ -23,10 +23,10 @@ header! { /// # Examples /// /// ```rust - /// # extern crate actix_web; + /// # extern crate actix_http; /// # extern crate language_tags; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem}; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); @@ -42,10 +42,10 @@ header! { /// ``` /// /// ```rust - /// # extern crate actix_web; + /// # extern crate actix_http; /// # #[macro_use] extern crate language_tags; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem}; /// # /// # fn main() { /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/allow.rs b/src/header/common/allow.rs index 5046290de..089c823d0 100644 --- a/src/header/common/allow.rs +++ b/src/header/common/allow.rs @@ -1,5 +1,5 @@ -use http::Method; use http::header; +use http::Method; header! { /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) @@ -23,11 +23,10 @@ header! { /// # Examples /// /// ```rust - /// # extern crate http; - /// # extern crate actix_web; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::Allow; - /// use http::Method; + /// # extern crate actix_http; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::Allow; + /// use actix_http::http::Method; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); @@ -38,11 +37,9 @@ header! { /// ``` /// /// ```rust - /// # extern crate http; - /// # extern crate actix_web; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::Allow; - /// use http::Method; + /// # extern crate actix_http; + /// use actix_http::HttpResponse; + /// use actix_http::http::{Method, header::Allow}; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/cache_control.rs b/src/header/common/cache_control.rs index adc60e4a5..4379b6f7a 100644 --- a/src/header/common/cache_control.rs +++ b/src/header/common/cache_control.rs @@ -1,5 +1,5 @@ -use header::{Header, IntoHeaderValue, Writer}; use header::{fmt_comma_delimited, from_comma_delimited}; +use header::{Header, IntoHeaderValue, Writer}; use http::header; use std::fmt::{self, Write}; use std::str::FromStr; @@ -26,16 +26,16 @@ use std::str::FromStr; /// /// # Examples /// ```rust -/// use actix_web::HttpResponse; -/// use actix_web::http::header::{CacheControl, CacheDirective}; +/// use actix_http::HttpResponse; +/// use actix_http::http::header::{CacheControl, CacheDirective}; /// /// let mut builder = HttpResponse::Ok(); /// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); /// ``` /// /// ```rust -/// use actix_web::HttpResponse; -/// use actix_web::http::header::{CacheControl, CacheDirective}; +/// use actix_http::HttpResponse; +/// use actix_http::http::header::{CacheControl, CacheDirective}; /// /// let mut builder = HttpResponse::Ok(); /// builder.set(CacheControl(vec![ diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs index 5e8cbd67a..0efc4fb0b 100644 --- a/src/header/common/content_disposition.rs +++ b/src/header/common/content_disposition.rs @@ -64,7 +64,7 @@ impl<'a> From<&'a str> for DispositionType { /// /// # Examples /// ``` -/// use actix_web::http::header::DispositionParam; +/// use actix_http::http::header::DispositionParam; /// /// let param = DispositionParam::Filename(String::from("sample.txt")); /// assert!(param.is_filename()); @@ -226,7 +226,7 @@ impl DispositionParam { /// # Example /// /// ``` -/// use actix_web::http::header::{ +/// use actix_http::http::header::{ /// Charset, ContentDisposition, DispositionParam, DispositionType, /// ExtendedValue, /// }; @@ -327,7 +327,8 @@ impl ContentDisposition { left = &left[end.ok_or(::error::ParseError::Header)? + 1..]; left = split_once(left, ';').1.trim_left(); // In fact, it should not be Err if the above code is correct. - String::from_utf8(quoted_string).map_err(|_| ::error::ParseError::Header)? + String::from_utf8(quoted_string) + .map_err(|_| ::error::ParseError::Header)? } else { // token: won't contains semicolon according to RFC 2616 Section 2.2 let (token, new_left) = split_once_and_trim(left, ';'); @@ -874,7 +875,7 @@ mod tests { "attachment; filename=\"carriage\\\rreturn.png\"", display_rendered );*/ - // No way to create a HeaderValue containing a carriage return. + // No way to create a HeaderValue containing a carriage return. let a: ContentDisposition = ContentDisposition { disposition: DispositionType::Inline, diff --git a/src/header/common/content_language.rs b/src/header/common/content_language.rs index e12d34d0d..c1f87d513 100644 --- a/src/header/common/content_language.rs +++ b/src/header/common/content_language.rs @@ -24,10 +24,10 @@ header! { /// # Examples /// /// ```rust - /// # extern crate actix_web; + /// # extern crate actix_http; /// # #[macro_use] extern crate language_tags; - /// use actix_web::HttpResponse; - /// # use actix_web::http::header::{ContentLanguage, qitem}; + /// use actix_http::HttpResponse; + /// # use actix_http::http::header::{ContentLanguage, qitem}; /// # /// # fn main() { /// let mut builder = HttpResponse::Ok(); @@ -40,10 +40,10 @@ header! { /// ``` /// /// ```rust - /// # extern crate actix_web; + /// # extern crate actix_http; /// # #[macro_use] extern crate language_tags; - /// use actix_web::HttpResponse; - /// # use actix_web::http::header::{ContentLanguage, qitem}; + /// use actix_http::HttpResponse; + /// # use actix_http::http::header::{ContentLanguage, qitem}; /// # /// # fn main() { /// diff --git a/src/header/common/content_type.rs b/src/header/common/content_type.rs index 08900e1cc..3286d4cae 100644 --- a/src/header/common/content_type.rs +++ b/src/header/common/content_type.rs @@ -31,8 +31,8 @@ header! { /// # Examples /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::ContentType; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::ContentType; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); @@ -44,10 +44,10 @@ header! { /// /// ```rust /// # extern crate mime; - /// # extern crate actix_web; + /// # extern crate actix_http; /// use mime::TEXT_HTML; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::ContentType; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::ContentType; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/date.rs b/src/header/common/date.rs index 88a47bc3f..9ce2bd65f 100644 --- a/src/header/common/date.rs +++ b/src/header/common/date.rs @@ -20,8 +20,8 @@ header! { /// # Example /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::Date; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::Date; /// use std::time::SystemTime; /// /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/etag.rs b/src/header/common/etag.rs index 39dd908c1..ea4be2a77 100644 --- a/src/header/common/etag.rs +++ b/src/header/common/etag.rs @@ -28,16 +28,16 @@ header! { /// # Examples /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{ETag, EntityTag}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{ETag, EntityTag}; /// /// let mut builder = HttpResponse::Ok(); /// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned()))); /// ``` /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{ETag, EntityTag}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{ETag, EntityTag}; /// /// let mut builder = HttpResponse::Ok(); /// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned()))); diff --git a/src/header/common/expires.rs b/src/header/common/expires.rs index 4ec66b880..bdd25fdb9 100644 --- a/src/header/common/expires.rs +++ b/src/header/common/expires.rs @@ -22,8 +22,8 @@ header! { /// # Example /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::Expires; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::Expires; /// use std::time::{SystemTime, Duration}; /// /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/if_match.rs b/src/header/common/if_match.rs index 20a2b1e6b..5f7976a4a 100644 --- a/src/header/common/if_match.rs +++ b/src/header/common/if_match.rs @@ -30,16 +30,16 @@ header! { /// # Examples /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::IfMatch; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::IfMatch; /// /// let mut builder = HttpResponse::Ok(); /// builder.set(IfMatch::Any); /// ``` /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{IfMatch, EntityTag}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{IfMatch, EntityTag}; /// /// let mut builder = HttpResponse::Ok(); /// builder.set( diff --git a/src/header/common/if_modified_since.rs b/src/header/common/if_modified_since.rs index 1914d34d3..41d6fba27 100644 --- a/src/header/common/if_modified_since.rs +++ b/src/header/common/if_modified_since.rs @@ -22,8 +22,8 @@ header! { /// # Example /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::IfModifiedSince; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::IfModifiedSince; /// use std::time::{SystemTime, Duration}; /// /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/if_none_match.rs b/src/header/common/if_none_match.rs index 124f4b8e0..8b3905bab 100644 --- a/src/header/common/if_none_match.rs +++ b/src/header/common/if_none_match.rs @@ -32,16 +32,16 @@ header! { /// # Examples /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::IfNoneMatch; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::IfNoneMatch; /// /// let mut builder = HttpResponse::Ok(); /// builder.set(IfNoneMatch::Any); /// ``` /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{IfNoneMatch, EntityTag}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{IfNoneMatch, EntityTag}; /// /// let mut builder = HttpResponse::Ok(); /// builder.set( diff --git a/src/header/common/if_range.rs b/src/header/common/if_range.rs index dd95b7ba8..8cbb8c897 100644 --- a/src/header/common/if_range.rs +++ b/src/header/common/if_range.rs @@ -1,7 +1,9 @@ use error::ParseError; use header::from_one_raw_str; -use header::{EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, - InvalidHeaderValueBytes, Writer}; +use header::{ + EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, + InvalidHeaderValueBytes, Writer, +}; use http::header; use httpmessage::HttpMessage; use std::fmt::{self, Display, Write}; @@ -35,8 +37,8 @@ use std::fmt::{self, Display, Write}; /// # Examples /// /// ```rust -/// use actix_web::HttpResponse; -/// use actix_web::http::header::{EntityTag, IfRange}; +/// use actix_http::HttpResponse; +/// use actix_http::http::header::{EntityTag, IfRange}; /// /// let mut builder = HttpResponse::Ok(); /// builder.set(IfRange::EntityTag(EntityTag::new( @@ -46,8 +48,8 @@ use std::fmt::{self, Display, Write}; /// ``` /// /// ```rust -/// use actix_web::HttpResponse; -/// use actix_web::http::header::IfRange; +/// use actix_http::HttpResponse; +/// use actix_http::http::header::IfRange; /// use std::time::{Duration, SystemTime}; /// /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/if_unmodified_since.rs b/src/header/common/if_unmodified_since.rs index f87e760c0..02f9252e2 100644 --- a/src/header/common/if_unmodified_since.rs +++ b/src/header/common/if_unmodified_since.rs @@ -23,8 +23,8 @@ header! { /// # Example /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::IfUnmodifiedSince; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::IfUnmodifiedSince; /// use std::time::{SystemTime, Duration}; /// /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/last_modified.rs b/src/header/common/last_modified.rs index aba828883..608f43138 100644 --- a/src/header/common/last_modified.rs +++ b/src/header/common/last_modified.rs @@ -22,8 +22,8 @@ header! { /// # Example /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::LastModified; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::LastModified; /// use std::time::{SystemTime, Duration}; /// /// let mut builder = HttpResponse::Ok(); diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 8c972bd13..531aa1a72 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -106,7 +106,7 @@ pub trait HttpMessage: Sized { /// /// ## Server example /// - /// ```rust + /// ```rust,ignore /// # extern crate bytes; /// # extern crate actix_web; /// # extern crate futures; @@ -143,7 +143,7 @@ pub trait HttpMessage: Sized { /// /// ## Server example /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// # extern crate futures; /// # use futures::Future; @@ -176,7 +176,7 @@ pub trait HttpMessage: Sized { /// /// ## Server example /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// # extern crate futures; /// # #[macro_use] extern crate serde_derive; diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 73de380ad..cdcdea94a 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -15,7 +15,7 @@ use serde_json; use body::Body; use error::Error; use header::{ContentEncoding, Header, IntoHeaderValue}; -use httpmessage::HttpMessage; +// use httpmessage::HttpMessage; // use httprequest::HttpRequest; /// max write buffer size 64k @@ -366,7 +366,7 @@ impl HttpResponseBuilder { /// Set a header. /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// use actix_web::{http, HttpRequest, HttpResponse, Result}; /// @@ -394,7 +394,7 @@ impl HttpResponseBuilder { /// Set a header. /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// use actix_web::{http, HttpRequest, HttpResponse}; /// @@ -516,7 +516,7 @@ impl HttpResponseBuilder { /// Set a cookie /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// use actix_web::{http, HttpRequest, HttpResponse, Result}; /// @@ -546,7 +546,7 @@ impl HttpResponseBuilder { /// Remove cookie /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// use actix_web::{http, HttpRequest, HttpResponse, Result}; /// @@ -956,38 +956,38 @@ mod tests { assert!(dbg.contains("HttpResponse")); } - #[test] - fn test_response_cookies() { - let req = TestRequest::default() - .header(COOKIE, "cookie1=value1") - .header(COOKIE, "cookie2=value2") - .finish(); - let cookies = req.cookies().unwrap(); + // #[test] + // fn test_response_cookies() { + // let req = TestRequest::default() + // .header(COOKIE, "cookie1=value1") + // .header(COOKIE, "cookie2=value2") + // .finish(); + // let cookies = req.cookies().unwrap(); - let resp = HttpResponse::Ok() - .cookie( - http::Cookie::build("name", "value") - .domain("www.rust-lang.org") - .path("/test") - .http_only(true) - .max_age(Duration::days(1)) - .finish(), - ).del_cookie(&cookies[0]) - .finish(); + // let resp = HttpResponse::Ok() + // .cookie( + // http::Cookie::build("name", "value") + // .domain("www.rust-lang.org") + // .path("/test") + // .http_only(true) + // .max_age(Duration::days(1)) + // .finish(), + // ).del_cookie(&cookies[0]) + // .finish(); - let mut val: Vec<_> = resp - .headers() - .get_all("Set-Cookie") - .iter() - .map(|v| v.to_str().unwrap().to_owned()) - .collect(); - val.sort(); - assert!(val[0].starts_with("cookie1=; Max-Age=0;")); - assert_eq!( - val[1], - "name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400" - ); - } + // let mut val: Vec<_> = resp + // .headers() + // .get_all("Set-Cookie") + // .iter() + // .map(|v| v.to_str().unwrap().to_owned()) + // .collect(); + // val.sort(); + // assert!(val[0].starts_with("cookie1=; Max-Age=0;")); + // assert_eq!( + // val[1], + // "name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400" + // ); + // } #[test] fn test_update_response_cookies() { @@ -1131,15 +1131,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), &Binary::from("test")); - let resp: HttpResponse = "test".respond_to(&req).ok().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from("test")); - let resp: HttpResponse = b"test".as_ref().into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( @@ -1149,15 +1140,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref())); - let resp: HttpResponse = b"test".as_ref().respond_to(&req).ok().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref())); - let resp: HttpResponse = "test".to_owned().into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( @@ -1167,15 +1149,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned())); - let resp: HttpResponse = "test".to_owned().respond_to(&req).ok().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned())); - let resp: HttpResponse = (&"test".to_owned()).into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( @@ -1185,15 +1158,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned())); - let resp: HttpResponse = (&"test".to_owned()).respond_to(&req).ok().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned())); - let b = Bytes::from_static(b"test"); let resp: HttpResponse = b.into(); assert_eq!(resp.status(), StatusCode::OK); @@ -1208,19 +1172,6 @@ mod tests { ); let b = Bytes::from_static(b"test"); - let resp: HttpResponse = b.respond_to(&req).ok().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body().bin_ref(), - &Binary::from(Bytes::from_static(b"test")) - ); - - let b = BytesMut::from("test"); let resp: HttpResponse = b.into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( @@ -1231,7 +1182,7 @@ mod tests { assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test"))); let b = BytesMut::from("test"); - let resp: HttpResponse = b.respond_to(&req).ok().unwrap(); + let resp: HttpResponse = b.into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), diff --git a/src/json.rs b/src/json.rs index 04dd369eb..5c64b9bdd 100644 --- a/src/json.rs +++ b/src/json.rs @@ -3,18 +3,13 @@ use futures::{Future, Poll, Stream}; use http::header::CONTENT_LENGTH; use std::fmt; use std::ops::{Deref, DerefMut}; -use std::rc::Rc; use mime; use serde::de::DeserializeOwned; -use serde::Serialize; use serde_json; -use error::{Error, JsonPayloadError}; -use http::StatusCode; +use error::JsonPayloadError; use httpmessage::HttpMessage; -// use httprequest::HttpRequest; -use httpresponse::HttpResponse; /// Json helper /// @@ -30,7 +25,7 @@ use httpresponse::HttpResponse; /// /// ## Example /// -/// ```rust +/// ```rust,ignore /// # extern crate actix_web; /// #[macro_use] extern crate serde_derive; /// use actix_web::{App, Json, Result, http}; @@ -57,7 +52,7 @@ use httpresponse::HttpResponse; /// to serialize into *JSON*. The type `T` must implement the `Serialize` /// trait from *serde*. /// -/// ```rust +/// ```rust,ignore /// # extern crate actix_web; /// # #[macro_use] extern crate serde_derive; /// # use actix_web::*; @@ -124,7 +119,7 @@ where /// /// # Server example /// -/// ```rust +/// ```rust,ignore /// # extern crate actix_web; /// # extern crate futures; /// # #[macro_use] extern crate serde_derive; @@ -243,9 +238,7 @@ mod tests { use futures::Async; use http::header; - use handler::Handler; use test::TestRequest; - use with::With; impl PartialEq for JsonPayloadError { fn eq(&self, other: &JsonPayloadError) -> bool { @@ -268,18 +261,6 @@ mod tests { name: String, } - #[test] - fn test_json() { - let json = Json(MyObject { - name: "test".to_owned(), - }); - let resp = json.respond_to(&TestRequest::default().finish()).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "application/json" - ); - } - #[test] fn test_json_body() { let req = TestRequest::default().finish(); @@ -323,24 +304,4 @@ mod tests { }) ); } - - #[test] - fn test_with_json() { - let mut cfg = JsonConfig::default(); - cfg.limit(4096); - let handler = With::new(|data: Json| data, cfg); - - let req = TestRequest::default().finish(); - assert!(handler.handle(&req).as_err().is_some()); - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ).header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .finish(); - assert!(handler.handle(&req).as_err().is_none()) - } } diff --git a/src/lib.rs b/src/lib.rs index ec86f0320..b9f90c7a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! Actix web is a small, pragmatic, and extremely fast web framework //! for Rust. //! -//! ```rust +//! ```rust,ignore //! use actix_web::{server, App, Path, Responder}; //! # use std::thread; //! @@ -78,10 +78,11 @@ //! `gzip`, `deflate` compression. //! #![cfg_attr(actix_nightly, feature(tool_lints))] -#![warn(missing_docs)] -#![allow(unused_imports, unused_variables, dead_code)] +// #![warn(missing_docs)] +// #![allow(unused_imports, unused_variables, dead_code)] extern crate actix; +extern crate actix_net; #[macro_use] extern crate log; extern crate base64; @@ -98,7 +99,12 @@ extern crate failure; extern crate lazy_static; #[macro_use] extern crate futures; +#[cfg(feature = "brotli")] +extern crate brotli2; extern crate cookie; +extern crate encoding; +#[cfg(feature = "flate2")] +extern crate flate2; extern crate http as modhttp; extern crate httparse; extern crate language_tags; @@ -106,6 +112,8 @@ extern crate mime; extern crate mime_guess; extern crate net2; extern crate rand; +extern crate serde; +extern crate serde_urlencoded; extern crate tokio_codec; extern crate tokio_current_thread; extern crate tokio_io; @@ -116,19 +124,10 @@ extern crate tokio_timer; extern crate tokio_uds; extern crate url; #[macro_use] -extern crate serde; -#[cfg(feature = "brotli")] -extern crate brotli2; -extern crate encoding; -#[cfg(feature = "flate2")] -extern crate flate2; -extern crate serde_urlencoded; -#[macro_use] extern crate percent_encoding; extern crate serde_json; extern crate smallvec; - -extern crate actix_net; +extern crate tokio; #[cfg(test)] #[macro_use] @@ -150,7 +149,7 @@ pub mod error; pub mod h1; pub(crate) mod helpers; pub mod server; -//pub mod test; +pub mod test; //pub mod ws; pub use body::{Binary, Body}; pub use error::{Error, ResponseError, Result}; @@ -170,7 +169,7 @@ pub mod dev { //! //! ``` //! # #![allow(unused_imports)] - //! use actix_web::dev::*; + //! use actix_http::dev::*; //! ``` pub use body::BodyStream; diff --git a/src/request.rs b/src/request.rs index a75fda3a0..82d8c22fa 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,7 +1,6 @@ use std::cell::{Cell, Ref, RefCell, RefMut}; use std::collections::VecDeque; use std::fmt; -use std::net::SocketAddr; use std::rc::Rc; use http::{header, HeaderMap, Method, Uri, Version}; @@ -31,7 +30,6 @@ pub(crate) struct InnerRequest { pub(crate) headers: HeaderMap, pub(crate) extensions: RefCell, pub(crate) payload: RefCell>, - pub(crate) stream_extensions: Option>, pool: &'static RequestPool, } @@ -81,7 +79,6 @@ impl Request { flags: Cell::new(MessageFlags::empty()), payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), - stream_extensions: None, }), } } @@ -170,15 +167,13 @@ impl Request { inner: self.inner.clone(), } } +} - pub(crate) fn release(self) { - let mut inner = self.inner; - if let Some(r) = Rc::get_mut(&mut inner) { - r.reset(); - } else { - return; +impl Drop for Request { + fn drop(&mut self) { + if Rc::strong_count(&self.inner) == 1 { + self.inner.pool.release(self.inner.clone()); } - inner.pool.release(inner); } } @@ -221,11 +216,13 @@ impl RequestPool { /// Get Request object #[inline] pub fn get(pool: &'static RequestPool) -> Request { - if let Some(msg) = pool.0.borrow_mut().pop_front() { - Request { inner: msg } - } else { - Request::with_pool(pool) + if let Some(mut msg) = pool.0.borrow_mut().pop_front() { + if let Some(r) = Rc::get_mut(&mut msg) { + r.reset(); + } + return Request { inner: msg }; } + Request::with_pool(pool) } #[inline] diff --git a/src/server/mod.rs b/src/server/mod.rs index 068094c2a..0abd7c216 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -106,12 +106,9 @@ //! let _ = sys.run(); //!} //! ``` -use std::net::{Shutdown, SocketAddr}; -use std::rc::Rc; +use std::net::SocketAddr; use std::{io, time}; -use bytes::{BufMut, BytesMut}; -use futures::{Async, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_tcp::TcpStream; @@ -123,16 +120,8 @@ pub(crate) mod output; #[doc(hidden)] pub use super::helpers::write_content_length; -use body::Binary; -use extensions::Extensions; -use header::ContentEncoding; -use httpresponse::HttpResponse; - -/// max buffer size 64k -pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; - -const LW_BUFFER_SIZE: usize = 4096; -const HW_BUFFER_SIZE: usize = 32_768; +// /// max buffer size 64k +// pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; #[derive(Debug, PartialEq, Clone, Copy)] /// Server keep-alive setting diff --git a/src/server/output.rs b/src/server/output.rs index f20bd3266..cfc85e4bc 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -1,3 +1,4 @@ +#![allow(unused_imports, unused_variables, dead_code)] use std::fmt::Write as FmtWrite; use std::io::Write; use std::str::FromStr; diff --git a/src/test.rs b/src/test.rs index d0cfb255a..3c48df643 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,10 +1,8 @@ //! Various helpers for Actix applications to use during testing. -use std::rc::Rc; +use std::net; use std::str::FromStr; -use std::sync::mpsc; -use std::{net, thread}; -use actix_inner::{Actor, Addr, System}; +use actix::System; use cookie::Cookie; use futures::Future; @@ -13,28 +11,12 @@ use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; use net2::TcpBuilder; use tokio::runtime::current_thread::Runtime; -#[cfg(any(feature = "alpn", feature = "ssl"))] -use openssl::ssl::SslAcceptorBuilder; -#[cfg(feature = "rust-tls")] -use rustls::ServerConfig; - -use application::{App, HttpApplication}; use body::Binary; -use client::{ClientConnector, ClientRequest, ClientRequestBuilder}; -use error::Error; -use handler::{AsyncResult, AsyncResultItem, Handler, Responder}; use header::{Header, IntoHeaderValue}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::Middleware; -use param::Params; use payload::Payload; -use resource::Resource; -use router::Router; -use server::message::{Request, RequestPool}; -use server::{HttpServer, IntoHttpHandler, ServerSettings}; +use request::Request; use uri::Url as InnerUrl; -use ws; +// use ws; /// The `TestServer` type. /// @@ -63,9 +45,8 @@ use ws; /// ``` pub struct TestServer { addr: net::SocketAddr, - ssl: bool, - conn: Addr, rt: Runtime, + ssl: bool, } impl TestServer { @@ -73,92 +54,11 @@ impl TestServer { /// /// This method accepts configuration method. You can add /// middlewares or set handlers for test application. - pub fn new(config: F) -> Self + pub fn new(_config: F) -> Self where - F: Clone + Send + 'static + Fn(&mut TestApp<()>), + F: Fn() + Clone + Send + 'static, { - TestServerBuilder::new(|| ()).start(config) - } - - /// Create test server builder - pub fn build() -> TestServerBuilder<(), impl Fn() -> () + Clone + Send + 'static> { - TestServerBuilder::new(|| ()) - } - - /// Create test server builder with specific state factory - /// - /// This method can be used for constructing application state. - /// Also it can be used for external dependency initialization, - /// like creating sync actors for diesel integration. - pub fn build_with_state(state: F) -> TestServerBuilder - where - F: Fn() -> S + Clone + Send + 'static, - S: 'static, - { - TestServerBuilder::new(state) - } - - /// Start new test server with application factory - pub fn with_factory(factory: F) -> Self - where - F: Fn() -> H + Send + Clone + 'static, - H: IntoHttpHandler + 'static, - { - let (tx, rx) = mpsc::channel(); - - // run server in separate thread - thread::spawn(move || { - let sys = System::new("actix-test-server"); - let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); - let local_addr = tcp.local_addr().unwrap(); - - let _ = HttpServer::new(factory) - .disable_signals() - .listen(tcp) - .keep_alive(5) - .start(); - - tx.send((System::current(), local_addr, TestServer::get_conn())) - .unwrap(); - sys.run(); - }); - - let (system, addr, conn) = rx.recv().unwrap(); - System::set_current(system); - TestServer { - addr, - conn, - ssl: false, - rt: Runtime::new().unwrap(), - } - } - - fn get_conn() -> Addr { - #[cfg(any(feature = "alpn", feature = "ssl"))] - { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - ClientConnector::with_connector(builder.build()).start() - } - #[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "ssl")) - ))] - { - use rustls::ClientConfig; - use std::fs::File; - use std::io::BufReader; - let mut config = ClientConfig::new(); - let pem_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - config.root_store.add_pem_file(pem_file).unwrap(); - ClientConnector::with_connector(config).start() - } - #[cfg(not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")))] - { - ClientConnector::default().start() - } + unimplemented!() } /// Get firat available unused address @@ -208,45 +108,45 @@ impl TestServer { self.rt.block_on(fut) } - /// Connect to websocket server at a given path - pub fn ws_at( - &mut self, path: &str, - ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { - let url = self.url(path); - self.rt - .block_on(ws::Client::with_connector(url, self.conn.clone()).connect()) - } + // /// Connect to websocket server at a given path + // pub fn ws_at( + // &mut self, path: &str, + // ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { + // let url = self.url(path); + // self.rt + // .block_on(ws::Client::with_connector(url, self.conn.clone()).connect()) + // } - /// Connect to a websocket server - pub fn ws( - &mut self, - ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { - self.ws_at("/") - } + // /// Connect to a websocket server + // pub fn ws( + // &mut self, + // ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { + // self.ws_at("/") + // } - /// Create `GET` request - pub fn get(&self) -> ClientRequestBuilder { - ClientRequest::get(self.url("/").as_str()) - } + // /// Create `GET` request + // pub fn get(&self) -> ClientRequestBuilder { + // ClientRequest::get(self.url("/").as_str()) + // } - /// Create `POST` request - pub fn post(&self) -> ClientRequestBuilder { - ClientRequest::post(self.url("/").as_str()) - } + // /// Create `POST` request + // pub fn post(&self) -> ClientRequestBuilder { + // ClientRequest::post(self.url("/").as_str()) + // } - /// Create `HEAD` request - pub fn head(&self) -> ClientRequestBuilder { - ClientRequest::head(self.url("/").as_str()) - } + // /// Create `HEAD` request + // pub fn head(&self) -> ClientRequestBuilder { + // ClientRequest::head(self.url("/").as_str()) + // } - /// Connect to test http server - pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder { - ClientRequest::build() - .method(meth) - .uri(self.url(path).as_str()) - .with_connector(self.conn.clone()) - .take() - } + // /// Connect to test http server + // pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder { + // ClientRequest::build() + // .method(meth) + // .uri(self.url(path).as_str()) + // .with_connector(self.conn.clone()) + // .take() + // } } impl Drop for TestServer { @@ -255,183 +155,98 @@ impl Drop for TestServer { } } -/// An `TestServer` builder -/// -/// This type can be used to construct an instance of `TestServer` through a -/// builder-like pattern. -pub struct TestServerBuilder -where - F: Fn() -> S + Send + Clone + 'static, -{ - state: F, - #[cfg(any(feature = "alpn", feature = "ssl"))] - ssl: Option, - #[cfg(feature = "rust-tls")] - rust_ssl: Option, -} +// /// An `TestServer` builder +// /// +// /// This type can be used to construct an instance of `TestServer` through a +// /// builder-like pattern. +// pub struct TestServerBuilder +// where +// F: Fn() -> S + Send + Clone + 'static, +// { +// state: F, +// } -impl TestServerBuilder -where - F: Fn() -> S + Send + Clone + 'static, -{ - /// Create a new test server - pub fn new(state: F) -> TestServerBuilder { - TestServerBuilder { - state, - #[cfg(any(feature = "alpn", feature = "ssl"))] - ssl: None, - #[cfg(feature = "rust-tls")] - rust_ssl: None, - } - } +// impl TestServerBuilder +// where +// F: Fn() -> S + Send + Clone + 'static, +// { +// /// Create a new test server +// pub fn new(state: F) -> TestServerBuilder { +// TestServerBuilder { state } +// } - #[cfg(any(feature = "alpn", feature = "ssl"))] - /// Create ssl server - pub fn ssl(mut self, ssl: SslAcceptorBuilder) -> Self { - self.ssl = Some(ssl); - self - } +// #[allow(unused_mut)] +// /// Configure test application and run test server +// pub fn start(mut self, config: C) -> TestServer +// where +// C: Fn(&mut TestApp) + Clone + Send + 'static, +// { +// let (tx, rx) = mpsc::channel(); - #[cfg(feature = "rust-tls")] - /// Create rust tls server - pub fn rustls(mut self, ssl: ServerConfig) -> Self { - self.rust_ssl = Some(ssl); - self - } +// let mut has_ssl = false; - #[allow(unused_mut)] - /// Configure test application and run test server - pub fn start(mut self, config: C) -> TestServer - where - C: Fn(&mut TestApp) + Clone + Send + 'static, - { - let (tx, rx) = mpsc::channel(); +// #[cfg(any(feature = "alpn", feature = "ssl"))] +// { +// has_ssl = has_ssl || self.ssl.is_some(); +// } - let mut has_ssl = false; +// #[cfg(feature = "rust-tls")] +// { +// has_ssl = has_ssl || self.rust_ssl.is_some(); +// } - #[cfg(any(feature = "alpn", feature = "ssl"))] - { - has_ssl = has_ssl || self.ssl.is_some(); - } +// // run server in separate thread +// thread::spawn(move || { +// let addr = TestServer::unused_addr(); - #[cfg(feature = "rust-tls")] - { - has_ssl = has_ssl || self.rust_ssl.is_some(); - } +// let sys = System::new("actix-test-server"); +// let state = self.state; +// let mut srv = HttpServer::new(move || { +// let mut app = TestApp::new(state()); +// config(&mut app); +// app +// }).workers(1) +// .keep_alive(5) +// .disable_signals(); - // run server in separate thread - thread::spawn(move || { - let addr = TestServer::unused_addr(); +// tx.send((System::current(), addr, TestServer::get_conn())) +// .unwrap(); - let sys = System::new("actix-test-server"); - let state = self.state; - let mut srv = HttpServer::new(move || { - let mut app = TestApp::new(state()); - config(&mut app); - app - }).workers(1) - .keep_alive(5) - .disable_signals(); +// #[cfg(any(feature = "alpn", feature = "ssl"))] +// { +// let ssl = self.ssl.take(); +// if let Some(ssl) = ssl { +// let tcp = net::TcpListener::bind(addr).unwrap(); +// srv = srv.listen_ssl(tcp, ssl).unwrap(); +// } +// } +// #[cfg(feature = "rust-tls")] +// { +// let ssl = self.rust_ssl.take(); +// if let Some(ssl) = ssl { +// let tcp = net::TcpListener::bind(addr).unwrap(); +// srv = srv.listen_rustls(tcp, ssl); +// } +// } +// if !has_ssl { +// let tcp = net::TcpListener::bind(addr).unwrap(); +// srv = srv.listen(tcp); +// } +// srv.start(); - tx.send((System::current(), addr, TestServer::get_conn())) - .unwrap(); +// sys.run(); +// }); - #[cfg(any(feature = "alpn", feature = "ssl"))] - { - let ssl = self.ssl.take(); - if let Some(ssl) = ssl { - let tcp = net::TcpListener::bind(addr).unwrap(); - srv = srv.listen_ssl(tcp, ssl).unwrap(); - } - } - #[cfg(feature = "rust-tls")] - { - let ssl = self.rust_ssl.take(); - if let Some(ssl) = ssl { - let tcp = net::TcpListener::bind(addr).unwrap(); - srv = srv.listen_rustls(tcp, ssl); - } - } - if !has_ssl { - let tcp = net::TcpListener::bind(addr).unwrap(); - srv = srv.listen(tcp); - } - srv.start(); - - sys.run(); - }); - - let (system, addr, conn) = rx.recv().unwrap(); - System::set_current(system); - TestServer { - addr, - conn, - ssl: has_ssl, - rt: Runtime::new().unwrap(), - } - } -} - -/// Test application helper for testing request handlers. -pub struct TestApp { - app: Option>, -} - -impl TestApp { - fn new(state: S) -> TestApp { - let app = App::with_state(state); - TestApp { app: Some(app) } - } - - /// Register handler for "/" - pub fn handler(&mut self, handler: F) - where - F: Fn(&HttpRequest) -> R + 'static, - R: Responder + 'static, - { - self.app = Some(self.app.take().unwrap().resource("/", |r| r.f(handler))); - } - - /// Register middleware - pub fn middleware(&mut self, mw: T) -> &mut TestApp - where - T: Middleware + 'static, - { - self.app = Some(self.app.take().unwrap().middleware(mw)); - self - } - - /// Register resource. This method is similar - /// to `App::resource()` method. - pub fn resource(&mut self, path: &str, f: F) -> &mut TestApp - where - F: FnOnce(&mut Resource) -> R + 'static, - { - self.app = Some(self.app.take().unwrap().resource(path, f)); - self - } -} - -impl IntoHttpHandler for TestApp { - type Handler = HttpApplication; - - fn into_handler(mut self) -> HttpApplication { - self.app.take().unwrap().into_handler() - } -} - -#[doc(hidden)] -impl Iterator for TestApp { - type Item = HttpApplication; - - fn next(&mut self) -> Option { - if let Some(mut app) = self.app.take() { - Some(app.finish()) - } else { - None - } - } -} +// let (system, addr, conn) = rx.recv().unwrap(); +// System::set_current(system); +// TestServer { +// addr, +// conn, +// ssl: has_ssl, +// rt: Runtime::new().unwrap(), +// } +// } +// } /// Test `HttpRequest` builder /// @@ -460,70 +275,49 @@ impl Iterator for TestApp { /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` -pub struct TestRequest { - state: S, +pub struct TestRequest { version: Version, method: Method, uri: Uri, headers: HeaderMap, - params: Params, - cookies: Option>>, + _cookies: Option>>, payload: Option, prefix: u16, } -impl Default for TestRequest<()> { - fn default() -> TestRequest<()> { +impl Default for TestRequest { + fn default() -> TestRequest { TestRequest { - state: (), method: Method::GET, uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, headers: HeaderMap::new(), - params: Params::new(), - cookies: None, + _cookies: None, payload: None, prefix: 0, } } } -impl TestRequest<()> { +impl TestRequest { /// Create TestRequest and set request uri - pub fn with_uri(path: &str) -> TestRequest<()> { + pub fn with_uri(path: &str) -> TestRequest { TestRequest::default().uri(path) } /// Create TestRequest and set header - pub fn with_hdr(hdr: H) -> TestRequest<()> { + pub fn with_hdr(hdr: H) -> TestRequest { TestRequest::default().set(hdr) } /// Create TestRequest and set header - pub fn with_header(key: K, value: V) -> TestRequest<()> + pub fn with_header(key: K, value: V) -> TestRequest where HeaderName: HttpTryFrom, V: IntoHeaderValue, { TestRequest::default().header(key, value) } -} - -impl TestRequest { - /// Start HttpRequest build process with application state - pub fn with_state(state: S) -> TestRequest { - TestRequest { - state, - method: Method::GET, - uri: Uri::from_str("/").unwrap(), - version: Version::HTTP_11, - headers: HeaderMap::new(), - params: Params::new(), - cookies: None, - payload: None, - prefix: 0, - } - } /// Set HTTP version of this request pub fn version(mut self, ver: Version) -> Self { @@ -567,12 +361,6 @@ impl TestRequest { panic!("Can not create header"); } - /// Set request path pattern parameter - pub fn param(mut self, name: &'static str, value: &'static str) -> Self { - self.params.add_static(name, value); - self - } - /// Set request payload pub fn set_payload>(mut self, data: B) -> Self { let mut data = data.into(); @@ -588,23 +376,19 @@ impl TestRequest { self } - /// Complete request creation and generate `HttpRequest` instance - pub fn finish(self) -> HttpRequest { + /// Complete request creation and generate `Request` instance + pub fn finish(self) -> Request { let TestRequest { - state, method, uri, version, headers, - mut params, - cookies, + _cookies: _, payload, - prefix, + prefix: _, } = self; - let router = Router::<()>::default(); - let pool = RequestPool::pool(ServerSettings::default()); - let mut req = RequestPool::get(pool); + let mut req = Request::new(); { let inner = req.inner_mut(); inner.method = method; @@ -613,156 +397,94 @@ impl TestRequest { inner.headers = headers; *inner.payload.borrow_mut() = payload; } - params.set_url(req.url().clone()); - let mut info = router.route_info_params(0, params); - info.set_prefix(prefix); - - let mut req = HttpRequest::new(req, Rc::new(state), info); - req.set_cookies(cookies); + // req.set_cookies(cookies); req } - #[cfg(test)] - /// Complete request creation and generate `HttpRequest` instance - pub(crate) fn finish_with_router(self, router: Router) -> HttpRequest { - let TestRequest { - state, - method, - uri, - version, - headers, - mut params, - cookies, - payload, - prefix, - } = self; + // /// This method generates `HttpRequest` instance and runs handler + // /// with generated request. + // pub fn run>(self, h: &H) -> Result { + // let req = self.finish(); + // let resp = h.handle(&req); - let pool = RequestPool::pool(ServerSettings::default()); - let mut req = RequestPool::get(pool); - { - let inner = req.inner_mut(); - inner.method = method; - inner.url = InnerUrl::new(uri); - inner.version = version; - inner.headers = headers; - *inner.payload.borrow_mut() = payload; - } - params.set_url(req.url().clone()); - let mut info = router.route_info_params(0, params); - info.set_prefix(prefix); - let mut req = HttpRequest::new(req, Rc::new(state), info); - req.set_cookies(cookies); - req - } + // match resp.respond_to(&req) { + // Ok(resp) => match resp.into().into() { + // AsyncResultItem::Ok(resp) => Ok(resp), + // AsyncResultItem::Err(err) => Err(err), + // AsyncResultItem::Future(fut) => { + // let mut sys = System::new("test"); + // sys.block_on(fut) + // } + // }, + // Err(err) => Err(err.into()), + // } + // } - /// Complete request creation and generate server `Request` instance - pub fn request(self) -> Request { - let TestRequest { - method, - uri, - version, - headers, - payload, - .. - } = self; + // /// This method generates `HttpRequest` instance and runs handler + // /// with generated request. + // /// + // /// This method panics is handler returns actor. + // pub fn run_async(self, h: H) -> Result + // where + // H: Fn(HttpRequest) -> F + 'static, + // F: Future + 'static, + // R: Responder + 'static, + // E: Into + 'static, + // { + // let req = self.finish(); + // let fut = h(req.clone()); - let pool = RequestPool::pool(ServerSettings::default()); - let mut req = RequestPool::get(pool); - { - let inner = req.inner_mut(); - inner.method = method; - inner.url = InnerUrl::new(uri); - inner.version = version; - inner.headers = headers; - *inner.payload.borrow_mut() = payload; - } - req - } + // let mut sys = System::new("test"); + // match sys.block_on(fut) { + // Ok(r) => match r.respond_to(&req) { + // Ok(reply) => match reply.into().into() { + // AsyncResultItem::Ok(resp) => Ok(resp), + // _ => panic!("Nested async replies are not supported"), + // }, + // Err(e) => Err(e), + // }, + // Err(err) => Err(err), + // } + // } - /// This method generates `HttpRequest` instance and runs handler - /// with generated request. - pub fn run>(self, h: &H) -> Result { - let req = self.finish(); - let resp = h.handle(&req); + // /// This method generates `HttpRequest` instance and executes handler + // pub fn run_async_result(self, f: F) -> Result + // where + // F: FnOnce(&HttpRequest) -> R, + // R: Into>, + // { + // let req = self.finish(); + // let res = f(&req); - match resp.respond_to(&req) { - Ok(resp) => match resp.into().into() { - AsyncResultItem::Ok(resp) => Ok(resp), - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Future(fut) => { - let mut sys = System::new("test"); - sys.block_on(fut) - } - }, - Err(err) => Err(err.into()), - } - } + // match res.into().into() { + // AsyncResultItem::Ok(resp) => Ok(resp), + // AsyncResultItem::Err(err) => Err(err), + // AsyncResultItem::Future(fut) => { + // let mut sys = System::new("test"); + // sys.block_on(fut) + // } + // } + // } - /// This method generates `HttpRequest` instance and runs handler - /// with generated request. - /// - /// This method panics is handler returns actor. - pub fn run_async(self, h: H) -> Result - where - H: Fn(HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - { - let req = self.finish(); - let fut = h(req.clone()); + // /// This method generates `HttpRequest` instance and executes handler + // pub fn execute(self, f: F) -> Result + // where + // F: FnOnce(&HttpRequest) -> R, + // R: Responder + 'static, + // { + // let req = self.finish(); + // let resp = f(&req); - let mut sys = System::new("test"); - match sys.block_on(fut) { - Ok(r) => match r.respond_to(&req) { - Ok(reply) => match reply.into().into() { - AsyncResultItem::Ok(resp) => Ok(resp), - _ => panic!("Nested async replies are not supported"), - }, - Err(e) => Err(e), - }, - Err(err) => Err(err), - } - } - - /// This method generates `HttpRequest` instance and executes handler - pub fn run_async_result(self, f: F) -> Result - where - F: FnOnce(&HttpRequest) -> R, - R: Into>, - { - let req = self.finish(); - let res = f(&req); - - match res.into().into() { - AsyncResultItem::Ok(resp) => Ok(resp), - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Future(fut) => { - let mut sys = System::new("test"); - sys.block_on(fut) - } - } - } - - /// This method generates `HttpRequest` instance and executes handler - pub fn execute(self, f: F) -> Result - where - F: FnOnce(&HttpRequest) -> R, - R: Responder + 'static, - { - let req = self.finish(); - let resp = f(&req); - - match resp.respond_to(&req) { - Ok(resp) => match resp.into().into() { - AsyncResultItem::Ok(resp) => Ok(resp), - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Future(fut) => { - let mut sys = System::new("test"); - sys.block_on(fut) - } - }, - Err(err) => Err(err.into()), - } - } + // match resp.respond_to(&req) { + // Ok(resp) => match resp.into().into() { + // AsyncResultItem::Ok(resp) => Ok(resp), + // AsyncResultItem::Err(err) => Err(err), + // AsyncResultItem::Future(fut) => { + // let mut sys = System::new("test"); + // sys.block_on(fut) + // } + // }, + // Err(err) => Err(err.into()), + // } + // } } diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs index e32481bc2..d06777b75 100644 --- a/tests/test_h1v2.rs +++ b/tests/test_h1v2.rs @@ -8,7 +8,6 @@ use std::thread; use actix::System; use actix_net::server::Server; -use actix_net::service::{IntoNewService, IntoService}; use actix_web::{client, test}; use futures::future; From 99a915e66870bc144aaab16eaf404c1f250f9288 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 21:15:24 -0700 Subject: [PATCH 006/208] disable gh-pages update --- .travis.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 62867e030..aa8a44f25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,12 +43,12 @@ script: fi # Upload docs -after_success: - - | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then - cargo doc --features "ssl,tls,rust-tls,session" --no-deps && - echo "" > target/doc/index.html && - git clone https://github.com/davisp/ghp-import.git && - ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && - echo "Uploaded documentation" - fi +#after_success: +# - | +# if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then +# cargo doc --features "ssl,tls,rust-tls,session" --no-deps && +# echo "" > target/doc/index.html && +# git clone https://github.com/davisp/ghp-import.git && +# ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && +# echo "Uploaded documentation" +# fi From df50e636f19ee864e15ba38e404845f8fb88b9e2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 21:18:36 -0700 Subject: [PATCH 007/208] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b092a1723..74024eb5a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Actix http [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/fafhrd91/actix-http.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix http From e78014c65a0971c87e7913c8bf6b8aa46edf1ebd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 21:19:43 -0700 Subject: [PATCH 008/208] fix travis link in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 74024eb5a..7ddd532e8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Actix http [![Build Status](https://travis-ci.org/fafhrd91/actix-http.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/fafhrd91/actix-http.svg?branch=master)](https://travis-ci.org/fafhrd91/actix-http) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix http From 7fdc18f9b9e278e6b64c14ddf3bff1a1f96e3c94 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 23:39:11 -0700 Subject: [PATCH 009/208] calculate response parameters --- src/config.rs | 36 +-- src/error.rs | 4 +- src/h1/codec.rs | 36 ++- src/h1/dispatcher.rs | 89 +++++++- src/lib.rs | 2 +- src/server/output.rs | 530 +++++-------------------------------------- 6 files changed, 194 insertions(+), 503 deletions(-) diff --git a/src/config.rs b/src/config.rs index 543e78acd..36b949c33 100644 --- a/src/config.rs +++ b/src/config.rs @@ -21,7 +21,7 @@ pub struct ServiceConfig(Rc); struct Inner { keep_alive: Option, client_timeout: u64, - client_shutdown: u64, + client_disconnect: u64, ka_enabled: bool, date: UnsafeCell<(bool, Date)>, } @@ -35,7 +35,7 @@ impl Clone for ServiceConfig { impl ServiceConfig { /// Create instance of `ServiceConfig` pub(crate) fn new( - keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64, + keep_alive: KeepAlive, client_timeout: u64, client_disconnect: u64, ) -> ServiceConfig { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), @@ -52,7 +52,7 @@ impl ServiceConfig { keep_alive, ka_enabled, client_timeout, - client_shutdown, + client_disconnect, date: UnsafeCell::new((false, Date::new())), })) } @@ -100,9 +100,9 @@ impl ServiceConfig { } } - /// Client shutdown timer - pub fn client_shutdown_timer(&self) -> Option { - let delay = self.0.client_shutdown; + /// Client disconnect timer + pub fn client_disconnect_timer(&self) -> Option { + let delay = self.0.client_disconnect; if delay != 0 { Some(self.now() + Duration::from_millis(delay)) } else { @@ -184,7 +184,7 @@ impl ServiceConfig { pub struct ServiceConfigBuilder { keep_alive: KeepAlive, client_timeout: u64, - client_shutdown: u64, + client_disconnect: u64, host: String, addr: net::SocketAddr, secure: bool, @@ -196,7 +196,7 @@ impl ServiceConfigBuilder { ServiceConfigBuilder { keep_alive: KeepAlive::Timeout(5), client_timeout: 5000, - client_shutdown: 5000, + client_disconnect: 0, secure: false, host: "localhost".to_owned(), addr: "127.0.0.1:8080".parse().unwrap(), @@ -204,10 +204,14 @@ impl ServiceConfigBuilder { } /// Enable secure flag for current server. + /// This flags also enables `client disconnect timeout`. /// /// By default this flag is set to false. pub fn secure(mut self) -> Self { self.secure = true; + if self.client_disconnect == 0 { + self.client_disconnect = 3000; + } self } @@ -233,16 +237,16 @@ impl ServiceConfigBuilder { self } - /// Set server connection shutdown timeout in milliseconds. + /// Set server connection disconnect timeout in milliseconds. /// - /// Defines a timeout for shutdown connection. If a shutdown procedure does not complete - /// within this time, the request is dropped. This timeout affects only secure connections. + /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete + /// within this time, the request get dropped. This timeout affects secure connections. /// /// To disable timeout set value to 0. /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_shutdown(mut self, val: u64) -> Self { - self.client_shutdown = val; + /// By default disconnect timeout is set to 3000 milliseconds. + pub fn client_disconnect(mut self, val: u64) -> Self { + self.client_disconnect = val; self } @@ -277,9 +281,7 @@ impl ServiceConfigBuilder { /// Finish service configuration and create `ServiceConfig` object. pub fn finish(self) -> ServiceConfig { - let client_shutdown = if self.secure { self.client_shutdown } else { 0 }; - - ServiceConfig::new(self.keep_alive, self.client_timeout, client_shutdown) + ServiceConfig::new(self.keep_alive, self.client_timeout, self.client_disconnect) } } diff --git a/src/error.rs b/src/error.rs index 21aabac49..fb5df2328 100644 --- a/src/error.rs +++ b/src/error.rs @@ -397,9 +397,9 @@ pub enum DispatchError { // #[fail(display = "The first request did not complete within the specified timeout")] SlowRequestTimeout, - /// Shutdown timeout + /// Disconnect timeout. Makes sense for ssl streams. // #[fail(display = "Connection shutdown timeout")] - ShutdownTimeout, + DisconnectTimeout, /// Payload is not consumed // #[fail(display = "Task is completed but request's payload is not consumed")] diff --git a/src/h1/codec.rs b/src/h1/codec.rs index f1b526d52..ac54194ab 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -10,7 +10,7 @@ use body::Body; use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; -use http::Version; +use http::{Method, Version}; use httpresponse::HttpResponse; use request::RequestPool; use server::output::{ResponseInfo, ResponseLength}; @@ -27,6 +27,8 @@ pub enum OutMessage { pub struct Codec { decoder: H1Decoder, encoder: H1Writer, + head: bool, + version: Version, } impl Codec { @@ -40,6 +42,8 @@ impl Codec { Codec { decoder: H1Decoder::with_pool(pool), encoder: H1Writer::new(), + head: false, + version: Version::HTTP_11, } } } @@ -49,7 +53,17 @@ impl Decoder for Codec { type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - self.decoder.decode(src) + let res = self.decoder.decode(src); + + match res { + Ok(Some(InMessage::Message(ref req))) + | Ok(Some(InMessage::MessageWithPayload(ref req))) => { + self.head = req.inner.method == Method::HEAD; + self.version = req.inner.version; + } + _ => (), + } + res } } @@ -62,7 +76,7 @@ impl Encoder for Codec { ) -> Result<(), Self::Error> { match item { OutMessage::Response(res) => { - self.encoder.encode(res, dst)?; + self.encoder.encode(res, dst, self.head, self.version)?; } OutMessage::Payload(bytes) => { dst.extend_from_slice(&bytes); @@ -87,6 +101,7 @@ struct H1Writer { flags: Flags, written: u64, headers_size: u32, + info: ResponseInfo, } impl H1Writer { @@ -95,6 +110,7 @@ impl H1Writer { flags: Flags::empty(), written: 0, headers_size: 0, + info: ResponseInfo::default(), } } @@ -116,10 +132,11 @@ impl H1Writer { } fn encode( - &mut self, mut msg: HttpResponse, buffer: &mut BytesMut, + &mut self, mut msg: HttpResponse, buffer: &mut BytesMut, head: bool, + version: Version, ) -> io::Result<()> { // prepare task - let info = ResponseInfo::new(false); // req.inner.method == Method::HEAD); + self.info.update(&mut msg, head, version); //if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { //self.flags = Flags::STARTED | Flags::KEEPALIVE; @@ -166,7 +183,7 @@ impl H1Writer { buffer.extend_from_slice(reason); // content length - match info.length { + match self.info.length { ResponseLength::Chunked => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } @@ -183,11 +200,6 @@ impl H1Writer { } ResponseLength::None => buffer.extend_from_slice(b"\r\n"), } - if let Some(ce) = info.content_encoding { - buffer.extend_from_slice(b"content-encoding: "); - buffer.extend_from_slice(ce.as_ref()); - buffer.extend_from_slice(b"\r\n"); - } // write headers let mut pos = 0; @@ -197,7 +209,7 @@ impl H1Writer { for (key, value) in msg.headers() { match *key { TRANSFER_ENCODING => continue, - CONTENT_LENGTH => match info.length { + CONTENT_LENGTH => match self.info.length { ResponseLength::None => (), _ => continue, }, diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index eda8ebf00..f777648ec 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -1,7 +1,7 @@ // #![allow(unused_imports, unused_variables, dead_code)] use std::collections::VecDeque; use std::fmt::{Debug, Display}; -// use std::time::{Duration, Instant}; +use std::time::Instant; use actix_net::service::Service; @@ -9,7 +9,7 @@ use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; use tokio_codec::Framed; // use tokio_current_thread::spawn; use tokio_io::{AsyncRead, AsyncWrite}; -// use tokio_timer::Delay; +use tokio_timer::Delay; use error::{ParseError, PayloadError}; use payload::{Payload, PayloadStatus, PayloadWriter}; @@ -47,12 +47,14 @@ where flags: Flags, framed: Framed, error: Option>, + config: ServiceConfig, state: State, payload: Option, messages: VecDeque, - config: ServiceConfig, + ka_expire: Instant, + ka_timer: Option, } enum State { @@ -81,9 +83,28 @@ where { /// Create http/1 dispatcher. pub fn new(stream: T, config: ServiceConfig, service: S) -> Self { - let flags = Flags::FLUSHED; + Dispatcher::with_timeout(stream, config, None, service) + } + + /// Create http/1 dispatcher with slow request timeout. + pub fn with_timeout( + stream: T, config: ServiceConfig, timeout: Option, service: S, + ) -> Self { + let flags = if config.keep_alive_enabled() { + Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED | Flags::FLUSHED + } else { + Flags::FLUSHED + }; let framed = Framed::new(stream, Codec::new()); + let (ka_expire, ka_timer) = if let Some(delay) = timeout { + (delay.deadline(), Some(delay)) + } else if let Some(delay) = config.keep_alive_timer() { + (delay.deadline(), Some(delay)) + } else { + (config.now(), None) + }; + Dispatcher { payload: None, state: State::None, @@ -93,6 +114,8 @@ where flags, framed, config, + ka_expire, + ka_timer, } } @@ -358,8 +381,64 @@ where } } + if self.ka_timer.is_some() && updated { + if let Some(expire) = self.config.keep_alive_expire() { + self.ka_expire = expire; + } + } Ok(updated) } + + /// keep-alive timer + fn poll_keepalive(&mut self) -> Result<(), DispatchError> { + if let Some(ref mut timer) = self.ka_timer { + match timer.poll() { + Ok(Async::Ready(_)) => { + if timer.deadline() >= self.ka_expire { + // check for any outstanding request handling + if self.state.is_empty() && self.messages.is_empty() { + // if we get timer during shutdown, just drop connection + if self.flags.contains(Flags::SHUTDOWN) { + return Err(DispatchError::DisconnectTimeout); + } else if !self.flags.contains(Flags::STARTED) { + // timeout on first request (slow request) return 408 + trace!("Slow request timeout"); + self.flags + .insert(Flags::STARTED | Flags::READ_DISCONNECTED); + self.state = + State::SendResponse(Some(OutMessage::Response( + HttpResponse::RequestTimeout().finish(), + ))); + } else { + trace!("Keep-alive timeout, close connection"); + self.flags.insert(Flags::SHUTDOWN); + + // start shutdown timer + if let Some(deadline) = + self.config.client_disconnect_timer() + { + timer.reset(deadline) + } else { + return Ok(()); + } + } + } else if let Some(deadline) = self.config.keep_alive_expire() { + timer.reset(deadline) + } + } else { + timer.reset(self.ka_expire) + } + } + Ok(Async::NotReady) => (), + Err(e) => { + error!("Timer error {:?}", e); + return Err(DispatchError::Unknown); + } + } + } + + Ok(()) + } } impl Future for Dispatcher @@ -373,6 +452,8 @@ where #[inline] fn poll(&mut self) -> Poll<(), Self::Error> { + self.poll_keepalive()?; + // shutdown if self.flags.contains(Flags::SHUTDOWN) { if self.flags.contains(Flags::WRITE_DISCONNECTED) { diff --git a/src/lib.rs b/src/lib.rs index b9f90c7a2..6215bc4fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,7 +79,7 @@ //! #![cfg_attr(actix_nightly, feature(tool_lints))] // #![warn(missing_docs)] -// #![allow(unused_imports, unused_variables, dead_code)] +#![allow(dead_code)] extern crate actix; extern crate actix_net; diff --git a/src/server/output.rs b/src/server/output.rs index cfc85e4bc..5fc6fc839 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -4,26 +4,15 @@ use std::io::Write; use std::str::FromStr; use std::{cmp, fmt, io, mem}; -#[cfg(feature = "brotli")] -use brotli2::write::BrotliEncoder; -use bytes::BytesMut; -#[cfg(feature = "flate2")] -use flate2::write::{GzEncoder, ZlibEncoder}; -#[cfg(feature = "flate2")] -use flate2::Compression; +use bytes::{Bytes, BytesMut}; use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; use body::{Binary, Body}; use header::ContentEncoding; +use http::Method; use httpresponse::HttpResponse; -use request::InnerRequest; - -// #[derive(Debug)] -// pub(crate) struct RequestInfo { -// pub version: Version, -// pub accept_encoding: Option, -// } +use request::Request; #[derive(Debug)] pub(crate) enum ResponseLength { @@ -38,285 +27,91 @@ pub(crate) enum ResponseLength { pub(crate) struct ResponseInfo { head: bool, pub length: ResponseLength, - pub content_encoding: Option<&'static str>, + pub te: TransferEncoding, +} + +impl Default for ResponseInfo { + fn default() -> Self { + ResponseInfo { + head: false, + length: ResponseLength::None, + te: TransferEncoding::empty(), + } + } } impl ResponseInfo { - pub fn new(head: bool) -> Self { - ResponseInfo { - head, - length: ResponseLength::None, - content_encoding: None, - } - } -} + pub fn update(&mut self, resp: &mut HttpResponse, head: bool, version: Version) { + self.head = head; -#[derive(Debug)] -pub(crate) enum Output { - Empty(BytesMut), - Buffer(BytesMut), - Encoder(ContentEncoder), - TE(TransferEncoding), - Done, -} - -impl Output { - pub fn take(&mut self) -> BytesMut { - match mem::replace(self, Output::Done) { - Output::Empty(bytes) => bytes, - Output::Buffer(bytes) => bytes, - Output::Encoder(mut enc) => enc.take_buf(), - Output::TE(mut te) => te.take(), - Output::Done => panic!(), - } - } - - pub fn take_option(&mut self) -> Option { - match mem::replace(self, Output::Done) { - Output::Empty(bytes) => Some(bytes), - Output::Buffer(bytes) => Some(bytes), - Output::Encoder(mut enc) => Some(enc.take_buf()), - Output::TE(mut te) => Some(te.take()), - Output::Done => None, - } - } - - pub fn as_ref(&mut self) -> &BytesMut { - match self { - Output::Empty(ref mut bytes) => bytes, - Output::Buffer(ref mut bytes) => bytes, - Output::Encoder(ref mut enc) => enc.buf_ref(), - Output::TE(ref mut te) => te.buf_ref(), - Output::Done => panic!(), - } - } - pub fn as_mut(&mut self) -> &mut BytesMut { - match self { - Output::Empty(ref mut bytes) => bytes, - Output::Buffer(ref mut bytes) => bytes, - Output::Encoder(ref mut enc) => enc.buf_mut(), - Output::TE(ref mut te) => te.buf_mut(), - Output::Done => panic!(), - } - } - pub fn split_to(&mut self, cap: usize) -> BytesMut { - match self { - Output::Empty(ref mut bytes) => bytes.split_to(cap), - Output::Buffer(ref mut bytes) => bytes.split_to(cap), - Output::Encoder(ref mut enc) => enc.buf_mut().split_to(cap), - Output::TE(ref mut te) => te.buf_mut().split_to(cap), - Output::Done => BytesMut::new(), - } - } - - pub fn len(&self) -> usize { - match self { - Output::Empty(ref bytes) => bytes.len(), - Output::Buffer(ref bytes) => bytes.len(), - Output::Encoder(ref enc) => enc.len(), - Output::TE(ref te) => te.len(), - Output::Done => 0, - } - } - - pub fn is_empty(&self) -> bool { - match self { - Output::Empty(ref bytes) => bytes.is_empty(), - Output::Buffer(ref bytes) => bytes.is_empty(), - Output::Encoder(ref enc) => enc.is_empty(), - Output::TE(ref te) => te.is_empty(), - Output::Done => true, - } - } - - pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { - match self { - Output::Buffer(ref mut bytes) => { - bytes.extend_from_slice(data); - Ok(()) - } - Output::Encoder(ref mut enc) => enc.write(data), - Output::TE(ref mut te) => te.encode(data).map(|_| ()), - Output::Empty(_) | Output::Done => Ok(()), - } - } - - pub fn write_eof(&mut self) -> Result { - match self { - Output::Buffer(_) => Ok(true), - Output::Encoder(ref mut enc) => enc.write_eof(), - Output::TE(ref mut te) => Ok(te.encode_eof()), - Output::Empty(_) | Output::Done => Ok(true), - } - } - - pub(crate) fn for_server( - &mut self, info: &mut ResponseInfo, req: &InnerRequest, resp: &mut HttpResponse, - response_encoding: ContentEncoding, - ) { - let buf = self.take(); - let version = resp.version().unwrap_or_else(|| req.version); + let version = resp.version().unwrap_or_else(|| version); let mut len = 0; let has_body = match resp.body() { Body::Empty => false, Body::Binary(ref bin) => { len = bin.len(); - !(response_encoding == ContentEncoding::Auto && len < 96) + true } _ => true, }; - // Enable content encoding only if response does not contain Content-Encoding - // header - #[cfg(any(feature = "brotli", feature = "flate2"))] - let mut encoding = if has_body { - let encoding = match response_encoding { - ContentEncoding::Auto => { - // negotiate content-encoding - if let Some(val) = req.headers.get(ACCEPT_ENCODING) { - if let Ok(enc) = val.to_str() { - AcceptEncoding::parse(enc) - } else { - ContentEncoding::Identity - } - } else { - ContentEncoding::Identity - } - } - encoding => encoding, - }; - if encoding.is_compression() { - info.content_encoding = Some(encoding.as_str()); - } - encoding - } else { - ContentEncoding::Identity + let has_body = match resp.body() { + Body::Empty => false, + _ => true, }; - #[cfg(not(any(feature = "brotli", feature = "flate2")))] - let mut encoding = ContentEncoding::Identity; let transfer = match resp.body() { Body::Empty => { - if !info.head { - info.length = match resp.status() { + if !self.head { + self.length = match resp.status() { StatusCode::NO_CONTENT | StatusCode::CONTINUE | StatusCode::SWITCHING_PROTOCOLS | StatusCode::PROCESSING => ResponseLength::None, _ => ResponseLength::Zero, }; + } else { + self.length = ResponseLength::Zero; } - *self = Output::Empty(buf); - return; + TransferEncoding::empty() } Body::Binary(_) => { - #[cfg(any(feature = "brotli", feature = "flate2"))] - { - if !(encoding == ContentEncoding::Identity - || encoding == ContentEncoding::Auto) - { - let mut tmp = BytesMut::new(); - let mut transfer = TransferEncoding::eof(tmp); - let mut enc = match encoding { - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => ContentEncoder::Deflate( - ZlibEncoder::new(transfer, Compression::fast()), - ), - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => ContentEncoder::Gzip( - GzEncoder::new(transfer, Compression::fast()), - ), - #[cfg(feature = "brotli")] - ContentEncoding::Br => { - ContentEncoder::Br(BrotliEncoder::new(transfer, 3)) - } - ContentEncoding::Identity | ContentEncoding::Auto => { - unreachable!() - } - }; - - let bin = resp.replace_body(Body::Empty).binary(); - - // TODO return error! - let _ = enc.write(bin.as_ref()); - let _ = enc.write_eof(); - let body = enc.buf_mut().take(); - len = body.len(); - resp.replace_body(Binary::from(body)); - } - } - - info.length = ResponseLength::Length(len); - if info.head { - *self = Output::Empty(buf); - } else { - *self = Output::Buffer(buf); - } - return; + self.length = ResponseLength::Length(len); + TransferEncoding::length(len as u64) } Body::Streaming(_) => { if resp.upgrade() { - if version == Version::HTTP_2 { - error!("Connection upgrade is forbidden for HTTP/2"); - } - if encoding != ContentEncoding::Identity { - encoding = ContentEncoding::Identity; - info.content_encoding.take(); - } - TransferEncoding::eof(buf) + self.length = ResponseLength::None; + TransferEncoding::eof() } else { - if !(encoding == ContentEncoding::Identity - || encoding == ContentEncoding::Auto) - { - resp.headers_mut().remove(CONTENT_LENGTH); - } - Output::streaming_encoding(info, buf, version, resp) + self.streaming_encoding(version, resp) } } }; // check for head response - if info.head { + if self.head { resp.set_body(Body::Empty); - *self = Output::Empty(transfer.buf.unwrap()); - return; + } else { + self.te = transfer; } - - let enc = match encoding { - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => { - ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::fast())) - } - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => { - ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast())) - } - #[cfg(feature = "brotli")] - ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 3)), - ContentEncoding::Identity | ContentEncoding::Auto => { - *self = Output::TE(transfer); - return; - } - }; - *self = Output::Encoder(enc); } fn streaming_encoding( - info: &mut ResponseInfo, buf: BytesMut, version: Version, - resp: &mut HttpResponse, + &mut self, version: Version, resp: &mut HttpResponse, ) -> TransferEncoding { match resp.chunked() { Some(true) => { // Enable transfer encoding if version == Version::HTTP_2 { - info.length = ResponseLength::None; - TransferEncoding::eof(buf) + self.length = ResponseLength::None; + TransferEncoding::eof() } else { - info.length = ResponseLength::Chunked; - TransferEncoding::chunked(buf) + self.length = ResponseLength::Chunked; + TransferEncoding::chunked() } } - Some(false) => TransferEncoding::eof(buf), + Some(false) => TransferEncoding::eof(), None => { // if Content-Length is specified, then use it as length hint let (len, chunked) = @@ -339,21 +134,21 @@ impl Output { if !chunked { if let Some(len) = len { - info.length = ResponseLength::Length64(len); - TransferEncoding::length(len, buf) + self.length = ResponseLength::Length64(len); + TransferEncoding::length(len) } else { - TransferEncoding::eof(buf) + TransferEncoding::eof() } } else { // Enable transfer encoding match version { Version::HTTP_11 => { - info.length = ResponseLength::Chunked; - TransferEncoding::chunked(buf) + self.length = ResponseLength::Chunked; + TransferEncoding::chunked() } _ => { - info.length = ResponseLength::None; - TransferEncoding::eof(buf) + self.length = ResponseLength::None; + TransferEncoding::eof() } } } @@ -362,178 +157,9 @@ impl Output { } } -pub(crate) enum ContentEncoder { - #[cfg(feature = "flate2")] - Deflate(ZlibEncoder), - #[cfg(feature = "flate2")] - Gzip(GzEncoder), - #[cfg(feature = "brotli")] - Br(BrotliEncoder), - Identity(TransferEncoding), -} - -impl fmt::Debug for ContentEncoder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(_) => writeln!(f, "ContentEncoder(Brotli)"), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(_) => writeln!(f, "ContentEncoder(Deflate)"), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(_) => writeln!(f, "ContentEncoder(Gzip)"), - ContentEncoder::Identity(_) => writeln!(f, "ContentEncoder(Identity)"), - } - } -} - -impl ContentEncoder { - #[inline] - pub fn len(&self) -> usize { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref encoder) => encoder.get_ref().len(), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref encoder) => encoder.get_ref().len(), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref encoder) => encoder.get_ref().len(), - ContentEncoder::Identity(ref encoder) => encoder.len(), - } - } - - #[inline] - pub fn is_empty(&self) -> bool { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref encoder) => encoder.get_ref().is_empty(), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_empty(), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_empty(), - ContentEncoder::Identity(ref encoder) => encoder.is_empty(), - } - } - - #[inline] - pub(crate) fn take_buf(&mut self) -> BytesMut { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(), - ContentEncoder::Identity(ref mut encoder) => encoder.take(), - } - } - - #[inline] - pub(crate) fn buf_mut(&mut self) -> &mut BytesMut { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_mut(), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().buf_mut(), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().buf_mut(), - ContentEncoder::Identity(ref mut encoder) => encoder.buf_mut(), - } - } - - #[inline] - pub(crate) fn buf_ref(&mut self) -> &BytesMut { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_ref(), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().buf_ref(), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().buf_ref(), - ContentEncoder::Identity(ref mut encoder) => encoder.buf_ref(), - } - } - - #[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))] - #[inline(always)] - pub fn write_eof(&mut self) -> Result { - let encoder = - mem::replace(self, ContentEncoder::Identity(TransferEncoding::empty())); - - match encoder { - #[cfg(feature = "brotli")] - ContentEncoder::Br(encoder) => match encoder.finish() { - Ok(mut writer) => { - writer.encode_eof(); - *self = ContentEncoder::Identity(writer); - Ok(true) - } - Err(err) => Err(err), - }, - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(encoder) => match encoder.finish() { - Ok(mut writer) => { - writer.encode_eof(); - *self = ContentEncoder::Identity(writer); - Ok(true) - } - Err(err) => Err(err), - }, - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(encoder) => match encoder.finish() { - Ok(mut writer) => { - writer.encode_eof(); - *self = ContentEncoder::Identity(writer); - Ok(true) - } - Err(err) => Err(err), - }, - ContentEncoder::Identity(mut writer) => { - let res = writer.encode_eof(); - *self = ContentEncoder::Identity(writer); - Ok(res) - } - } - } - - #[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))] - #[inline(always)] - pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(()), - Err(err) => { - trace!("Error decoding br encoding: {}", err); - Err(err) - } - }, - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(()), - Err(err) => { - trace!("Error decoding gzip encoding: {}", err); - Err(err) - } - }, - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(()), - Err(err) => { - trace!("Error decoding deflate encoding: {}", err); - Err(err) - } - }, - ContentEncoder::Identity(ref mut encoder) => { - encoder.encode(data)?; - Ok(()) - } - } - } -} - /// Encoders to handle different Transfer-Encodings. #[derive(Debug)] pub(crate) struct TransferEncoding { - buf: Option, kind: TransferEncodingKind, } @@ -552,65 +178,41 @@ enum TransferEncodingKind { } impl TransferEncoding { - fn take(&mut self) -> BytesMut { - self.buf.take().unwrap() - } - - fn buf_ref(&mut self) -> &BytesMut { - self.buf.as_ref().unwrap() - } - - fn len(&self) -> usize { - self.buf.as_ref().unwrap().len() - } - - fn is_empty(&self) -> bool { - self.buf.as_ref().unwrap().is_empty() - } - - fn buf_mut(&mut self) -> &mut BytesMut { - self.buf.as_mut().unwrap() - } - #[inline] pub fn empty() -> TransferEncoding { TransferEncoding { - buf: None, kind: TransferEncodingKind::Eof, } } #[inline] - pub fn eof(buf: BytesMut) -> TransferEncoding { + pub fn eof() -> TransferEncoding { TransferEncoding { - buf: Some(buf), kind: TransferEncodingKind::Eof, } } #[inline] - pub fn chunked(buf: BytesMut) -> TransferEncoding { + pub fn chunked() -> TransferEncoding { TransferEncoding { - buf: Some(buf), kind: TransferEncodingKind::Chunked(false), } } #[inline] - pub fn length(len: u64, buf: BytesMut) -> TransferEncoding { + pub fn length(len: u64) -> TransferEncoding { TransferEncoding { - buf: Some(buf), kind: TransferEncodingKind::Length(len), } } /// Encode message. Return `EOF` state of encoder #[inline] - pub fn encode(&mut self, msg: &[u8]) -> io::Result { + pub fn encode(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result { match self.kind { TransferEncodingKind::Eof => { let eof = msg.is_empty(); - self.buf.as_mut().unwrap().extend_from_slice(msg); + buf.extend_from_slice(msg); Ok(eof) } TransferEncodingKind::Chunked(ref mut eof) => { @@ -620,17 +222,14 @@ impl TransferEncoding { if msg.is_empty() { *eof = true; - self.buf.as_mut().unwrap().extend_from_slice(b"0\r\n\r\n"); + buf.extend_from_slice(b"0\r\n\r\n"); } else { - let mut buf = BytesMut::new(); - writeln!(&mut buf, "{:X}\r", msg.len()) + writeln!(buf.as_mut(), "{:X}\r", msg.len()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - let b = self.buf.as_mut().unwrap(); - b.reserve(buf.len() + msg.len() + 2); - b.extend_from_slice(buf.as_ref()); - b.extend_from_slice(msg); - b.extend_from_slice(b"\r\n"); + buf.reserve(msg.len() + 2); + buf.extend_from_slice(msg); + buf.extend_from_slice(b"\r\n"); } Ok(*eof) } @@ -641,10 +240,7 @@ impl TransferEncoding { } let len = cmp::min(*remaining, msg.len() as u64); - self.buf - .as_mut() - .unwrap() - .extend_from_slice(&msg[..len as usize]); + buf.extend_from_slice(&msg[..len as usize]); *remaining -= len as u64; Ok(*remaining == 0) @@ -657,14 +253,14 @@ impl TransferEncoding { /// Encode eof. Return `EOF` state of encoder #[inline] - pub fn encode_eof(&mut self) -> bool { + pub fn encode_eof(&mut self, buf: &mut BytesMut) -> bool { match self.kind { TransferEncodingKind::Eof => true, TransferEncodingKind::Length(rem) => rem == 0, TransferEncodingKind::Chunked(ref mut eof) => { if !*eof { *eof = true; - self.buf.as_mut().unwrap().extend_from_slice(b"0\r\n\r\n"); + buf.extend_from_slice(b"0\r\n\r\n"); } true } @@ -675,9 +271,9 @@ impl TransferEncoding { impl io::Write for TransferEncoding { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { - if self.buf.is_some() { - self.encode(buf)?; - } + // if self.buf.is_some() { + // self.encode(buf)?; + // } Ok(buf.len()) } From caa5a54b8f965484d2fb1fb5584f9a6047dc57b8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 23:46:43 -0700 Subject: [PATCH 010/208] fix test and remove unused code --- src/h1/decoder.rs | 8 +- src/httprequest.rs | 545 ------------------------------------------- src/server/output.rs | 89 +------ 3 files changed, 12 insertions(+), 630 deletions(-) delete mode 100644 src/httprequest.rs diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 66f24628c..90946b453 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -510,21 +510,17 @@ impl ChunkedState { #[cfg(test)] mod tests { - use std::net::Shutdown; - use std::{cmp, io, time}; + use std::{cmp, io}; - use actix::System; use bytes::{Buf, Bytes, BytesMut}; - use futures::{future, future::ok}; use http::{Method, Version}; use tokio_io::{AsyncRead, AsyncWrite}; use super::*; use error::ParseError; - use h1::{Dispatcher, InMessage}; + use h1::InMessage; use httpmessage::HttpMessage; use request::Request; - use server::KeepAlive; impl InMessage { fn message(self) -> Request { diff --git a/src/httprequest.rs b/src/httprequest.rs deleted file mode 100644 index d8c49496a..000000000 --- a/src/httprequest.rs +++ /dev/null @@ -1,545 +0,0 @@ -//! HTTP Request message related code. -use std::cell::{Ref, RefMut}; -use std::collections::HashMap; -use std::net::SocketAddr; -use std::ops::Deref; -use std::rc::Rc; -use std::{fmt, str}; - -use cookie::Cookie; -use futures_cpupool::CpuPool; -use http::{header, HeaderMap, Method, StatusCode, Uri, Version}; -use url::{form_urlencoded, Url}; - -use body::Body; -use error::{CookieParseError, UrlGenerationError}; -use extensions::Extensions; -use handler::FromRequest; -use httpmessage::HttpMessage; -use httpresponse::{HttpResponse, HttpResponseBuilder}; -use info::ConnectionInfo; -use param::Params; -use payload::Payload; -use router::ResourceInfo; -use server::Request; - -struct Query(HashMap); -struct Cookies(Vec>); - -/// An HTTP Request -pub struct HttpRequest { - req: Option, - state: Rc, - resource: ResourceInfo, -} - -impl HttpMessage for HttpRequest { - type Stream = Payload; - - #[inline] - fn headers(&self) -> &HeaderMap { - self.request().headers() - } - - #[inline] - fn payload(&self) -> Payload { - if let Some(payload) = self.request().inner.payload.borrow_mut().take() { - payload - } else { - Payload::empty() - } - } -} - -impl Deref for HttpRequest { - type Target = Request; - - fn deref(&self) -> &Request { - self.request() - } -} - -impl HttpRequest { - #[inline] - pub(crate) fn new( - req: Request, state: Rc, resource: ResourceInfo, - ) -> HttpRequest { - HttpRequest { - state, - resource, - req: Some(req), - } - } - - #[inline] - /// Construct new http request with state. - pub(crate) fn with_state(&self, state: Rc) -> HttpRequest { - HttpRequest { - state, - req: self.req.as_ref().map(|r| r.clone()), - resource: self.resource.clone(), - } - } - - /// Construct new http request with empty state. - pub fn drop_state(&self) -> HttpRequest { - HttpRequest { - state: Rc::new(()), - req: self.req.as_ref().map(|r| r.clone()), - resource: self.resource.clone(), - } - } - - #[inline] - /// Construct new http request with new RouteInfo. - pub(crate) fn with_route_info(&self, mut resource: ResourceInfo) -> HttpRequest { - resource.merge(&self.resource); - - HttpRequest { - resource, - req: self.req.as_ref().map(|r| r.clone()), - state: self.state.clone(), - } - } - - /// Shared application state - #[inline] - pub fn state(&self) -> &S { - &self.state - } - - #[inline] - /// Server request - pub fn request(&self) -> &Request { - self.req.as_ref().unwrap() - } - - /// Request extensions - #[inline] - pub fn extensions(&self) -> Ref { - self.request().extensions() - } - - /// Mutable reference to a the request's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut { - self.request().extensions_mut() - } - - /// Default `CpuPool` - #[inline] - #[doc(hidden)] - pub fn cpu_pool(&self) -> &CpuPool { - self.request().server_settings().cpu_pool() - } - - #[inline] - /// Create http response - pub fn response(&self, status: StatusCode, body: Body) -> HttpResponse { - self.request().server_settings().get_response(status, body) - } - - #[inline] - /// Create http response builder - pub fn build_response(&self, status: StatusCode) -> HttpResponseBuilder { - self.request() - .server_settings() - .get_response_builder(status) - } - - /// Read the Request Uri. - #[inline] - pub fn uri(&self) -> &Uri { - self.request().inner.url.uri() - } - - /// Read the Request method. - #[inline] - pub fn method(&self) -> &Method { - &self.request().inner.method - } - - /// Read the Request Version. - #[inline] - pub fn version(&self) -> Version { - self.request().inner.version - } - - /// The target path of this Request. - #[inline] - pub fn path(&self) -> &str { - self.request().inner.url.path() - } - - /// Get *ConnectionInfo* for the correct request. - #[inline] - pub fn connection_info(&self) -> Ref { - self.request().connection_info() - } - - /// Generate url for named resource - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::{App, HttpRequest, HttpResponse, http}; - /// # - /// fn index(req: HttpRequest) -> HttpResponse { - /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource - /// HttpResponse::Ok().into() - /// } - /// - /// fn main() { - /// let app = App::new() - /// .resource("/test/{one}/{two}/{three}", |r| { - /// r.name("foo"); // <- set resource name, then it could be used in `url_for` - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// }) - /// .finish(); - /// } - /// ``` - pub fn url_for( - &self, name: &str, elements: U, - ) -> Result - where - U: IntoIterator, - I: AsRef, - { - self.resource.url_for(&self, name, elements) - } - - /// Generate url for named resource - /// - /// This method is similar to `HttpRequest::url_for()` but it can be used - /// for urls that do not contain variable parts. - pub fn url_for_static(&self, name: &str) -> Result { - const NO_PARAMS: [&str; 0] = []; - self.url_for(name, &NO_PARAMS) - } - - /// This method returns reference to current `RouteInfo` object. - #[inline] - pub fn resource(&self) -> &ResourceInfo { - &self.resource - } - - /// Peer socket address - /// - /// Peer address is actual socket address, if proxy is used in front of - /// actix http server, then peer address would be address of this proxy. - /// - /// To get client connection information `connection_info()` method should - /// be used. - #[inline] - pub fn peer_addr(&self) -> Option { - self.request().inner.addr - } - - /// url query parameters. - pub fn query(&self) -> Ref> { - if self.extensions().get::().is_none() { - let mut query = HashMap::new(); - for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) { - query.insert(key.as_ref().to_string(), val.to_string()); - } - self.extensions_mut().insert(Query(query)); - } - Ref::map(self.extensions(), |ext| &ext.get::().unwrap().0) - } - - /// The query string in the URL. - /// - /// E.g., id=10 - #[inline] - pub fn query_string(&self) -> &str { - if let Some(query) = self.uri().query().as_ref() { - query - } else { - "" - } - } - - /// Load request cookies. - #[inline] - pub fn cookies(&self) -> Result>>, CookieParseError> { - if self.extensions().get::().is_none() { - let mut cookies = Vec::new(); - for hdr in self.request().inner.headers.get_all(header::COOKIE) { - let s = - str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; - for cookie_str in s.split(';').map(|s| s.trim()) { - if !cookie_str.is_empty() { - cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned()); - } - } - } - self.extensions_mut().insert(Cookies(cookies)); - } - Ok(Ref::map(self.extensions(), |ext| { - &ext.get::().unwrap().0 - })) - } - - /// Return request cookie. - #[inline] - pub fn cookie(&self, name: &str) -> Option> { - if let Ok(cookies) = self.cookies() { - for cookie in cookies.iter() { - if cookie.name() == name { - return Some(cookie.to_owned()); - } - } - } - None - } - - pub(crate) fn set_cookies(&mut self, cookies: Option>>) { - if let Some(cookies) = cookies { - self.extensions_mut().insert(Cookies(cookies)); - } - } - - /// Get a reference to the Params object. - /// - /// Params is a container for url parameters. - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. - #[inline] - pub fn match_info(&self) -> &Params { - &self.resource.match_info() - } - - /// Check if request requires connection upgrade - pub(crate) fn upgrade(&self) -> bool { - self.request().upgrade() - } - - /// Set read buffer capacity - /// - /// Default buffer capacity is 32Kb. - pub fn set_read_buffer_capacity(&mut self, cap: usize) { - if let Some(payload) = self.request().inner.payload.borrow_mut().as_mut() { - payload.set_read_buffer_capacity(cap) - } - } -} - -impl Drop for HttpRequest { - fn drop(&mut self) { - if let Some(req) = self.req.take() { - req.release(); - } - } -} - -impl Clone for HttpRequest { - fn clone(&self) -> HttpRequest { - HttpRequest { - req: self.req.as_ref().map(|r| r.clone()), - state: self.state.clone(), - resource: self.resource.clone(), - } - } -} - -impl FromRequest for HttpRequest { - type Config = (); - type Result = Self; - - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - req.clone() - } -} - -impl fmt::Debug for HttpRequest { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!( - f, - "\nHttpRequest {:?} {}:{}", - self.version(), - self.method(), - self.path() - )?; - if !self.query_string().is_empty() { - writeln!(f, " query: ?{:?}", self.query_string())?; - } - if !self.match_info().is_empty() { - writeln!(f, " params: {:?}", self.match_info())?; - } - writeln!(f, " headers:")?; - for (key, val) in self.headers().iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use resource::Resource; - use router::{ResourceDef, Router}; - use test::TestRequest; - - #[test] - fn test_debug() { - let req = TestRequest::with_header("content-type", "text/plain").finish(); - let dbg = format!("{:?}", req); - assert!(dbg.contains("HttpRequest")); - } - - #[test] - fn test_no_request_cookies() { - let req = TestRequest::default().finish(); - assert!(req.cookies().unwrap().is_empty()); - } - - #[test] - fn test_request_cookies() { - let req = TestRequest::default() - .header(header::COOKIE, "cookie1=value1") - .header(header::COOKIE, "cookie2=value2") - .finish(); - { - let cookies = req.cookies().unwrap(); - assert_eq!(cookies.len(), 2); - assert_eq!(cookies[0].name(), "cookie1"); - assert_eq!(cookies[0].value(), "value1"); - assert_eq!(cookies[1].name(), "cookie2"); - assert_eq!(cookies[1].value(), "value2"); - } - - let cookie = req.cookie("cookie1"); - assert!(cookie.is_some()); - let cookie = cookie.unwrap(); - assert_eq!(cookie.name(), "cookie1"); - assert_eq!(cookie.value(), "value1"); - - let cookie = req.cookie("cookie-unknown"); - assert!(cookie.is_none()); - } - - #[test] - fn test_request_query() { - let req = TestRequest::with_uri("/?id=test").finish(); - assert_eq!(req.query_string(), "id=test"); - let query = req.query(); - assert_eq!(&query["id"], "test"); - } - - #[test] - fn test_request_match_info() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{key}/"))); - - let req = TestRequest::with_uri("/value/?id=test").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.match_info().get("key"), Some("value")); - } - - #[test] - fn test_url_for() { - let mut router = Router::<()>::default(); - let mut resource = Resource::new(ResourceDef::new("/user/{name}.{ext}")); - resource.name("index"); - router.register_resource(resource); - - let info = router.default_route_info(); - assert!(!info.has_prefixed_resource("/use/")); - assert!(info.has_resource("/user/test.html")); - assert!(info.has_prefixed_resource("/user/test.html")); - assert!(!info.has_resource("/test/unknown")); - assert!(!info.has_prefixed_resource("/test/unknown")); - - let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") - .finish_with_router(router); - - assert_eq!( - req.url_for("unknown", &["test"]), - Err(UrlGenerationError::ResourceNotFound) - ); - assert_eq!( - req.url_for("index", &["test"]), - Err(UrlGenerationError::NotEnoughElements) - ); - let url = req.url_for("index", &["test", "html"]); - assert_eq!( - url.ok().unwrap().as_str(), - "http://www.rust-lang.org/user/test.html" - ); - } - - #[test] - fn test_url_for_with_prefix() { - let mut resource = Resource::new(ResourceDef::new("/user/{name}.html")); - resource.name("index"); - let mut router = Router::<()>::default(); - router.set_prefix("/prefix"); - router.register_resource(resource); - - let mut info = router.default_route_info(); - info.set_prefix(7); - assert!(!info.has_prefixed_resource("/use/")); - assert!(info.has_resource("/user/test.html")); - assert!(!info.has_prefixed_resource("/user/test.html")); - assert!(!info.has_resource("/prefix/user/test.html")); - assert!(info.has_prefixed_resource("/prefix/user/test.html")); - - let req = TestRequest::with_uri("/prefix/test") - .prefix(7) - .header(header::HOST, "www.rust-lang.org") - .finish_with_router(router); - let url = req.url_for("index", &["test"]); - assert_eq!( - url.ok().unwrap().as_str(), - "http://www.rust-lang.org/prefix/user/test.html" - ); - } - - #[test] - fn test_url_for_static() { - let mut resource = Resource::new(ResourceDef::new("/index.html")); - resource.name("index"); - let mut router = Router::<()>::default(); - router.set_prefix("/prefix"); - router.register_resource(resource); - - let mut info = router.default_route_info(); - info.set_prefix(7); - assert!(info.has_resource("/index.html")); - assert!(!info.has_prefixed_resource("/index.html")); - assert!(!info.has_resource("/prefix/index.html")); - assert!(info.has_prefixed_resource("/prefix/index.html")); - - let req = TestRequest::with_uri("/prefix/test") - .prefix(7) - .header(header::HOST, "www.rust-lang.org") - .finish_with_router(router); - let url = req.url_for_static("index"); - assert_eq!( - url.ok().unwrap().as_str(), - "http://www.rust-lang.org/prefix/index.html" - ); - } - - #[test] - fn test_url_for_external() { - let mut router = Router::<()>::default(); - router.register_external( - "youtube", - ResourceDef::external("https://youtube.com/watch/{video_id}"), - ); - - let info = router.default_route_info(); - assert!(!info.has_resource("https://youtube.com/watch/unknown")); - assert!(!info.has_prefixed_resource("https://youtube.com/watch/unknown")); - - let req = TestRequest::default().finish_with_router(router); - let url = req.url_for("youtube", &["oHg5SJYRHA0"]); - assert_eq!( - url.ok().unwrap().as_str(), - "https://youtube.com/watch/oHg5SJYRHA0" - ); - } -} diff --git a/src/server/output.rs b/src/server/output.rs index 5fc6fc839..fc6886840 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -224,7 +224,7 @@ impl TransferEncoding { *eof = true; buf.extend_from_slice(b"0\r\n\r\n"); } else { - writeln!(buf.as_mut(), "{:X}\r", msg.len()) + writeln!(Writer(buf), "{:X}\r", msg.len()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; buf.reserve(msg.len() + 2); @@ -268,87 +268,18 @@ impl TransferEncoding { } } -impl io::Write for TransferEncoding { - #[inline] +struct Writer<'a>(pub &'a mut BytesMut); + +impl<'a> io::Write for Writer<'a> { fn write(&mut self, buf: &[u8]) -> io::Result { - // if self.buf.is_some() { - // self.encode(buf)?; - // } + self.0.extend_from_slice(buf); Ok(buf.len()) } - - #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } } -struct AcceptEncoding { - encoding: ContentEncoding, - quality: f64, -} - -impl Eq for AcceptEncoding {} - -impl Ord for AcceptEncoding { - fn cmp(&self, other: &AcceptEncoding) -> cmp::Ordering { - if self.quality > other.quality { - cmp::Ordering::Less - } else if self.quality < other.quality { - cmp::Ordering::Greater - } else { - cmp::Ordering::Equal - } - } -} - -impl PartialOrd for AcceptEncoding { - fn partial_cmp(&self, other: &AcceptEncoding) -> Option { - Some(self.cmp(other)) - } -} - -impl PartialEq for AcceptEncoding { - fn eq(&self, other: &AcceptEncoding) -> bool { - self.quality == other.quality - } -} - -impl AcceptEncoding { - fn new(tag: &str) -> Option { - let parts: Vec<&str> = tag.split(';').collect(); - let encoding = match parts.len() { - 0 => return None, - _ => ContentEncoding::from(parts[0]), - }; - let quality = match parts.len() { - 1 => encoding.quality(), - _ => match f64::from_str(parts[1]) { - Ok(q) => q, - Err(_) => 0.0, - }, - }; - Some(AcceptEncoding { encoding, quality }) - } - - /// Parse a raw Accept-Encoding header value into an ordered list. - pub fn parse(raw: &str) -> ContentEncoding { - let mut encodings: Vec<_> = raw - .replace(' ', "") - .split(',') - .map(|l| AcceptEncoding::new(l)) - .collect(); - encodings.sort(); - - for enc in encodings { - if let Some(enc) = enc { - return enc.encoding; - } - } - ContentEncoding::Identity - } -} - #[cfg(test)] mod tests { use super::*; @@ -356,14 +287,14 @@ mod tests { #[test] fn test_chunked_te() { - let bytes = BytesMut::new(); - let mut enc = TransferEncoding::chunked(bytes); + let mut bytes = BytesMut::new(); + let mut enc = TransferEncoding::chunked(); { - assert!(!enc.encode(b"test").ok().unwrap()); - assert!(enc.encode(b"").ok().unwrap()); + assert!(!enc.encode(b"test", &mut bytes).ok().unwrap()); + assert!(enc.encode(b"", &mut bytes).ok().unwrap()); } assert_eq!( - enc.buf_mut().take().freeze(), + bytes.take().freeze(), Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n") ); } From c99f9eaa63b87670fcf6cb5f15f859ed761bfdb8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 05:59:02 -0700 Subject: [PATCH 011/208] Update test_h1v2.rs --- tests/test_h1v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs index d06777b75..77a6ecae5 100644 --- a/tests/test_h1v2.rs +++ b/tests/test_h1v2.rs @@ -23,7 +23,7 @@ fn test_h1_v2() { let settings = ServiceConfig::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) - .client_shutdown(1000) + .client_disconnect(1000) .server_hostname("localhost") .server_address(addr) .finish(); From c24a8f4c2d121a02e828be52712b9d4b43004f29 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 07:02:09 -0700 Subject: [PATCH 012/208] remove high level apis --- Cargo.toml | 17 +- src/h1/codec.rs | 2 +- src/h1/dispatcher.rs | 7 +- src/h1/mod.rs | 1 + src/{server/output.rs => h1/response.rs} | 0 src/header.rs | 265 +++++++ src/header/common/accept.rs | 159 ---- src/header/common/accept_charset.rs | 69 -- src/header/common/accept_encoding.rs | 72 -- src/header/common/accept_language.rs | 75 -- src/header/common/allow.rs | 82 -- src/header/common/cache_control.rs | 254 ------- src/header/common/content_disposition.rs | 915 ----------------------- src/header/common/content_language.rs | 65 -- src/header/common/content_range.rs | 210 ------ src/header/common/content_type.rs | 122 --- src/header/common/date.rs | 42 -- src/header/common/etag.rs | 96 --- src/header/common/expires.rs | 39 - src/header/common/if_match.rs | 70 -- src/header/common/if_modified_since.rs | 39 - src/header/common/if_none_match.rs | 92 --- src/header/common/if_range.rs | 117 --- src/header/common/if_unmodified_since.rs | 40 - src/header/common/last_modified.rs | 38 - src/header/common/mod.rs | 350 --------- src/header/common/range.rs | 434 ----------- src/header/mod.rs | 471 ------------ src/header/shared/charset.rs | 152 ---- src/header/shared/encoding.rs | 59 -- src/header/shared/entity.rs | 266 ------- src/header/shared/httpdate.rs | 119 --- src/header/shared/mod.rs | 14 - src/header/shared/quality_item.rs | 294 -------- src/httpresponse.rs | 4 +- src/lib.rs | 13 +- src/server/input.rs | 288 ------- src/server/mod.rs | 3 - src/server/service.rs | 273 ------- src/ws/context.rs | 334 --------- 40 files changed, 273 insertions(+), 5689 deletions(-) rename src/{server/output.rs => h1/response.rs} (100%) create mode 100644 src/header.rs delete mode 100644 src/header/common/accept.rs delete mode 100644 src/header/common/accept_charset.rs delete mode 100644 src/header/common/accept_encoding.rs delete mode 100644 src/header/common/accept_language.rs delete mode 100644 src/header/common/allow.rs delete mode 100644 src/header/common/cache_control.rs delete mode 100644 src/header/common/content_disposition.rs delete mode 100644 src/header/common/content_language.rs delete mode 100644 src/header/common/content_range.rs delete mode 100644 src/header/common/content_type.rs delete mode 100644 src/header/common/date.rs delete mode 100644 src/header/common/etag.rs delete mode 100644 src/header/common/expires.rs delete mode 100644 src/header/common/if_match.rs delete mode 100644 src/header/common/if_modified_since.rs delete mode 100644 src/header/common/if_none_match.rs delete mode 100644 src/header/common/if_range.rs delete mode 100644 src/header/common/if_unmodified_since.rs delete mode 100644 src/header/common/last_modified.rs delete mode 100644 src/header/common/mod.rs delete mode 100644 src/header/common/range.rs delete mode 100644 src/header/mod.rs delete mode 100644 src/header/shared/charset.rs delete mode 100644 src/header/shared/encoding.rs delete mode 100644 src/header/shared/entity.rs delete mode 100644 src/header/shared/httpdate.rs delete mode 100644 src/header/shared/mod.rs delete mode 100644 src/header/shared/quality_item.rs delete mode 100644 src/server/input.rs delete mode 100644 src/server/service.rs delete mode 100644 src/ws/context.rs diff --git a/Cargo.toml b/Cargo.toml index 86012175f..870f772e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ name = "actix_http" path = "src/lib.rs" [features] -default = ["session", "brotli", "flate2-c", "cell"] +default = ["session", "cell"] # tls tls = ["native-tls", "tokio-tls", "actix-net/tls"] @@ -48,15 +48,6 @@ uds = ["tokio-uds"] # sessions feature, session require "ring" crate and c compiler session = ["cookie/secure"] -# brotli encoding, requires c compiler -brotli = ["brotli2"] - -# miniz-sys backend for flate2 crate -flate2-c = ["flate2/miniz-sys"] - -# rust backend for flate2 crate -flate2-rust = ["flate2/rust_backend"] - cell = ["actix-net/cell"] [dependencies] @@ -70,23 +61,17 @@ http = "^0.1.8" httparse = "1.3" log = "0.4" mime = "0.3" -mime_guess = "2.0.0-alpha" percent-encoding = "1.0" rand = "0.5" -regex = "1.0" serde = "1.0" serde_json = "1.0" sha1 = "0.6" -smallvec = "0.6" time = "0.1" encoding = "0.2" -language-tags = "0.2" lazy_static = "1.0" serde_urlencoded = "^0.5.3" url = { version="1.7", features=["query_encoding"] } cookie = { version="0.11", features=["percent-encode"] } -brotli2 = { version="^0.3.2", optional = true } -flate2 = { version="^1.0.2", optional = true, default-features = false } failure = "^0.1.2" diff --git a/src/h1/codec.rs b/src/h1/codec.rs index ac54194ab..dd6b26bab 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -6,6 +6,7 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::H1Decoder; pub use super::decoder::InMessage; +use super::response::{ResponseInfo, ResponseLength}; use body::Body; use error::ParseError; use helpers; @@ -13,7 +14,6 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCOD use http::{Method, Version}; use httpresponse::HttpResponse; use request::RequestPool; -use server::output::{ResponseInfo, ResponseLength}; /// Http response pub enum OutMessage { diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index f777648ec..2b524556e 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -12,14 +12,13 @@ use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; use error::{ParseError, PayloadError}; -use payload::{Payload, PayloadStatus, PayloadWriter}; +use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; use body::Body; use config::ServiceConfig; use error::DispatchError; use httpresponse::HttpResponse; use request::Request; -use server::input::PayloadType; use super::codec::{Codec, InMessage, OutMessage}; @@ -50,7 +49,7 @@ where config: ServiceConfig, state: State, - payload: Option, + payload: Option, messages: VecDeque, ka_expire: Instant, @@ -316,7 +315,7 @@ where // payload let (ps, pl) = Payload::new(false); *msg.inner.payload.borrow_mut() = Some(pl); - self.payload = Some(PayloadType::new(&msg.inner.headers, ps)); + self.payload = Some(ps); self.messages.push_back(msg); } diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 1a2bb0183..1653227be 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -2,6 +2,7 @@ mod codec; mod decoder; mod dispatcher; +mod response; mod service; pub use self::codec::{Codec, InMessage, OutMessage}; diff --git a/src/server/output.rs b/src/h1/response.rs similarity index 100% rename from src/server/output.rs rename to src/h1/response.rs diff --git a/src/header.rs b/src/header.rs new file mode 100644 index 000000000..7b32d6c24 --- /dev/null +++ b/src/header.rs @@ -0,0 +1,265 @@ +//! Various http headers + +use std::fmt; +use std::str::FromStr; + +use bytes::{Bytes, BytesMut}; +use mime::Mime; +use modhttp::header::GetAll; +use modhttp::Error as HttpError; +use percent_encoding; + +pub use modhttp::header::*; + +use error::ParseError; +use httpmessage::HttpMessage; + +#[doc(hidden)] +/// A trait for any object that will represent a header field and value. +pub trait Header +where + Self: IntoHeaderValue, +{ + /// Returns the name of the header field + fn name() -> HeaderName; + + /// Parse a header + fn parse(msg: &T) -> Result; +} + +#[doc(hidden)] +/// A trait for any object that can be Converted to a `HeaderValue` +pub trait IntoHeaderValue: Sized { + /// The type returned in the event of a conversion error. + type Error: Into; + + /// Try to convert value to a Header value. + fn try_into(self) -> Result; +} + +impl IntoHeaderValue for HeaderValue { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into(self) -> Result { + Ok(self) + } +} + +impl<'a> IntoHeaderValue for &'a str { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into(self) -> Result { + self.parse() + } +} + +impl<'a> IntoHeaderValue for &'a [u8] { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_bytes(self) + } +} + +impl IntoHeaderValue for Bytes { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(self) + } +} + +impl IntoHeaderValue for Vec { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(Bytes::from(self)) + } +} + +impl IntoHeaderValue for String { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(Bytes::from(self)) + } +} + +impl IntoHeaderValue for Mime { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(Bytes::from(format!("{}", self))) + } +} + +/// Represents supported types of content encodings +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum ContentEncoding { + /// Automatically select encoding based on encoding negotiation + Auto, + /// A format using the Brotli algorithm + Br, + /// A format using the zlib structure with deflate algorithm + Deflate, + /// Gzip algorithm + Gzip, + /// Indicates the identity function (i.e. no compression, nor modification) + Identity, +} + +impl ContentEncoding { + #[inline] + /// Is the content compressed? + pub fn is_compression(self) -> bool { + match self { + ContentEncoding::Identity | ContentEncoding::Auto => false, + _ => true, + } + } + + #[inline] + /// Convert content encoding to string + pub fn as_str(self) -> &'static str { + match self { + ContentEncoding::Br => "br", + ContentEncoding::Gzip => "gzip", + ContentEncoding::Deflate => "deflate", + ContentEncoding::Identity | ContentEncoding::Auto => "identity", + } + } + + #[inline] + /// default quality value + pub fn quality(self) -> f64 { + match self { + ContentEncoding::Br => 1.1, + ContentEncoding::Gzip => 1.0, + ContentEncoding::Deflate => 0.9, + ContentEncoding::Identity | ContentEncoding::Auto => 0.1, + } + } +} + +// TODO: remove memory allocation +impl<'a> From<&'a str> for ContentEncoding { + fn from(s: &'a str) -> ContentEncoding { + match AsRef::::as_ref(&s.trim().to_lowercase()) { + "br" => ContentEncoding::Br, + "gzip" => ContentEncoding::Gzip, + "deflate" => ContentEncoding::Deflate, + _ => ContentEncoding::Identity, + } + } +} + +#[doc(hidden)] +pub(crate) struct Writer { + buf: BytesMut, +} + +impl Writer { + fn new() -> Writer { + Writer { + buf: BytesMut::new(), + } + } + fn take(&mut self) -> Bytes { + self.buf.take().freeze() + } +} + +impl fmt::Write for Writer { + #[inline] + fn write_str(&mut self, s: &str) -> fmt::Result { + self.buf.extend_from_slice(s.as_bytes()); + Ok(()) + } + + #[inline] + fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { + fmt::write(self, args) + } +} + +#[inline] +#[doc(hidden)] +/// Reads a comma-delimited raw header into a Vec. +pub fn from_comma_delimited( + all: GetAll, +) -> Result, ParseError> { + let mut result = Vec::new(); + for h in all { + let s = h.to_str().map_err(|_| ParseError::Header)?; + result.extend( + s.split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y), + }).filter_map(|x| x.trim().parse().ok()), + ) + } + Ok(result) +} + +#[inline] +#[doc(hidden)] +/// Reads a single string when parsing a header. +pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { + if let Some(line) = val { + let line = line.to_str().map_err(|_| ParseError::Header)?; + if !line.is_empty() { + return T::from_str(line).or(Err(ParseError::Header)); + } + } + Err(ParseError::Header) +} + +#[inline] +#[doc(hidden)] +/// Format an array into a comma-delimited string. +pub fn fmt_comma_delimited(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result +where + T: fmt::Display, +{ + let mut iter = parts.iter(); + if let Some(part) = iter.next() { + fmt::Display::fmt(part, f)?; + } + for part in iter { + f.write_str(", ")?; + fmt::Display::fmt(part, f)?; + } + Ok(()) +} + +/// Percent encode a sequence of bytes with a character set defined in +/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] +/// +/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 +pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { + let encoded = + percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); + fmt::Display::fmt(&encoded, f) +} +mod percent_encoding_http { + use percent_encoding; + + // internal module because macro is hard-coded to make a public item + // but we don't want to public export this item + define_encode_set! { + // This encode set is used for HTTP header values and is defined at + // https://tools.ietf.org/html/rfc5987#section-3.2 + pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { + ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', + '[', '\\', ']', '{', '}' + } + } +} diff --git a/src/header/common/accept.rs b/src/header/common/accept.rs deleted file mode 100644 index 1ba321ce8..000000000 --- a/src/header/common/accept.rs +++ /dev/null @@ -1,159 +0,0 @@ -use header::{qitem, QualityItem}; -use http::header as http; -use mime::{self, Mime}; - -header! { - /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) - /// - /// The `Accept` header field can be used by user agents to specify - /// response media types that are acceptable. Accept header fields can - /// be used to indicate that the request is specifically limited to a - /// small set of desired types, as in the case of a request for an - /// in-line image - /// - /// # ABNF - /// - /// ```text - /// Accept = #( media-range [ accept-params ] ) - /// - /// media-range = ( "*/*" - /// / ( type "/" "*" ) - /// / ( type "/" subtype ) - /// ) *( OWS ";" OWS parameter ) - /// accept-params = weight *( accept-ext ) - /// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ] - /// ``` - /// - /// # Example values - /// * `audio/*; q=0.2, audio/basic` - /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` - /// - /// # Examples - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{Accept, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// - /// builder.set( - /// Accept(vec![ - /// qitem(mime::TEXT_HTML), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{Accept, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// - /// builder.set( - /// Accept(vec![ - /// qitem(mime::APPLICATION_JSON), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{Accept, QualityItem, q, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// - /// builder.set( - /// Accept(vec![ - /// qitem(mime::TEXT_HTML), - /// qitem("application/xhtml+xml".parse().unwrap()), - /// QualityItem::new( - /// mime::TEXT_XML, - /// q(900) - /// ), - /// qitem("image/webp".parse().unwrap()), - /// QualityItem::new( - /// mime::STAR_STAR, - /// q(800) - /// ), - /// ]) - /// ); - /// # } - /// ``` - (Accept, http::ACCEPT) => (QualityItem)+ - - test_accept { - // Tests from the RFC - test_header!( - test1, - vec![b"audio/*; q=0.2, audio/basic"], - Some(HeaderField(vec![ - QualityItem::new("audio/*".parse().unwrap(), q(200)), - qitem("audio/basic".parse().unwrap()), - ]))); - test_header!( - test2, - vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], - Some(HeaderField(vec![ - QualityItem::new(TEXT_PLAIN, q(500)), - qitem(TEXT_HTML), - QualityItem::new( - "text/x-dvi".parse().unwrap(), - q(800)), - qitem("text/x-c".parse().unwrap()), - ]))); - // Custom tests - test_header!( - test3, - vec![b"text/plain; charset=utf-8"], - Some(Accept(vec![ - qitem(TEXT_PLAIN_UTF_8), - ]))); - test_header!( - test4, - vec![b"text/plain; charset=utf-8; q=0.5"], - Some(Accept(vec![ - QualityItem::new(TEXT_PLAIN_UTF_8, - q(500)), - ]))); - - #[test] - fn test_fuzzing1() { - use test::TestRequest; - let req = TestRequest::with_header(super::http::ACCEPT, "chunk#;e").finish(); - let header = Accept::parse(&req); - assert!(header.is_ok()); - } - } -} - -impl Accept { - /// A constructor to easily create `Accept: */*`. - pub fn star() -> Accept { - Accept(vec![qitem(mime::STAR_STAR)]) - } - - /// A constructor to easily create `Accept: application/json`. - pub fn json() -> Accept { - Accept(vec![qitem(mime::APPLICATION_JSON)]) - } - - /// A constructor to easily create `Accept: text/*`. - pub fn text() -> Accept { - Accept(vec![qitem(mime::TEXT_STAR)]) - } - - /// A constructor to easily create `Accept: image/*`. - pub fn image() -> Accept { - Accept(vec![qitem(mime::IMAGE_STAR)]) - } -} diff --git a/src/header/common/accept_charset.rs b/src/header/common/accept_charset.rs deleted file mode 100644 index 49a7237aa..000000000 --- a/src/header/common/accept_charset.rs +++ /dev/null @@ -1,69 +0,0 @@ -use header::{Charset, QualityItem, ACCEPT_CHARSET}; - -header! { - /// `Accept-Charset` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) - /// - /// The `Accept-Charset` header field can be sent by a user agent to - /// indicate what charsets are acceptable in textual response content. - /// This field allows user agents capable of understanding more - /// comprehensive or special-purpose charsets to signal that capability - /// to an origin server that is capable of representing information in - /// those charsets. - /// - /// # ABNF - /// - /// ```text - /// Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) - /// ``` - /// - /// # Example values - /// * `iso-8859-5, unicode-1-1;q=0.8` - /// - /// # Examples - /// ```rust - /// # extern crate actix_http; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) - /// ); - /// # } - /// ``` - /// ```rust - /// # extern crate actix_http; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// AcceptCharset(vec![ - /// QualityItem::new(Charset::Us_Ascii, q(900)), - /// QualityItem::new(Charset::Iso_8859_10, q(200)), - /// ]) - /// ); - /// # } - /// ``` - /// ```rust - /// # extern crate actix_http; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) - /// ); - /// # } - /// ``` - (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)+ - - test_accept_charset { - /// Test case from RFC - test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); - } -} diff --git a/src/header/common/accept_encoding.rs b/src/header/common/accept_encoding.rs deleted file mode 100644 index c90f529bc..000000000 --- a/src/header/common/accept_encoding.rs +++ /dev/null @@ -1,72 +0,0 @@ -use header::{Encoding, QualityItem}; - -header! { - /// `Accept-Encoding` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.4) - /// - /// The `Accept-Encoding` header field can be used by user agents to - /// indicate what response content-codings are - /// acceptable in the response. An `identity` token is used as a synonym - /// for "no encoding" in order to communicate when no encoding is - /// preferred. - /// - /// # ABNF - /// - /// ```text - /// Accept-Encoding = #( codings [ weight ] ) - /// codings = content-coding / "identity" / "*" - /// ``` - /// - /// # Example values - /// * `compress, gzip` - /// * `` - /// * `*` - /// * `compress;q=0.5, gzip;q=1` - /// * `gzip;q=1.0, identity; q=0.5, *;q=0` - /// - /// # Examples - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) - /// ); - /// ``` - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![ - /// qitem(Encoding::Chunked), - /// qitem(Encoding::Gzip), - /// qitem(Encoding::Deflate), - /// ]) - /// ); - /// ``` - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![ - /// qitem(Encoding::Chunked), - /// QualityItem::new(Encoding::Gzip, q(600)), - /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), - /// ]) - /// ); - /// ``` - (AcceptEncoding, "Accept-Encoding") => (QualityItem)* - - test_accept_encoding { - // From the RFC - test_header!(test1, vec![b"compress, gzip"]); - test_header!(test2, vec![b""], Some(AcceptEncoding(vec![]))); - test_header!(test3, vec![b"*"]); - // Note: Removed quality 1 from gzip - test_header!(test4, vec![b"compress;q=0.5, gzip"]); - // Note: Removed quality 1 from gzip - test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); - } -} diff --git a/src/header/common/accept_language.rs b/src/header/common/accept_language.rs deleted file mode 100644 index 25fd97df4..000000000 --- a/src/header/common/accept_language.rs +++ /dev/null @@ -1,75 +0,0 @@ -use header::{QualityItem, ACCEPT_LANGUAGE}; -use language_tags::LanguageTag; - -header! { - /// `Accept-Language` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) - /// - /// The `Accept-Language` header field can be used by user agents to - /// indicate the set of natural languages that are preferred in the - /// response. - /// - /// # ABNF - /// - /// ```text - /// Accept-Language = 1#( language-range [ weight ] ) - /// language-range = - /// ``` - /// - /// # Example values - /// * `da, en-gb;q=0.8, en;q=0.7` - /// * `en-us;q=1.0, en;q=0.5, fr` - /// - /// # Examples - /// - /// ```rust - /// # extern crate actix_http; - /// # extern crate language_tags; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// let mut langtag: LanguageTag = Default::default(); - /// langtag.language = Some("en".to_owned()); - /// langtag.region = Some("US".to_owned()); - /// builder.set( - /// AcceptLanguage(vec![ - /// qitem(langtag), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem}; - /// # - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// AcceptLanguage(vec![ - /// qitem(langtag!(da)), - /// QualityItem::new(langtag!(en;;;GB), q(800)), - /// QualityItem::new(langtag!(en), q(700)), - /// ]) - /// ); - /// # } - /// ``` - (AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem)+ - - test_accept_language { - // From the RFC - test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); - // Own test - test_header!( - test2, vec![b"en-US, en; q=0.5, fr"], - Some(AcceptLanguage(vec![ - qitem("en-US".parse().unwrap()), - QualityItem::new("en".parse().unwrap(), q(500)), - qitem("fr".parse().unwrap()), - ]))); - } -} diff --git a/src/header/common/allow.rs b/src/header/common/allow.rs deleted file mode 100644 index 089c823d0..000000000 --- a/src/header/common/allow.rs +++ /dev/null @@ -1,82 +0,0 @@ -use http::header; -use http::Method; - -header! { - /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) - /// - /// The `Allow` header field lists the set of methods advertised as - /// supported by the target resource. The purpose of this field is - /// strictly to inform the recipient of valid request methods associated - /// with the resource. - /// - /// # ABNF - /// - /// ```text - /// Allow = #method - /// ``` - /// - /// # Example values - /// * `GET, HEAD, PUT` - /// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr` - /// * `` - /// - /// # Examples - /// - /// ```rust - /// # extern crate actix_http; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::Allow; - /// use actix_http::http::Method; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// Allow(vec![Method::GET]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// use actix_http::HttpResponse; - /// use actix_http::http::{Method, header::Allow}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// Allow(vec![ - /// Method::GET, - /// Method::POST, - /// Method::PATCH, - /// ]) - /// ); - /// # } - /// ``` - (Allow, header::ALLOW) => (Method)* - - test_allow { - // From the RFC - test_header!( - test1, - vec![b"GET, HEAD, PUT"], - Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT]))); - // Own tests - test_header!( - test2, - vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"], - Some(HeaderField(vec![ - Method::OPTIONS, - Method::GET, - Method::PUT, - Method::POST, - Method::DELETE, - Method::HEAD, - Method::TRACE, - Method::CONNECT, - Method::PATCH]))); - test_header!( - test3, - vec![b""], - Some(HeaderField(Vec::::new()))); - } -} diff --git a/src/header/common/cache_control.rs b/src/header/common/cache_control.rs deleted file mode 100644 index 4379b6f7a..000000000 --- a/src/header/common/cache_control.rs +++ /dev/null @@ -1,254 +0,0 @@ -use header::{fmt_comma_delimited, from_comma_delimited}; -use header::{Header, IntoHeaderValue, Writer}; -use http::header; -use std::fmt::{self, Write}; -use std::str::FromStr; - -/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) -/// -/// The `Cache-Control` header field is used to specify directives for -/// caches along the request/response chain. Such cache directives are -/// unidirectional in that the presence of a directive in a request does -/// not imply that the same directive is to be given in the response. -/// -/// # ABNF -/// -/// ```text -/// Cache-Control = 1#cache-directive -/// cache-directive = token [ "=" ( token / quoted-string ) ] -/// ``` -/// -/// # Example values -/// -/// * `no-cache` -/// * `private, community="UCI"` -/// * `max-age=30` -/// -/// # Examples -/// ```rust -/// use actix_http::HttpResponse; -/// use actix_http::http::header::{CacheControl, CacheDirective}; -/// -/// let mut builder = HttpResponse::Ok(); -/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); -/// ``` -/// -/// ```rust -/// use actix_http::HttpResponse; -/// use actix_http::http::header::{CacheControl, CacheDirective}; -/// -/// let mut builder = HttpResponse::Ok(); -/// builder.set(CacheControl(vec![ -/// CacheDirective::NoCache, -/// CacheDirective::Private, -/// CacheDirective::MaxAge(360u32), -/// CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())), -/// ])); -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub struct CacheControl(pub Vec); - -__hyper__deref!(CacheControl => Vec); - -//TODO: this could just be the header! macro -impl Header for CacheControl { - fn name() -> header::HeaderName { - header::CACHE_CONTROL - } - - #[inline] - fn parse(msg: &T) -> Result - where - T: ::HttpMessage, - { - let directives = from_comma_delimited(msg.headers().get_all(Self::name()))?; - if !directives.is_empty() { - Ok(CacheControl(directives)) - } else { - Err(::error::ParseError::Header) - } - } -} - -impl fmt::Display for CacheControl { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_comma_delimited(f, &self[..]) - } -} - -impl IntoHeaderValue for CacheControl { - type Error = header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - header::HeaderValue::from_shared(writer.take()) - } -} - -/// `CacheControl` contains a list of these directives. -#[derive(PartialEq, Clone, Debug)] -pub enum CacheDirective { - /// "no-cache" - NoCache, - /// "no-store" - NoStore, - /// "no-transform" - NoTransform, - /// "only-if-cached" - OnlyIfCached, - - // request directives - /// "max-age=delta" - MaxAge(u32), - /// "max-stale=delta" - MaxStale(u32), - /// "min-fresh=delta" - MinFresh(u32), - - // response directives - /// "must-revalidate" - MustRevalidate, - /// "public" - Public, - /// "private" - Private, - /// "proxy-revalidate" - ProxyRevalidate, - /// "s-maxage=delta" - SMaxAge(u32), - - /// Extension directives. Optionally include an argument. - Extension(String, Option), -} - -impl fmt::Display for CacheDirective { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use self::CacheDirective::*; - fmt::Display::fmt( - match *self { - NoCache => "no-cache", - NoStore => "no-store", - NoTransform => "no-transform", - OnlyIfCached => "only-if-cached", - - MaxAge(secs) => return write!(f, "max-age={}", secs), - MaxStale(secs) => return write!(f, "max-stale={}", secs), - MinFresh(secs) => return write!(f, "min-fresh={}", secs), - - MustRevalidate => "must-revalidate", - Public => "public", - Private => "private", - ProxyRevalidate => "proxy-revalidate", - SMaxAge(secs) => return write!(f, "s-maxage={}", secs), - - Extension(ref name, None) => &name[..], - Extension(ref name, Some(ref arg)) => { - return write!(f, "{}={}", name, arg) - } - }, - f, - ) - } -} - -impl FromStr for CacheDirective { - type Err = Option<::Err>; - fn from_str(s: &str) -> Result::Err>> { - use self::CacheDirective::*; - match s { - "no-cache" => Ok(NoCache), - "no-store" => Ok(NoStore), - "no-transform" => Ok(NoTransform), - "only-if-cached" => Ok(OnlyIfCached), - "must-revalidate" => Ok(MustRevalidate), - "public" => Ok(Public), - "private" => Ok(Private), - "proxy-revalidate" => Ok(ProxyRevalidate), - "" => Err(None), - _ => match s.find('=') { - Some(idx) if idx + 1 < s.len() => { - match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) { - ("max-age", secs) => secs.parse().map(MaxAge).map_err(Some), - ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some), - ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some), - ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some), - (left, right) => { - Ok(Extension(left.to_owned(), Some(right.to_owned()))) - } - } - } - Some(_) => Err(None), - None => Ok(Extension(s.to_owned(), None)), - }, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use header::Header; - use test::TestRequest; - - #[test] - fn test_parse_multiple_headers() { - let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private") - .finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::NoCache, - CacheDirective::Private, - ])) - ) - } - - #[test] - fn test_parse_argument() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private") - .finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::MaxAge(100), - CacheDirective::Private, - ])) - ) - } - - #[test] - fn test_parse_quote_form() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![CacheDirective::MaxAge(200)])) - ) - } - - #[test] - fn test_parse_extension() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::Extension("foo".to_owned(), None), - CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())), - ])) - ) - } - - #[test] - fn test_parse_bad_syntax() { - let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish(); - let cache: Result = Header::parse(&req); - assert_eq!(cache.ok(), None) - } -} diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs deleted file mode 100644 index 0efc4fb0b..000000000 --- a/src/header/common/content_disposition.rs +++ /dev/null @@ -1,915 +0,0 @@ -// # References -// -// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt -// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt -// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt -// Browser conformance tests at: http://greenbytes.de/tech/tc2231/ -// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml - -use header; -use header::ExtendedValue; -use header::{Header, IntoHeaderValue, Writer}; -use regex::Regex; - -use std::fmt::{self, Write}; - -/// Split at the index of the first `needle` if it exists or at the end. -fn split_once(haystack: &str, needle: char) -> (&str, &str) { - haystack.find(needle).map_or_else( - || (haystack, ""), - |sc| { - let (first, last) = haystack.split_at(sc); - (first, last.split_at(1).1) - }, - ) -} - -/// Split at the index of the first `needle` if it exists or at the end, trim the right of the -/// first part and the left of the last part. -fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) { - let (first, last) = split_once(haystack, needle); - (first.trim_right(), last.trim_left()) -} - -/// The implied disposition of the content of the HTTP body. -#[derive(Clone, Debug, PartialEq)] -pub enum DispositionType { - /// Inline implies default processing - Inline, - /// Attachment implies that the recipient should prompt the user to save the response locally, - /// rather than process it normally (as per its media type). - Attachment, - /// Used in *multipart/form-data* as defined in - /// [RFC7578](https://tools.ietf.org/html/rfc7578) to carry the field name and the file name. - FormData, - /// Extension type. Should be handled by recipients the same way as Attachment - Ext(String), -} - -impl<'a> From<&'a str> for DispositionType { - fn from(origin: &'a str) -> DispositionType { - if origin.eq_ignore_ascii_case("inline") { - DispositionType::Inline - } else if origin.eq_ignore_ascii_case("attachment") { - DispositionType::Attachment - } else if origin.eq_ignore_ascii_case("form-data") { - DispositionType::FormData - } else { - DispositionType::Ext(origin.to_owned()) - } - } -} - -/// Parameter in [`ContentDisposition`]. -/// -/// # Examples -/// ``` -/// use actix_http::http::header::DispositionParam; -/// -/// let param = DispositionParam::Filename(String::from("sample.txt")); -/// assert!(param.is_filename()); -/// assert_eq!(param.as_filename().unwrap(), "sample.txt"); -/// ``` -#[derive(Clone, Debug, PartialEq)] -pub enum DispositionParam { - /// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from - /// the form. - Name(String), - /// A plain file name. - Filename(String), - /// An extended file name. It must not exist for `ContentType::Formdata` according to - /// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2). - FilenameExt(ExtendedValue), - /// An unrecognized regular parameter as defined in - /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *reg-parameter*, in - /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *token "=" value*. Recipients should - /// ignore unrecognizable parameters. - Unknown(String, String), - /// An unrecognized extended paramater as defined in - /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *ext-parameter*, in - /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *ext-token "=" ext-value*. The single - /// trailling asterisk is not included. Recipients should ignore unrecognizable parameters. - UnknownExt(String, ExtendedValue), -} - -impl DispositionParam { - /// Returns `true` if the paramater is [`Name`](DispositionParam::Name). - #[inline] - pub fn is_name(&self) -> bool { - self.as_name().is_some() - } - - /// Returns `true` if the paramater is [`Filename`](DispositionParam::Filename). - #[inline] - pub fn is_filename(&self) -> bool { - self.as_filename().is_some() - } - - /// Returns `true` if the paramater is [`FilenameExt`](DispositionParam::FilenameExt). - #[inline] - pub fn is_filename_ext(&self) -> bool { - self.as_filename_ext().is_some() - } - - /// Returns `true` if the paramater is [`Unknown`](DispositionParam::Unknown) and the `name` - #[inline] - /// matches. - pub fn is_unknown>(&self, name: T) -> bool { - self.as_unknown(name).is_some() - } - - /// Returns `true` if the paramater is [`UnknownExt`](DispositionParam::UnknownExt) and the - /// `name` matches. - #[inline] - pub fn is_unknown_ext>(&self, name: T) -> bool { - self.as_unknown_ext(name).is_some() - } - - /// Returns the name if applicable. - #[inline] - pub fn as_name(&self) -> Option<&str> { - match self { - DispositionParam::Name(ref name) => Some(name.as_str()), - _ => None, - } - } - - /// Returns the filename if applicable. - #[inline] - pub fn as_filename(&self) -> Option<&str> { - match self { - DispositionParam::Filename(ref filename) => Some(filename.as_str()), - _ => None, - } - } - - /// Returns the filename* if applicable. - #[inline] - pub fn as_filename_ext(&self) -> Option<&ExtendedValue> { - match self { - DispositionParam::FilenameExt(ref value) => Some(value), - _ => None, - } - } - - /// Returns the value of the unrecognized regular parameter if it is - /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. - #[inline] - pub fn as_unknown>(&self, name: T) -> Option<&str> { - match self { - DispositionParam::Unknown(ref ext_name, ref value) - if ext_name.eq_ignore_ascii_case(name.as_ref()) => - { - Some(value.as_str()) - } - _ => None, - } - } - - /// Returns the value of the unrecognized extended parameter if it is - /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. - #[inline] - pub fn as_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { - match self { - DispositionParam::UnknownExt(ref ext_name, ref value) - if ext_name.eq_ignore_ascii_case(name.as_ref()) => - { - Some(value) - } - _ => None, - } - } -} - -/// A *Content-Disposition* header. It is compatible to be used either as -/// [a response header for the main body](https://mdn.io/Content-Disposition#As_a_response_header_for_the_main_body) -/// as (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266), or as -/// [a header for a multipart body](https://mdn.io/Content-Disposition#As_a_header_for_a_multipart_body) -/// as (re)defined in [RFC7587](https://tools.ietf.org/html/rfc7578). -/// -/// In a regular HTTP response, the *Content-Disposition* response header is a header indicating if -/// the content is expected to be displayed *inline* in the browser, that is, as a Web page or as -/// part of a Web page, or as an attachment, that is downloaded and saved locally, and also can be -/// used to attach additional metadata, such as the filename to use when saving the response payload -/// locally. -/// -/// In a *multipart/form-data* body, the HTTP *Content-Disposition* general header is a header that -/// can be used on the subpart of a multipart body to give information about the field it applies to. -/// The subpart is delimited by the boundary defined in the *Content-Type* header. Used on the body -/// itself, *Content-Disposition* has no effect. -/// -/// # ABNF - -/// ```text -/// content-disposition = "Content-Disposition" ":" -/// disposition-type *( ";" disposition-parm ) -/// -/// disposition-type = "inline" | "attachment" | disp-ext-type -/// ; case-insensitive -/// -/// disp-ext-type = token -/// -/// disposition-parm = filename-parm | disp-ext-parm -/// -/// filename-parm = "filename" "=" value -/// | "filename*" "=" ext-value -/// -/// disp-ext-parm = token "=" value -/// | ext-token "=" ext-value -/// -/// ext-token = -/// ``` -/// -/// **Note**: filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within -/// *multipart/form-data*. -/// -/// # Example -/// -/// ``` -/// use actix_http::http::header::{ -/// Charset, ContentDisposition, DispositionParam, DispositionType, -/// ExtendedValue, -/// }; -/// -/// let cd1 = ContentDisposition { -/// disposition: DispositionType::Attachment, -/// parameters: vec![DispositionParam::FilenameExt(ExtendedValue { -/// charset: Charset::Iso_8859_1, // The character set for the bytes of the filename -/// language_tag: None, // The optional language tag (see `language-tag` crate) -/// value: b"\xa9 Copyright 1989.txt".to_vec(), // the actual bytes of the filename -/// })], -/// }; -/// assert!(cd1.is_attachment()); -/// assert!(cd1.get_filename_ext().is_some()); -/// -/// let cd2 = ContentDisposition { -/// disposition: DispositionType::FormData, -/// parameters: vec![ -/// DispositionParam::Name(String::from("file")), -/// DispositionParam::Filename(String::from("bill.odt")), -/// ], -/// }; -/// assert_eq!(cd2.get_name(), Some("file")); // field name -/// assert_eq!(cd2.get_filename(), Some("bill.odt")); -/// ``` -/// -/// # WARN -/// If "filename" parameter is supplied, do not use the file name blindly, check and possibly -/// change to match local file system conventions if applicable, and do not use directory path -/// information that may be present. See [RFC2183](https://tools.ietf.org/html/rfc2183#section-2.3) -/// . -#[derive(Clone, Debug, PartialEq)] -pub struct ContentDisposition { - /// The disposition type - pub disposition: DispositionType, - /// Disposition parameters - pub parameters: Vec, -} - -impl ContentDisposition { - /// Parse a raw Content-Disposition header value. - pub fn from_raw(hv: &header::HeaderValue) -> Result { - // `header::from_one_raw_str` invokes `hv.to_str` which assumes `hv` contains only visible - // ASCII characters. So `hv.as_bytes` is necessary here. - let hv = String::from_utf8(hv.as_bytes().to_vec()) - .map_err(|_| ::error::ParseError::Header)?; - let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';'); - if disp_type.is_empty() { - return Err(::error::ParseError::Header); - } - let mut cd = ContentDisposition { - disposition: disp_type.into(), - parameters: Vec::new(), - }; - - while !left.is_empty() { - let (param_name, new_left) = split_once_and_trim(left, '='); - if param_name.is_empty() || param_name == "*" || new_left.is_empty() { - return Err(::error::ParseError::Header); - } - left = new_left; - if param_name.ends_with('*') { - // extended parameters - let param_name = ¶m_name[..param_name.len() - 1]; // trim asterisk - let (ext_value, new_left) = split_once_and_trim(left, ';'); - left = new_left; - let ext_value = header::parse_extended_value(ext_value)?; - - let param = if param_name.eq_ignore_ascii_case("filename") { - DispositionParam::FilenameExt(ext_value) - } else { - DispositionParam::UnknownExt(param_name.to_owned(), ext_value) - }; - cd.parameters.push(param); - } else { - // regular parameters - let value = if left.starts_with('\"') { - // quoted-string: defined in RFC6266 -> RFC2616 Section 3.6 - let mut escaping = false; - let mut quoted_string = vec![]; - let mut end = None; - // search for closing quote - for (i, &c) in left.as_bytes().iter().skip(1).enumerate() { - if escaping { - escaping = false; - quoted_string.push(c); - } else if c == 0x5c { - // backslash - escaping = true; - } else if c == 0x22 { - // double quote - end = Some(i + 1); // cuz skipped 1 for the leading quote - break; - } else { - quoted_string.push(c); - } - } - left = &left[end.ok_or(::error::ParseError::Header)? + 1..]; - left = split_once(left, ';').1.trim_left(); - // In fact, it should not be Err if the above code is correct. - String::from_utf8(quoted_string) - .map_err(|_| ::error::ParseError::Header)? - } else { - // token: won't contains semicolon according to RFC 2616 Section 2.2 - let (token, new_left) = split_once_and_trim(left, ';'); - left = new_left; - token.to_owned() - }; - if value.is_empty() { - return Err(::error::ParseError::Header); - } - - let param = if param_name.eq_ignore_ascii_case("name") { - DispositionParam::Name(value) - } else if param_name.eq_ignore_ascii_case("filename") { - DispositionParam::Filename(value) - } else { - DispositionParam::Unknown(param_name.to_owned(), value) - }; - cd.parameters.push(param); - } - } - - Ok(cd) - } - - /// Returns `true` if it is [`Inline`](DispositionType::Inline). - pub fn is_inline(&self) -> bool { - match self.disposition { - DispositionType::Inline => true, - _ => false, - } - } - - /// Returns `true` if it is [`Attachment`](DispositionType::Attachment). - pub fn is_attachment(&self) -> bool { - match self.disposition { - DispositionType::Attachment => true, - _ => false, - } - } - - /// Returns `true` if it is [`FormData`](DispositionType::FormData). - pub fn is_form_data(&self) -> bool { - match self.disposition { - DispositionType::FormData => true, - _ => false, - } - } - - /// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches. - pub fn is_ext>(&self, disp_type: T) -> bool { - match self.disposition { - DispositionType::Ext(ref t) - if t.eq_ignore_ascii_case(disp_type.as_ref()) => - { - true - } - _ => false, - } - } - - /// Return the value of *name* if exists. - pub fn get_name(&self) -> Option<&str> { - self.parameters.iter().filter_map(|p| p.as_name()).nth(0) - } - - /// Return the value of *filename* if exists. - pub fn get_filename(&self) -> Option<&str> { - self.parameters - .iter() - .filter_map(|p| p.as_filename()) - .nth(0) - } - - /// Return the value of *filename\** if exists. - pub fn get_filename_ext(&self) -> Option<&ExtendedValue> { - self.parameters - .iter() - .filter_map(|p| p.as_filename_ext()) - .nth(0) - } - - /// Return the value of the parameter which the `name` matches. - pub fn get_unknown>(&self, name: T) -> Option<&str> { - let name = name.as_ref(); - self.parameters - .iter() - .filter_map(|p| p.as_unknown(name)) - .nth(0) - } - - /// Return the value of the extended parameter which the `name` matches. - pub fn get_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { - let name = name.as_ref(); - self.parameters - .iter() - .filter_map(|p| p.as_unknown_ext(name)) - .nth(0) - } -} - -impl IntoHeaderValue for ContentDisposition { - type Error = header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - header::HeaderValue::from_shared(writer.take()) - } -} - -impl Header for ContentDisposition { - fn name() -> header::HeaderName { - header::CONTENT_DISPOSITION - } - - fn parse(msg: &T) -> Result { - if let Some(h) = msg.headers().get(Self::name()) { - Self::from_raw(&h) - } else { - Err(::error::ParseError::Header) - } - } -} - -impl fmt::Display for DispositionType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - DispositionType::Inline => write!(f, "inline"), - DispositionType::Attachment => write!(f, "attachment"), - DispositionType::FormData => write!(f, "form-data"), - DispositionType::Ext(ref s) => write!(f, "{}", s), - } - } -} - -impl fmt::Display for DispositionParam { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // All ASCII control charaters (0-30, 127) excepting horizontal tab, double quote, and - // backslash should be escaped in quoted-string (i.e. "foobar"). - // Ref: RFC6266 S4.1 -> RFC2616 S2.2; RFC 7578 S4.2 -> RFC2183 S2 -> ... . - lazy_static! { - static ref RE: Regex = Regex::new("[\x01-\x08\x10\x1F\x7F\"\\\\]").unwrap(); - } - match self { - DispositionParam::Name(ref value) => write!(f, "name={}", value), - DispositionParam::Filename(ref value) => { - write!(f, "filename=\"{}\"", RE.replace_all(value, "\\$0").as_ref()) - } - DispositionParam::Unknown(ref name, ref value) => write!( - f, - "{}=\"{}\"", - name, - &RE.replace_all(value, "\\$0").as_ref() - ), - DispositionParam::FilenameExt(ref ext_value) => { - write!(f, "filename*={}", ext_value) - } - DispositionParam::UnknownExt(ref name, ref ext_value) => { - write!(f, "{}*={}", name, ext_value) - } - } - } -} - -impl fmt::Display for ContentDisposition { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.disposition)?; - self.parameters - .iter() - .map(|param| write!(f, "; {}", param)) - .collect() - } -} - -#[cfg(test)] -mod tests { - use super::{ContentDisposition, DispositionParam, DispositionType}; - use header::shared::Charset; - use header::{ExtendedValue, HeaderValue}; - #[test] - fn test_from_raw_basic() { - assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err()); - - let a = HeaderValue::from_static( - "form-data; dummy=3; name=upload; filename=\"sample.png\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - ], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static("attachment; filename=\"image.jpg\""); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Filename("image.jpg".to_owned())], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static("inline; filename=image.jpg"); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![DispositionParam::Filename("image.jpg".to_owned())], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static( - "attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Unknown( - String::from("creation-date"), - "Wed, 12 Feb 1997 16:29:51 -0500".to_owned(), - )], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_extended() { - let a = HeaderValue::from_static( - "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Ext(String::from("UTF-8")), - language_tag: None, - value: vec![ - 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, - b'r', b'a', b't', b'e', b's', - ], - })], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static( - "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Ext(String::from("UTF-8")), - language_tag: None, - value: vec![ - 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, - b'r', b'a', b't', b'e', b's', - ], - })], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_extra_whitespace() { - let a = HeaderValue::from_static( - "form-data ; du-mmy= 3 ; name =upload ; filename = \"sample.png\" ; ", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("du-mmy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_unordered() { - let a = HeaderValue::from_static( - "form-data; dummy=3; filename=\"sample.png\" ; name=upload;", - // Actually, a trailling semolocon is not compliant. But it is fine to accept. - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - DispositionParam::Name("upload".to_owned()), - ], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_str( - "attachment; filename*=iso-8859-1''foo-%E4.html; filename=\"foo-ä.html\"", - ).unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![ - DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: None, - value: b"foo-\xe4.html".to_vec(), - }), - DispositionParam::Filename("foo-ä.html".to_owned()), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_only_disp() { - let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment")) - .unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![], - }; - assert_eq!(a, b); - - let a = - ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![], - }; - assert_eq!(a, b); - - let a = ContentDisposition::from_raw(&HeaderValue::from_static( - "unknown-disp-param", - )).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Ext(String::from("unknown-disp-param")), - parameters: vec![], - }; - assert_eq!(a, b); - } - - #[test] - fn from_raw_with_mixed_case() { - let a = HeaderValue::from_str( - "InLInE; fIlenAME*=iso-8859-1''foo-%E4.html; filEName=\"foo-ä.html\"", - ).unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![ - DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: None, - value: b"foo-\xe4.html".to_vec(), - }), - DispositionParam::Filename("foo-ä.html".to_owned()), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn from_raw_with_unicode() { - /* RFC7578 Section 4.2: - Some commonly deployed systems use multipart/form-data with file names directly encoded - including octets outside the US-ASCII range. The encoding used for the file names is - typically UTF-8, although HTML forms will use the charset associated with the form. - - Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above. - (And now, only UTF-8 is handled by this implementation.) - */ - let a = - HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"") - .unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name(String::from("upload")), - DispositionParam::Filename(String::from("文件.webp")), - ], - }; - assert_eq!(a, b); - - let a = - HeaderValue::from_str("form-data; name=upload; filename=\"余固知謇謇之為患兮,忍而不能舍也.pptx\"").unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name(String::from("upload")), - DispositionParam::Filename(String::from( - "余固知謇謇之為患兮,忍而不能舍也.pptx", - )), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_escape() { - let a = HeaderValue::from_static( - "form-data; dummy=3; name=upload; filename=\"s\\amp\\\"le.png\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename( - ['s', 'a', 'm', 'p', '\"', 'l', 'e', '.', 'p', 'n', 'g'] - .iter() - .collect(), - ), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_semicolon() { - let a = - HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\""); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![DispositionParam::Filename(String::from( - "A semicolon here;.pdf", - ))], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_uncessary_percent_decode() { - let a = HeaderValue::from_static( - "form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"", // Should not be decoded! - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name("photo".to_owned()), - DispositionParam::Filename(String::from("%74%65%73%74%2e%70%6e%67")), - ], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static( - "form-data; name=photo; filename=\"%74%65%73%74.png\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name("photo".to_owned()), - DispositionParam::Filename(String::from("%74%65%73%74.png")), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_param_value_missing() { - let a = HeaderValue::from_static("form-data; name=upload ; filename="); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("attachment; dummy=; filename=invoice.pdf"); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("inline; filename= "); - assert!(ContentDisposition::from_raw(&a).is_err()); - } - - #[test] - fn test_from_raw_param_name_missing() { - let a = HeaderValue::from_static("inline; =\"test.txt\""); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("inline; =diary.odt"); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("inline; ="); - assert!(ContentDisposition::from_raw(&a).is_err()); - } - - #[test] - fn test_display_extended() { - let as_string = - "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; - let a = HeaderValue::from_static(as_string); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!(as_string, display_rendered); - - let a = HeaderValue::from_static("attachment; filename=colourful.csv"); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!( - "attachment; filename=\"colourful.csv\"".to_owned(), - display_rendered - ); - } - - #[test] - fn test_display_quote() { - let as_string = "form-data; name=upload; filename=\"Quote\\\"here.png\""; - as_string - .find(['\\', '\"'].iter().collect::().as_str()) - .unwrap(); // ensure `\"` is there - let a = HeaderValue::from_static(as_string); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!(as_string, display_rendered); - } - - #[test] - fn test_display_space_tab() { - let as_string = "form-data; name=upload; filename=\"Space here.png\""; - let a = HeaderValue::from_static(as_string); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!(as_string, display_rendered); - - let a: ContentDisposition = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![DispositionParam::Filename(String::from("Tab\there.png"))], - }; - let display_rendered = format!("{}", a); - assert_eq!("inline; filename=\"Tab\x09here.png\"", display_rendered); - } - - #[test] - fn test_display_control_characters() { - /* let a = "attachment; filename=\"carriage\rreturn.png\""; - let a = HeaderValue::from_static(a); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!( - "attachment; filename=\"carriage\\\rreturn.png\"", - display_rendered - );*/ - // No way to create a HeaderValue containing a carriage return. - - let a: ContentDisposition = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![DispositionParam::Filename(String::from("bell\x07.png"))], - }; - let display_rendered = format!("{}", a); - assert_eq!("inline; filename=\"bell\\\x07.png\"", display_rendered); - } - - #[test] - fn test_param_methods() { - let param = DispositionParam::Filename(String::from("sample.txt")); - assert!(param.is_filename()); - assert_eq!(param.as_filename().unwrap(), "sample.txt"); - - let param = DispositionParam::Unknown(String::from("foo"), String::from("bar")); - assert!(param.is_unknown("foo")); - assert_eq!(param.as_unknown("fOo"), Some("bar")); - } - - #[test] - fn test_disposition_methods() { - let cd = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - ], - }; - assert_eq!(cd.get_name(), Some("upload")); - assert_eq!(cd.get_unknown("dummy"), Some("3")); - assert_eq!(cd.get_filename(), Some("sample.png")); - assert_eq!(cd.get_unknown_ext("dummy"), None); - assert_eq!(cd.get_unknown("duMMy"), Some("3")); - } -} diff --git a/src/header/common/content_language.rs b/src/header/common/content_language.rs deleted file mode 100644 index c1f87d513..000000000 --- a/src/header/common/content_language.rs +++ /dev/null @@ -1,65 +0,0 @@ -use header::{QualityItem, CONTENT_LANGUAGE}; -use language_tags::LanguageTag; - -header! { - /// `Content-Language` header, defined in - /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) - /// - /// The `Content-Language` header field describes the natural language(s) - /// of the intended audience for the representation. Note that this - /// might not be equivalent to all the languages used within the - /// representation. - /// - /// # ABNF - /// - /// ```text - /// Content-Language = 1#language-tag - /// ``` - /// - /// # Example values - /// - /// * `da` - /// * `mi, en` - /// - /// # Examples - /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; - /// use actix_http::HttpResponse; - /// # use actix_http::http::header::{ContentLanguage, qitem}; - /// # - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// ContentLanguage(vec![ - /// qitem(langtag!(en)), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; - /// use actix_http::HttpResponse; - /// # use actix_http::http::header::{ContentLanguage, qitem}; - /// # - /// # fn main() { - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// ContentLanguage(vec![ - /// qitem(langtag!(da)), - /// qitem(langtag!(en;;;GB)), - /// ]) - /// ); - /// # } - /// ``` - (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+ - - test_content_language { - test_header!(test1, vec![b"da"]); - test_header!(test2, vec![b"mi, en"]); - } -} diff --git a/src/header/common/content_range.rs b/src/header/common/content_range.rs deleted file mode 100644 index 999307e2a..000000000 --- a/src/header/common/content_range.rs +++ /dev/null @@ -1,210 +0,0 @@ -use error::ParseError; -use header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer, - CONTENT_RANGE}; -use std::fmt::{self, Display, Write}; -use std::str::FromStr; - -header! { - /// `Content-Range` header, defined in - /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) - (ContentRange, CONTENT_RANGE) => [ContentRangeSpec] - - test_content_range { - test_header!(test_bytes, - vec![b"bytes 0-499/500"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: Some((0, 499)), - instance_length: Some(500) - }))); - - test_header!(test_bytes_unknown_len, - vec![b"bytes 0-499/*"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: Some((0, 499)), - instance_length: None - }))); - - test_header!(test_bytes_unknown_range, - vec![b"bytes */500"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: None, - instance_length: Some(500) - }))); - - test_header!(test_unregistered, - vec![b"seconds 1-2"], - Some(ContentRange(ContentRangeSpec::Unregistered { - unit: "seconds".to_owned(), - resp: "1-2".to_owned() - }))); - - test_header!(test_no_len, - vec![b"bytes 0-499"], - None::); - - test_header!(test_only_unit, - vec![b"bytes"], - None::); - - test_header!(test_end_less_than_start, - vec![b"bytes 499-0/500"], - None::); - - test_header!(test_blank, - vec![b""], - None::); - - test_header!(test_bytes_many_spaces, - vec![b"bytes 1-2/500 3"], - None::); - - test_header!(test_bytes_many_slashes, - vec![b"bytes 1-2/500/600"], - None::); - - test_header!(test_bytes_many_dashes, - vec![b"bytes 1-2-3/500"], - None::); - - } -} - -/// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2) -/// -/// # ABNF -/// -/// ```text -/// Content-Range = byte-content-range -/// / other-content-range -/// -/// byte-content-range = bytes-unit SP -/// ( byte-range-resp / unsatisfied-range ) -/// -/// byte-range-resp = byte-range "/" ( complete-length / "*" ) -/// byte-range = first-byte-pos "-" last-byte-pos -/// unsatisfied-range = "*/" complete-length -/// -/// complete-length = 1*DIGIT -/// -/// other-content-range = other-range-unit SP other-range-resp -/// other-range-resp = *CHAR -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub enum ContentRangeSpec { - /// Byte range - Bytes { - /// First and last bytes of the range, omitted if request could not be - /// satisfied - range: Option<(u64, u64)>, - - /// Total length of the instance, can be omitted if unknown - instance_length: Option, - }, - - /// Custom range, with unit not registered at IANA - Unregistered { - /// other-range-unit - unit: String, - - /// other-range-resp - resp: String, - }, -} - -fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> { - let mut iter = s.splitn(2, separator); - match (iter.next(), iter.next()) { - (Some(a), Some(b)) => Some((a, b)), - _ => None, - } -} - -impl FromStr for ContentRangeSpec { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - let res = match split_in_two(s, ' ') { - Some(("bytes", resp)) => { - let (range, instance_length) = - split_in_two(resp, '/').ok_or(ParseError::Header)?; - - let instance_length = if instance_length == "*" { - None - } else { - Some(instance_length - .parse() - .map_err(|_| ParseError::Header)?) - }; - - let range = if range == "*" { - None - } else { - let (first_byte, last_byte) = - split_in_two(range, '-').ok_or(ParseError::Header)?; - let first_byte = first_byte.parse().map_err(|_| ParseError::Header)?; - let last_byte = last_byte.parse().map_err(|_| ParseError::Header)?; - if last_byte < first_byte { - return Err(ParseError::Header); - } - Some((first_byte, last_byte)) - }; - - ContentRangeSpec::Bytes { - range, - instance_length, - } - } - Some((unit, resp)) => ContentRangeSpec::Unregistered { - unit: unit.to_owned(), - resp: resp.to_owned(), - }, - _ => return Err(ParseError::Header), - }; - Ok(res) - } -} - -impl Display for ContentRangeSpec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ContentRangeSpec::Bytes { - range, - instance_length, - } => { - f.write_str("bytes ")?; - match range { - Some((first_byte, last_byte)) => { - write!(f, "{}-{}", first_byte, last_byte)?; - } - None => { - f.write_str("*")?; - } - }; - f.write_str("/")?; - if let Some(v) = instance_length { - write!(f, "{}", v) - } else { - f.write_str("*") - } - } - ContentRangeSpec::Unregistered { - ref unit, - ref resp, - } => { - f.write_str(unit)?; - f.write_str(" ")?; - f.write_str(resp) - } - } - } -} - -impl IntoHeaderValue for ContentRangeSpec { - type Error = InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - HeaderValue::from_shared(writer.take()) - } -} diff --git a/src/header/common/content_type.rs b/src/header/common/content_type.rs deleted file mode 100644 index 3286d4cae..000000000 --- a/src/header/common/content_type.rs +++ /dev/null @@ -1,122 +0,0 @@ -use header::CONTENT_TYPE; -use mime::{self, Mime}; - -header! { - /// `Content-Type` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) - /// - /// The `Content-Type` header field indicates the media type of the - /// associated representation: either the representation enclosed in the - /// message payload or the selected representation, as determined by the - /// message semantics. The indicated media type defines both the data - /// format and how that data is intended to be processed by a recipient, - /// within the scope of the received message semantics, after any content - /// codings indicated by Content-Encoding are decoded. - /// - /// Although the `mime` crate allows the mime options to be any slice, this crate - /// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If - /// this is an issue, it's possible to implement `Header` on a custom struct. - /// - /// # ABNF - /// - /// ```text - /// Content-Type = media-type - /// ``` - /// - /// # Example values - /// - /// * `text/html; charset=utf-8` - /// * `application/json` - /// - /// # Examples - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::ContentType; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// ContentType::json() - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate mime; - /// # extern crate actix_http; - /// use mime::TEXT_HTML; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::ContentType; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// ContentType(TEXT_HTML) - /// ); - /// # } - /// ``` - (ContentType, CONTENT_TYPE) => [Mime] - - test_content_type { - test_header!( - test1, - vec![b"text/html"], - Some(HeaderField(TEXT_HTML))); - } -} - -impl ContentType { - /// A constructor to easily create a `Content-Type: application/json` - /// header. - #[inline] - pub fn json() -> ContentType { - ContentType(mime::APPLICATION_JSON) - } - - /// A constructor to easily create a `Content-Type: text/plain; - /// charset=utf-8` header. - #[inline] - pub fn plaintext() -> ContentType { - ContentType(mime::TEXT_PLAIN_UTF_8) - } - - /// A constructor to easily create a `Content-Type: text/html` header. - #[inline] - pub fn html() -> ContentType { - ContentType(mime::TEXT_HTML) - } - - /// A constructor to easily create a `Content-Type: text/xml` header. - #[inline] - pub fn xml() -> ContentType { - ContentType(mime::TEXT_XML) - } - - /// A constructor to easily create a `Content-Type: - /// application/www-form-url-encoded` header. - #[inline] - pub fn form_url_encoded() -> ContentType { - ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) - } - /// A constructor to easily create a `Content-Type: image/jpeg` header. - #[inline] - pub fn jpeg() -> ContentType { - ContentType(mime::IMAGE_JPEG) - } - - /// A constructor to easily create a `Content-Type: image/png` header. - #[inline] - pub fn png() -> ContentType { - ContentType(mime::IMAGE_PNG) - } - - /// A constructor to easily create a `Content-Type: - /// application/octet-stream` header. - #[inline] - pub fn octet_stream() -> ContentType { - ContentType(mime::APPLICATION_OCTET_STREAM) - } -} - -impl Eq for ContentType {} diff --git a/src/header/common/date.rs b/src/header/common/date.rs deleted file mode 100644 index 9ce2bd65f..000000000 --- a/src/header/common/date.rs +++ /dev/null @@ -1,42 +0,0 @@ -use header::{HttpDate, DATE}; -use std::time::SystemTime; - -header! { - /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) - /// - /// The `Date` header field represents the date and time at which the - /// message was originated. - /// - /// # ABNF - /// - /// ```text - /// Date = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Tue, 15 Nov 1994 08:12:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::Date; - /// use std::time::SystemTime; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(Date(SystemTime::now().into())); - /// ``` - (Date, DATE) => [HttpDate] - - test_date { - test_header!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); - } -} - -impl Date { - /// Create a date instance set to the current system time - pub fn now() -> Date { - Date(SystemTime::now().into()) - } -} diff --git a/src/header/common/etag.rs b/src/header/common/etag.rs deleted file mode 100644 index ea4be2a77..000000000 --- a/src/header/common/etag.rs +++ /dev/null @@ -1,96 +0,0 @@ -use header::{EntityTag, ETAG}; - -header! { - /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) - /// - /// The `ETag` header field in a response provides the current entity-tag - /// for the selected representation, as determined at the conclusion of - /// handling the request. An entity-tag is an opaque validator for - /// differentiating between multiple representations of the same - /// resource, regardless of whether those multiple representations are - /// due to resource state changes over time, content negotiation - /// resulting in multiple representations being valid at the same time, - /// or both. An entity-tag consists of an opaque quoted string, possibly - /// prefixed by a weakness indicator. - /// - /// # ABNF - /// - /// ```text - /// ETag = entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * `W/"xyzzy"` - /// * `""` - /// - /// # Examples - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{ETag, EntityTag}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned()))); - /// ``` - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{ETag, EntityTag}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned()))); - /// ``` - (ETag, ETAG) => [EntityTag] - - test_etag { - // From the RFC - test_header!(test1, - vec![b"\"xyzzy\""], - Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); - test_header!(test2, - vec![b"W/\"xyzzy\""], - Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); - test_header!(test3, - vec![b"\"\""], - Some(ETag(EntityTag::new(false, "".to_owned())))); - // Own tests - test_header!(test4, - vec![b"\"foobar\""], - Some(ETag(EntityTag::new(false, "foobar".to_owned())))); - test_header!(test5, - vec![b"\"\""], - Some(ETag(EntityTag::new(false, "".to_owned())))); - test_header!(test6, - vec![b"W/\"weak-etag\""], - Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); - test_header!(test7, - vec![b"W/\"\x65\x62\""], - Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); - test_header!(test8, - vec![b"W/\"\""], - Some(ETag(EntityTag::new(true, "".to_owned())))); - test_header!(test9, - vec![b"no-dquotes"], - None::); - test_header!(test10, - vec![b"w/\"the-first-w-is-case-sensitive\""], - None::); - test_header!(test11, - vec![b""], - None::); - test_header!(test12, - vec![b"\"unmatched-dquotes1"], - None::); - test_header!(test13, - vec![b"unmatched-dquotes2\""], - None::); - test_header!(test14, - vec![b"matched-\"dquotes\""], - None::); - test_header!(test15, - vec![b"\""], - None::); - } -} diff --git a/src/header/common/expires.rs b/src/header/common/expires.rs deleted file mode 100644 index bdd25fdb9..000000000 --- a/src/header/common/expires.rs +++ /dev/null @@ -1,39 +0,0 @@ -use header::{HttpDate, EXPIRES}; - -header! { - /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) - /// - /// The `Expires` header field gives the date/time after which the - /// response is considered stale. - /// - /// The presence of an Expires field does not imply that the original - /// resource will change or cease to exist at, before, or after that - /// time. - /// - /// # ABNF - /// - /// ```text - /// Expires = HTTP-date - /// ``` - /// - /// # Example values - /// * `Thu, 01 Dec 1994 16:00:00 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::Expires; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = HttpResponse::Ok(); - /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); - /// builder.set(Expires(expiration.into())); - /// ``` - (Expires, EXPIRES) => [HttpDate] - - test_expires { - // Test case from RFC - test_header!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); - } -} diff --git a/src/header/common/if_match.rs b/src/header/common/if_match.rs deleted file mode 100644 index 5f7976a4a..000000000 --- a/src/header/common/if_match.rs +++ /dev/null @@ -1,70 +0,0 @@ -use header::{EntityTag, IF_MATCH}; - -header! { - /// `If-Match` header, defined in - /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) - /// - /// The `If-Match` header field makes the request method conditional on - /// the recipient origin server either having at least one current - /// representation of the target resource, when the field-value is "*", - /// or having a current representation of the target resource that has an - /// entity-tag matching a member of the list of entity-tags provided in - /// the field-value. - /// - /// An origin server MUST use the strong comparison function when - /// comparing entity-tags for `If-Match`, since the client - /// intends this precondition to prevent the method from being applied if - /// there have been any changes to the representation data. - /// - /// # ABNF - /// - /// ```text - /// If-Match = "*" / 1#entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * "xyzzy", "r2d2xxxx", "c3piozzzz" - /// - /// # Examples - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::IfMatch; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(IfMatch::Any); - /// ``` - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{IfMatch, EntityTag}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// IfMatch::Items(vec![ - /// EntityTag::new(false, "xyzzy".to_owned()), - /// EntityTag::new(false, "foobar".to_owned()), - /// EntityTag::new(false, "bazquux".to_owned()), - /// ]) - /// ); - /// ``` - (IfMatch, IF_MATCH) => {Any / (EntityTag)+} - - test_if_match { - test_header!( - test1, - vec![b"\"xyzzy\""], - Some(HeaderField::Items( - vec![EntityTag::new(false, "xyzzy".to_owned())]))); - test_header!( - test2, - vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""], - Some(HeaderField::Items( - vec![EntityTag::new(false, "xyzzy".to_owned()), - EntityTag::new(false, "r2d2xxxx".to_owned()), - EntityTag::new(false, "c3piozzzz".to_owned())]))); - test_header!(test3, vec![b"*"], Some(IfMatch::Any)); - } -} diff --git a/src/header/common/if_modified_since.rs b/src/header/common/if_modified_since.rs deleted file mode 100644 index 41d6fba27..000000000 --- a/src/header/common/if_modified_since.rs +++ /dev/null @@ -1,39 +0,0 @@ -use header::{HttpDate, IF_MODIFIED_SINCE}; - -header! { - /// `If-Modified-Since` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) - /// - /// The `If-Modified-Since` header field makes a GET or HEAD request - /// method conditional on the selected representation's modification date - /// being more recent than the date provided in the field-value. - /// Transfer of the selected representation's data is avoided if that - /// data has not changed. - /// - /// # ABNF - /// - /// ```text - /// If-Unmodified-Since = HTTP-date - /// ``` - /// - /// # Example values - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::IfModifiedSince; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = HttpResponse::Ok(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(IfModifiedSince(modified.into())); - /// ``` - (IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate] - - test_if_modified_since { - // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - } -} diff --git a/src/header/common/if_none_match.rs b/src/header/common/if_none_match.rs deleted file mode 100644 index 8b3905bab..000000000 --- a/src/header/common/if_none_match.rs +++ /dev/null @@ -1,92 +0,0 @@ -use header::{EntityTag, IF_NONE_MATCH}; - -header! { - /// `If-None-Match` header, defined in - /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) - /// - /// The `If-None-Match` header field makes the request method conditional - /// on a recipient cache or origin server either not having any current - /// representation of the target resource, when the field-value is "*", - /// or having a selected representation with an entity-tag that does not - /// match any of those listed in the field-value. - /// - /// A recipient MUST use the weak comparison function when comparing - /// entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags - /// can be used for cache validation even if there have been changes to - /// the representation data. - /// - /// # ABNF - /// - /// ```text - /// If-None-Match = "*" / 1#entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * `W/"xyzzy"` - /// * `"xyzzy", "r2d2xxxx", "c3piozzzz"` - /// * `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"` - /// * `*` - /// - /// # Examples - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::IfNoneMatch; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(IfNoneMatch::Any); - /// ``` - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{IfNoneMatch, EntityTag}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// IfNoneMatch::Items(vec![ - /// EntityTag::new(false, "xyzzy".to_owned()), - /// EntityTag::new(false, "foobar".to_owned()), - /// EntityTag::new(false, "bazquux".to_owned()), - /// ]) - /// ); - /// ``` - (IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+} - - test_if_none_match { - test_header!(test1, vec![b"\"xyzzy\""]); - test_header!(test2, vec![b"W/\"xyzzy\""]); - test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); - test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); - test_header!(test5, vec![b"*"]); - } -} - -#[cfg(test)] -mod tests { - use super::IfNoneMatch; - use header::{EntityTag, Header, IF_NONE_MATCH}; - use test::TestRequest; - - #[test] - fn test_if_none_match() { - let mut if_none_match: Result; - - let req = TestRequest::with_header(IF_NONE_MATCH, "*").finish(); - if_none_match = Header::parse(&req); - assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any)); - - let req = - TestRequest::with_header(IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]) - .finish(); - - if_none_match = Header::parse(&req); - let mut entities: Vec = Vec::new(); - let foobar_etag = EntityTag::new(false, "foobar".to_owned()); - let weak_etag = EntityTag::new(true, "weak-etag".to_owned()); - entities.push(foobar_etag); - entities.push(weak_etag); - assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Items(entities))); - } -} diff --git a/src/header/common/if_range.rs b/src/header/common/if_range.rs deleted file mode 100644 index 8cbb8c897..000000000 --- a/src/header/common/if_range.rs +++ /dev/null @@ -1,117 +0,0 @@ -use error::ParseError; -use header::from_one_raw_str; -use header::{ - EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, - InvalidHeaderValueBytes, Writer, -}; -use http::header; -use httpmessage::HttpMessage; -use std::fmt::{self, Display, Write}; - -/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) -/// -/// If a client has a partial copy of a representation and wishes to have -/// an up-to-date copy of the entire representation, it could use the -/// Range header field with a conditional GET (using either or both of -/// If-Unmodified-Since and If-Match.) However, if the precondition -/// fails because the representation has been modified, the client would -/// then have to make a second request to obtain the entire current -/// representation. -/// -/// The `If-Range` header field allows a client to \"short-circuit\" the -/// second request. Informally, its meaning is as follows: if the -/// representation is unchanged, send me the part(s) that I am requesting -/// in Range; otherwise, send me the entire representation. -/// -/// # ABNF -/// -/// ```text -/// If-Range = entity-tag / HTTP-date -/// ``` -/// -/// # Example values -/// -/// * `Sat, 29 Oct 1994 19:43:31 GMT` -/// * `\"xyzzy\"` -/// -/// # Examples -/// -/// ```rust -/// use actix_http::HttpResponse; -/// use actix_http::http::header::{EntityTag, IfRange}; -/// -/// let mut builder = HttpResponse::Ok(); -/// builder.set(IfRange::EntityTag(EntityTag::new( -/// false, -/// "xyzzy".to_owned(), -/// ))); -/// ``` -/// -/// ```rust -/// use actix_http::HttpResponse; -/// use actix_http::http::header::IfRange; -/// use std::time::{Duration, SystemTime}; -/// -/// let mut builder = HttpResponse::Ok(); -/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); -/// builder.set(IfRange::Date(fetched.into())); -/// ``` -#[derive(Clone, Debug, PartialEq)] -pub enum IfRange { - /// The entity-tag the client has of the resource - EntityTag(EntityTag), - /// The date when the client retrieved the resource - Date(HttpDate), -} - -impl Header for IfRange { - fn name() -> HeaderName { - header::IF_RANGE - } - #[inline] - fn parse(msg: &T) -> Result - where - T: HttpMessage, - { - let etag: Result = - from_one_raw_str(msg.headers().get(header::IF_RANGE)); - if let Ok(etag) = etag { - return Ok(IfRange::EntityTag(etag)); - } - let date: Result = - from_one_raw_str(msg.headers().get(header::IF_RANGE)); - if let Ok(date) = date { - return Ok(IfRange::Date(date)); - } - Err(ParseError::Header) - } -} - -impl Display for IfRange { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - IfRange::EntityTag(ref x) => Display::fmt(x, f), - IfRange::Date(ref x) => Display::fmt(x, f), - } - } -} - -impl IntoHeaderValue for IfRange { - type Error = InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - HeaderValue::from_shared(writer.take()) - } -} - -#[cfg(test)] -mod test_if_range { - use super::IfRange as HeaderField; - use header::*; - use std::str; - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - test_header!(test2, vec![b"\"xyzzy\""]); - test_header!(test3, vec![b"this-is-invalid"], None::); -} diff --git a/src/header/common/if_unmodified_since.rs b/src/header/common/if_unmodified_since.rs deleted file mode 100644 index 02f9252e2..000000000 --- a/src/header/common/if_unmodified_since.rs +++ /dev/null @@ -1,40 +0,0 @@ -use header::{HttpDate, IF_UNMODIFIED_SINCE}; - -header! { - /// `If-Unmodified-Since` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) - /// - /// The `If-Unmodified-Since` header field makes the request method - /// conditional on the selected representation's last modification date - /// being earlier than or equal to the date provided in the field-value. - /// This field accomplishes the same purpose as If-Match for cases where - /// the user agent does not have an entity-tag for the representation. - /// - /// # ABNF - /// - /// ```text - /// If-Unmodified-Since = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::IfUnmodifiedSince; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = HttpResponse::Ok(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(IfUnmodifiedSince(modified.into())); - /// ``` - (IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate] - - test_if_unmodified_since { - // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - } -} diff --git a/src/header/common/last_modified.rs b/src/header/common/last_modified.rs deleted file mode 100644 index 608f43138..000000000 --- a/src/header/common/last_modified.rs +++ /dev/null @@ -1,38 +0,0 @@ -use header::{HttpDate, LAST_MODIFIED}; - -header! { - /// `Last-Modified` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) - /// - /// The `Last-Modified` header field in a response provides a timestamp - /// indicating the date and time at which the origin server believes the - /// selected representation was last modified, as determined at the - /// conclusion of handling the request. - /// - /// # ABNF - /// - /// ```text - /// Expires = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::LastModified; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = HttpResponse::Ok(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(LastModified(modified.into())); - /// ``` - (LastModified, LAST_MODIFIED) => [HttpDate] - - test_last_modified { - // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);} -} diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs deleted file mode 100644 index e6185b5a7..000000000 --- a/src/header/common/mod.rs +++ /dev/null @@ -1,350 +0,0 @@ -//! A Collection of Header implementations for common HTTP Headers. -//! -//! ## Mime -//! -//! Several header fields use MIME values for their contents. Keeping with the -//! strongly-typed theme, the [mime](https://docs.rs/mime) crate -//! is used, such as `ContentType(pub Mime)`. -#![cfg_attr(rustfmt, rustfmt_skip)] - -pub use self::accept_charset::AcceptCharset; -//pub use self::accept_encoding::AcceptEncoding; -pub use self::accept_language::AcceptLanguage; -pub use self::accept::Accept; -pub use self::allow::Allow; -pub use self::cache_control::{CacheControl, CacheDirective}; -pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam}; -pub use self::content_language::ContentLanguage; -pub use self::content_range::{ContentRange, ContentRangeSpec}; -pub use self::content_type::ContentType; -pub use self::date::Date; -pub use self::etag::ETag; -pub use self::expires::Expires; -pub use self::if_match::IfMatch; -pub use self::if_modified_since::IfModifiedSince; -pub use self::if_none_match::IfNoneMatch; -pub use self::if_range::IfRange; -pub use self::if_unmodified_since::IfUnmodifiedSince; -pub use self::last_modified::LastModified; -//pub use self::range::{Range, ByteRangeSpec}; - -#[doc(hidden)] -#[macro_export] -macro_rules! __hyper__deref { - ($from:ty => $to:ty) => { - impl ::std::ops::Deref for $from { - type Target = $to; - - #[inline] - fn deref(&self) -> &$to { - &self.0 - } - } - - impl ::std::ops::DerefMut for $from { - #[inline] - fn deref_mut(&mut self) -> &mut $to { - &mut self.0 - } - } - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __hyper__tm { - ($id:ident, $tm:ident{$($tf:item)*}) => { - #[allow(unused_imports)] - #[cfg(test)] - mod $tm{ - use std::str; - use http::Method; - use $crate::header::*; - use $crate::mime::*; - use super::$id as HeaderField; - $($tf)* - } - - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! test_header { - ($id:ident, $raw:expr) => { - #[test] - fn $id() { - use test; - let raw = $raw; - let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); - let mut req = test::TestRequest::default(); - for item in a { - req = req.header(HeaderField::name(), item); - } - let req = req.finish(); - let value = HeaderField::parse(&req); - let result = format!("{}", value.unwrap()); - let expected = String::from_utf8(raw[0].to_vec()).unwrap(); - let result_cmp: Vec = result - .to_ascii_lowercase() - .split(' ') - .map(|x| x.to_owned()) - .collect(); - let expected_cmp: Vec = expected - .to_ascii_lowercase() - .split(' ') - .map(|x| x.to_owned()) - .collect(); - assert_eq!(result_cmp.concat(), expected_cmp.concat()); - } - }; - ($id:ident, $raw:expr, $typed:expr) => { - #[test] - fn $id() { - use $crate::test; - let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); - let mut req = test::TestRequest::default(); - for item in a { - req = req.header(HeaderField::name(), item); - } - let req = req.finish(); - let val = HeaderField::parse(&req); - let typed: Option = $typed; - // Test parsing - assert_eq!(val.ok(), typed); - // Test formatting - if typed.is_some() { - let raw = &($raw)[..]; - let mut iter = raw.iter().map(|b|str::from_utf8(&b[..]).unwrap()); - let mut joined = String::new(); - joined.push_str(iter.next().unwrap()); - for s in iter { - joined.push_str(", "); - joined.push_str(s); - } - assert_eq!(format!("{}", typed.unwrap()), joined); - } - } - } -} - -#[macro_export] -macro_rules! header { - // $a:meta: Attributes associated with the header item (usually docs) - // $id:ident: Identifier of the header - // $n:expr: Lowercase name of the header - // $nn:expr: Nice name of the header - - // List header, zero or more items - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)*) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub Vec<$item>); - __hyper__deref!($id => Vec<$item>); - impl $crate::header::Header for $id { - #[inline] - fn name() -> $crate::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::header::from_comma_delimited( - msg.headers().get_all(Self::name())).map($id) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - $crate::header::fmt_comma_delimited(f, &self.0[..]) - } - } - impl $crate::header::IntoHeaderValue for $id { - type Error = $crate::header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::header::HeaderValue::from_shared(writer.take()) - } - } - }; - // List header, one or more items - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)+) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub Vec<$item>); - __hyper__deref!($id => Vec<$item>); - impl $crate::header::Header for $id { - #[inline] - fn name() -> $crate::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::header::from_comma_delimited( - msg.headers().get_all(Self::name())).map($id) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - $crate::header::fmt_comma_delimited(f, &self.0[..]) - } - } - impl $crate::header::IntoHeaderValue for $id { - type Error = $crate::header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::header::HeaderValue::from_shared(writer.take()) - } - } - }; - // Single value header - ($(#[$a:meta])*($id:ident, $name:expr) => [$value:ty]) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub $value); - __hyper__deref!($id => $value); - impl $crate::header::Header for $id { - #[inline] - fn name() -> $crate::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::header::from_one_raw_str( - msg.headers().get(Self::name())).map($id) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - ::std::fmt::Display::fmt(&self.0, f) - } - } - impl $crate::header::IntoHeaderValue for $id { - type Error = $crate::header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> { - self.0.try_into() - } - } - }; - // List header, one or more items with "*" option - ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub enum $id { - /// Any value is a match - Any, - /// Only the listed items are a match - Items(Vec<$item>), - } - impl $crate::header::Header for $id { - #[inline] - fn name() -> $crate::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - let any = msg.headers().get(Self::name()).and_then(|hdr| { - hdr.to_str().ok().and_then(|hdr| Some(hdr.trim() == "*"))}); - - if let Some(true) = any { - Ok($id::Any) - } else { - Ok($id::Items( - $crate::header::from_comma_delimited( - msg.headers().get_all(Self::name()))?)) - } - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - match *self { - $id::Any => f.write_str("*"), - $id::Items(ref fields) => $crate::header::fmt_comma_delimited( - f, &fields[..]) - } - } - } - impl $crate::header::IntoHeaderValue for $id { - type Error = $crate::header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::header::HeaderValue::from_shared(writer.take()) - } - } - }; - - // optional test module - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $name) => ($item)* - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $n) => ($item)+ - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* ($id, $name) => [$item] - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $name) => {Any / ($item)+} - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; -} - - -mod accept_charset; -//mod accept_encoding; -mod accept_language; -mod accept; -mod allow; -mod cache_control; -mod content_disposition; -mod content_language; -mod content_range; -mod content_type; -mod date; -mod etag; -mod expires; -mod if_match; -mod if_modified_since; -mod if_none_match; -mod if_range; -mod if_unmodified_since; -mod last_modified; -//mod range; diff --git a/src/header/common/range.rs b/src/header/common/range.rs deleted file mode 100644 index 71718fc7a..000000000 --- a/src/header/common/range.rs +++ /dev/null @@ -1,434 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; - -use header::parsing::from_one_raw_str; -use header::{Header, Raw}; - -/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1) -/// -/// The "Range" header field on a GET request modifies the method -/// semantics to request transfer of only one or more subranges of the -/// selected representation data, rather than the entire selected -/// representation data. -/// -/// # ABNF -/// -/// ```text -/// Range = byte-ranges-specifier / other-ranges-specifier -/// other-ranges-specifier = other-range-unit "=" other-range-set -/// other-range-set = 1*VCHAR -/// -/// bytes-unit = "bytes" -/// -/// byte-ranges-specifier = bytes-unit "=" byte-range-set -/// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec) -/// byte-range-spec = first-byte-pos "-" [last-byte-pos] -/// first-byte-pos = 1*DIGIT -/// last-byte-pos = 1*DIGIT -/// ``` -/// -/// # Example values -/// -/// * `bytes=1000-` -/// * `bytes=-2000` -/// * `bytes=0-1,30-40` -/// * `bytes=0-10,20-90,-100` -/// * `custom_unit=0-123` -/// * `custom_unit=xxx-yyy` -/// -/// # Examples -/// -/// ``` -/// use hyper::header::{Headers, Range, ByteRangeSpec}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Range::Bytes( -/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] -/// )); -/// -/// headers.clear(); -/// headers.set(Range::Unregistered("letters".to_owned(), "a-f".to_owned())); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, Range}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Range::bytes(1, 100)); -/// -/// headers.clear(); -/// headers.set(Range::bytes_multi(vec![(1, 100), (200, 300)])); -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub enum Range { - /// Byte range - Bytes(Vec), - /// Custom range, with unit not registered at IANA - /// (`other-range-unit`: String , `other-range-set`: String) - Unregistered(String, String), -} - -/// Each `Range::Bytes` header can contain one or more `ByteRangeSpecs`. -/// Each `ByteRangeSpec` defines a range of bytes to fetch -#[derive(PartialEq, Clone, Debug)] -pub enum ByteRangeSpec { - /// Get all bytes between x and y ("x-y") - FromTo(u64, u64), - /// Get all bytes starting from x ("x-") - AllFrom(u64), - /// Get last x bytes ("-x") - Last(u64), -} - -impl ByteRangeSpec { - /// Given the full length of the entity, attempt to normalize the byte range - /// into an satisfiable end-inclusive (from, to) range. - /// - /// The resulting range is guaranteed to be a satisfiable range within the - /// bounds of `0 <= from <= to < full_length`. - /// - /// If the byte range is deemed unsatisfiable, `None` is returned. - /// An unsatisfiable range is generally cause for a server to either reject - /// the client request with a `416 Range Not Satisfiable` status code, or to - /// simply ignore the range header and serve the full entity using a `200 - /// OK` status code. - /// - /// This function closely follows [RFC 7233][1] section 2.1. - /// As such, it considers ranges to be satisfiable if they meet the - /// following conditions: - /// - /// > If a valid byte-range-set includes at least one byte-range-spec with - /// a first-byte-pos that is less than the current length of the - /// representation, or at least one suffix-byte-range-spec with a - /// non-zero suffix-length, then the byte-range-set is satisfiable. - /// Otherwise, the byte-range-set is unsatisfiable. - /// - /// The function also computes remainder ranges based on the RFC: - /// - /// > If the last-byte-pos value is - /// absent, or if the value is greater than or equal to the current - /// length of the representation data, the byte range is interpreted as - /// the remainder of the representation (i.e., the server replaces the - /// value of last-byte-pos with a value that is one less than the current - /// length of the selected representation). - /// - /// [1]: https://tools.ietf.org/html/rfc7233 - pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> { - // If the full length is zero, there is no satisfiable end-inclusive range. - if full_length == 0 { - return None; - } - match self { - &ByteRangeSpec::FromTo(from, to) => { - if from < full_length && from <= to { - Some((from, ::std::cmp::min(to, full_length - 1))) - } else { - None - } - } - &ByteRangeSpec::AllFrom(from) => { - if from < full_length { - Some((from, full_length - 1)) - } else { - None - } - } - &ByteRangeSpec::Last(last) => { - if last > 0 { - // From the RFC: If the selected representation is shorter - // than the specified suffix-length, - // the entire representation is used. - if last > full_length { - Some((0, full_length - 1)) - } else { - Some((full_length - last, full_length - 1)) - } - } else { - None - } - } - } - } -} - -impl Range { - /// Get the most common byte range header ("bytes=from-to") - pub fn bytes(from: u64, to: u64) -> Range { - Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)]) - } - - /// Get byte range header with multiple subranges - /// ("bytes=from1-to1,from2-to2,fromX-toX") - pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range { - Range::Bytes( - ranges - .iter() - .map(|r| ByteRangeSpec::FromTo(r.0, r.1)) - .collect(), - ) - } -} - -impl fmt::Display for ByteRangeSpec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to), - ByteRangeSpec::Last(pos) => write!(f, "-{}", pos), - ByteRangeSpec::AllFrom(pos) => write!(f, "{}-", pos), - } - } -} - -impl fmt::Display for Range { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Range::Bytes(ref ranges) => { - try!(write!(f, "bytes=")); - - for (i, range) in ranges.iter().enumerate() { - if i != 0 { - try!(f.write_str(",")); - } - try!(Display::fmt(range, f)); - } - Ok(()) - } - Range::Unregistered(ref unit, ref range_str) => { - write!(f, "{}={}", unit, range_str) - } - } - } -} - -impl FromStr for Range { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - let mut iter = s.splitn(2, '='); - - match (iter.next(), iter.next()) { - (Some("bytes"), Some(ranges)) => { - let ranges = from_comma_delimited(ranges); - if ranges.is_empty() { - return Err(::Error::Header); - } - Ok(Range::Bytes(ranges)) - } - (Some(unit), Some(range_str)) if unit != "" && range_str != "" => Ok( - Range::Unregistered(unit.to_owned(), range_str.to_owned()), - ), - _ => Err(::Error::Header), - } - } -} - -impl FromStr for ByteRangeSpec { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - let mut parts = s.splitn(2, '-'); - - match (parts.next(), parts.next()) { - (Some(""), Some(end)) => end.parse() - .or(Err(::Error::Header)) - .map(ByteRangeSpec::Last), - (Some(start), Some("")) => start - .parse() - .or(Err(::Error::Header)) - .map(ByteRangeSpec::AllFrom), - (Some(start), Some(end)) => match (start.parse(), end.parse()) { - (Ok(start), Ok(end)) if start <= end => { - Ok(ByteRangeSpec::FromTo(start, end)) - } - _ => Err(::Error::Header), - }, - _ => Err(::Error::Header), - } - } -} - -fn from_comma_delimited(s: &str) -> Vec { - s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y), - }) - .filter_map(|x| x.parse().ok()) - .collect() -} - -impl Header for Range { - fn header_name() -> &'static str { - static NAME: &'static str = "Range"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - from_one_raw_str(raw) - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -#[test] -fn test_parse_bytes_range_valid() { - let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap(); - let r3 = Range::bytes(1, 100); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap(); - let r2: Range = - Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); - let r3 = Range::Bytes(vec![ - ByteRangeSpec::FromTo(1, 100), - ByteRangeSpec::AllFrom(200), - ]); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap(); - let r3 = Range::Bytes(vec![ - ByteRangeSpec::FromTo(1, 100), - ByteRangeSpec::Last(100), - ]); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); - assert_eq!(r, r2); -} - -#[test] -fn test_parse_unregistered_range_valid() { - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); - assert_eq!(r, r2); - - let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned()); - assert_eq!(r, r2); - - let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned()); - assert_eq!(r, r2); -} - -#[test] -fn test_parse_invalid() { - let r: ::Result = Header::parse_header(&"bytes=1-a,-".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=1-2-3".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"abc".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=1-100=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"custom=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"=1-100".into()); - assert_eq!(r.ok(), None); -} - -#[test] -fn test_fmt() { - use header::Headers; - - let mut headers = Headers::new(); - - headers.set(Range::Bytes(vec![ - ByteRangeSpec::FromTo(0, 1000), - ByteRangeSpec::AllFrom(2000), - ])); - assert_eq!(&headers.to_string(), "Range: bytes=0-1000,2000-\r\n"); - - headers.clear(); - headers.set(Range::Bytes(vec![])); - - assert_eq!(&headers.to_string(), "Range: bytes=\r\n"); - - headers.clear(); - headers.set(Range::Unregistered( - "custom".to_owned(), - "1-xxx".to_owned(), - )); - - assert_eq!(&headers.to_string(), "Range: custom=1-xxx\r\n"); -} - -#[test] -fn test_byte_range_spec_to_satisfiable_range() { - assert_eq!( - Some((0, 0)), - ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(3) - ); - assert_eq!( - Some((1, 2)), - ByteRangeSpec::FromTo(1, 2).to_satisfiable_range(3) - ); - assert_eq!( - Some((1, 2)), - ByteRangeSpec::FromTo(1, 5).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::FromTo(3, 3).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0) - ); - - assert_eq!( - Some((0, 2)), - ByteRangeSpec::AllFrom(0).to_satisfiable_range(3) - ); - assert_eq!( - Some((2, 2)), - ByteRangeSpec::AllFrom(2).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::AllFrom(3).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::AllFrom(5).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::AllFrom(0).to_satisfiable_range(0) - ); - - assert_eq!( - Some((1, 2)), - ByteRangeSpec::Last(2).to_satisfiable_range(3) - ); - assert_eq!( - Some((2, 2)), - ByteRangeSpec::Last(1).to_satisfiable_range(3) - ); - assert_eq!( - Some((0, 2)), - ByteRangeSpec::Last(5).to_satisfiable_range(3) - ); - assert_eq!(None, ByteRangeSpec::Last(0).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::Last(2).to_satisfiable_range(0)); -} diff --git a/src/header/mod.rs b/src/header/mod.rs deleted file mode 100644 index 74e4b03e5..000000000 --- a/src/header/mod.rs +++ /dev/null @@ -1,471 +0,0 @@ -//! Various http headers -// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header) - -use std::fmt; -use std::str::FromStr; - -use bytes::{Bytes, BytesMut}; -use mime::Mime; -use modhttp::header::GetAll; -use modhttp::Error as HttpError; -use percent_encoding; - -pub use modhttp::header::*; - -use error::ParseError; -use httpmessage::HttpMessage; - -mod common; -mod shared; -#[doc(hidden)] -pub use self::common::*; -#[doc(hidden)] -pub use self::shared::*; - -#[doc(hidden)] -/// A trait for any object that will represent a header field and value. -pub trait Header -where - Self: IntoHeaderValue, -{ - /// Returns the name of the header field - fn name() -> HeaderName; - - /// Parse a header - fn parse(msg: &T) -> Result; -} - -#[doc(hidden)] -/// A trait for any object that can be Converted to a `HeaderValue` -pub trait IntoHeaderValue: Sized { - /// The type returned in the event of a conversion error. - type Error: Into; - - /// Try to convert value to a Header value. - fn try_into(self) -> Result; -} - -impl IntoHeaderValue for HeaderValue { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - Ok(self) - } -} - -impl<'a> IntoHeaderValue for &'a str { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - self.parse() - } -} - -impl<'a> IntoHeaderValue for &'a [u8] { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_bytes(self) - } -} - -impl IntoHeaderValue for Bytes { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(self) - } -} - -impl IntoHeaderValue for Vec { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(self)) - } -} - -impl IntoHeaderValue for String { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(self)) - } -} - -impl IntoHeaderValue for Mime { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(format!("{}", self))) - } -} - -/// Represents supported types of content encodings -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum ContentEncoding { - /// Automatically select encoding based on encoding negotiation - Auto, - /// A format using the Brotli algorithm - #[cfg(feature = "brotli")] - Br, - /// A format using the zlib structure with deflate algorithm - #[cfg(feature = "flate2")] - Deflate, - /// Gzip algorithm - #[cfg(feature = "flate2")] - Gzip, - /// Indicates the identity function (i.e. no compression, nor modification) - Identity, -} - -impl ContentEncoding { - #[inline] - /// Is the content compressed? - pub fn is_compression(self) -> bool { - match self { - ContentEncoding::Identity | ContentEncoding::Auto => false, - _ => true, - } - } - - #[inline] - /// Convert content encoding to string - pub fn as_str(self) -> &'static str { - match self { - #[cfg(feature = "brotli")] - ContentEncoding::Br => "br", - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => "gzip", - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => "deflate", - ContentEncoding::Identity | ContentEncoding::Auto => "identity", - } - } - - #[inline] - /// default quality value - pub fn quality(self) -> f64 { - match self { - #[cfg(feature = "brotli")] - ContentEncoding::Br => 1.1, - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => 1.0, - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => 0.9, - ContentEncoding::Identity | ContentEncoding::Auto => 0.1, - } - } -} - -// TODO: remove memory allocation -impl<'a> From<&'a str> for ContentEncoding { - fn from(s: &'a str) -> ContentEncoding { - match AsRef::::as_ref(&s.trim().to_lowercase()) { - #[cfg(feature = "brotli")] - "br" => ContentEncoding::Br, - #[cfg(feature = "flate2")] - "gzip" => ContentEncoding::Gzip, - #[cfg(feature = "flate2")] - "deflate" => ContentEncoding::Deflate, - _ => ContentEncoding::Identity, - } - } -} - -#[doc(hidden)] -pub(crate) struct Writer { - buf: BytesMut, -} - -impl Writer { - fn new() -> Writer { - Writer { - buf: BytesMut::new(), - } - } - fn take(&mut self) -> Bytes { - self.buf.take().freeze() - } -} - -impl fmt::Write for Writer { - #[inline] - fn write_str(&mut self, s: &str) -> fmt::Result { - self.buf.extend_from_slice(s.as_bytes()); - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { - fmt::write(self, args) - } -} - -#[inline] -#[doc(hidden)] -/// Reads a comma-delimited raw header into a Vec. -pub fn from_comma_delimited( - all: GetAll, -) -> Result, ParseError> { - let mut result = Vec::new(); - for h in all { - let s = h.to_str().map_err(|_| ParseError::Header)?; - result.extend( - s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y), - }).filter_map(|x| x.trim().parse().ok()), - ) - } - Ok(result) -} - -#[inline] -#[doc(hidden)] -/// Reads a single string when parsing a header. -pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { - if let Some(line) = val { - let line = line.to_str().map_err(|_| ParseError::Header)?; - if !line.is_empty() { - return T::from_str(line).or(Err(ParseError::Header)); - } - } - Err(ParseError::Header) -} - -#[inline] -#[doc(hidden)] -/// Format an array into a comma-delimited string. -pub fn fmt_comma_delimited(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result -where - T: fmt::Display, -{ - let mut iter = parts.iter(); - if let Some(part) = iter.next() { - fmt::Display::fmt(part, f)?; - } - for part in iter { - f.write_str(", ")?; - fmt::Display::fmt(part, f)?; - } - Ok(()) -} - -// From hyper v0.11.27 src/header/parsing.rs - -/// The value part of an extended parameter consisting of three parts: -/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`), -/// and a character sequence representing the actual value (`value`), separated by single quote -/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). -#[derive(Clone, Debug, PartialEq)] -pub struct ExtendedValue { - /// The character set that is used to encode the `value` to a string. - pub charset: Charset, - /// The human language details of the `value`, if available. - pub language_tag: Option, - /// The parameter value, as expressed in octets. - pub value: Vec, -} - -/// Parses extended header parameter values (`ext-value`), as defined in -/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). -/// -/// Extended values are denoted by parameter names that end with `*`. -/// -/// ## ABNF -/// -/// ```text -/// ext-value = charset "'" [ language ] "'" value-chars -/// ; like RFC 2231's -/// ; (see [RFC2231], Section 7) -/// -/// charset = "UTF-8" / "ISO-8859-1" / mime-charset -/// -/// mime-charset = 1*mime-charsetc -/// mime-charsetc = ALPHA / DIGIT -/// / "!" / "#" / "$" / "%" / "&" -/// / "+" / "-" / "^" / "_" / "`" -/// / "{" / "}" / "~" -/// ; as in Section 2.3 of [RFC2978] -/// ; except that the single quote is not included -/// ; SHOULD be registered in the IANA charset registry -/// -/// language = -/// -/// value-chars = *( pct-encoded / attr-char ) -/// -/// pct-encoded = "%" HEXDIG HEXDIG -/// ; see [RFC3986], Section 2.1 -/// -/// attr-char = ALPHA / DIGIT -/// / "!" / "#" / "$" / "&" / "+" / "-" / "." -/// / "^" / "_" / "`" / "|" / "~" -/// ; token except ( "*" / "'" / "%" ) -/// ``` -pub fn parse_extended_value(val: &str) -> Result { - // Break into three pieces separated by the single-quote character - let mut parts = val.splitn(3, '\''); - - // Interpret the first piece as a Charset - let charset: Charset = match parts.next() { - None => return Err(::error::ParseError::Header), - Some(n) => FromStr::from_str(n).map_err(|_| ::error::ParseError::Header)?, - }; - - // Interpret the second piece as a language tag - let language_tag: Option = match parts.next() { - None => return Err(::error::ParseError::Header), - Some("") => None, - Some(s) => match s.parse() { - Ok(lt) => Some(lt), - Err(_) => return Err(::error::ParseError::Header), - }, - }; - - // Interpret the third piece as a sequence of value characters - let value: Vec = match parts.next() { - None => return Err(::error::ParseError::Header), - Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(), - }; - - Ok(ExtendedValue { - value, - charset, - language_tag, - }) -} - -impl fmt::Display for ExtendedValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let encoded_value = percent_encoding::percent_encode( - &self.value[..], - self::percent_encoding_http::HTTP_VALUE, - ); - if let Some(ref lang) = self.language_tag { - write!(f, "{}'{}'{}", self.charset, lang, encoded_value) - } else { - write!(f, "{}''{}", self.charset, encoded_value) - } - } -} - -/// Percent encode a sequence of bytes with a character set defined in -/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] -/// -/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 -pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { - let encoded = - percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); - fmt::Display::fmt(&encoded, f) -} -mod percent_encoding_http { - use percent_encoding; - - // internal module because macro is hard-coded to make a public item - // but we don't want to public export this item - define_encode_set! { - // This encode set is used for HTTP header values and is defined at - // https://tools.ietf.org/html/rfc5987#section-3.2 - pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { - ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', - '[', '\\', ']', '{', '}' - } - } -} - -#[cfg(test)] -mod tests { - use super::{parse_extended_value, ExtendedValue}; - use header::shared::Charset; - use language_tags::LanguageTag; - - #[test] - fn test_parse_extended_value_with_encoding_and_language_tag() { - let expected_language_tag = "en".parse::().unwrap(); - // RFC 5987, Section 3.2.2 - // Extended notation, using the Unicode character U+00A3 (POUND SIGN) - let result = parse_extended_value("iso-8859-1'en'%A3%20rates"); - assert!(result.is_ok()); - let extended_value = result.unwrap(); - assert_eq!(Charset::Iso_8859_1, extended_value.charset); - assert!(extended_value.language_tag.is_some()); - assert_eq!(expected_language_tag, extended_value.language_tag.unwrap()); - assert_eq!( - vec![163, b' ', b'r', b'a', b't', b'e', b's'], - extended_value.value - ); - } - - #[test] - fn test_parse_extended_value_with_encoding() { - // RFC 5987, Section 3.2.2 - // Extended notation, using the Unicode characters U+00A3 (POUND SIGN) - // and U+20AC (EURO SIGN) - let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); - assert!(result.is_ok()); - let extended_value = result.unwrap(); - assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset); - assert!(extended_value.language_tag.is_none()); - assert_eq!( - vec![ - 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', - b't', b'e', b's', - ], - extended_value.value - ); - } - - #[test] - fn test_parse_extended_value_missing_language_tag_and_encoding() { - // From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2 - let result = parse_extended_value("foo%20bar.html"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_extended_value_partially_formatted() { - let result = parse_extended_value("UTF-8'missing third part"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_extended_value_partially_formatted_blank() { - let result = parse_extended_value("blank second part'"); - assert!(result.is_err()); - } - - #[test] - fn test_fmt_extended_value_with_encoding_and_language_tag() { - let extended_value = ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: Some("en".parse().expect("Could not parse language tag")), - value: vec![163, b' ', b'r', b'a', b't', b'e', b's'], - }; - assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value)); - } - - #[test] - fn test_fmt_extended_value_with_encoding() { - let extended_value = ExtendedValue { - charset: Charset::Ext("UTF-8".to_string()), - language_tag: None, - value: vec![ - 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', - b't', b'e', b's', - ], - }; - assert_eq!( - "UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", - format!("{}", extended_value) - ); - } -} diff --git a/src/header/shared/charset.rs b/src/header/shared/charset.rs deleted file mode 100644 index b679971b0..000000000 --- a/src/header/shared/charset.rs +++ /dev/null @@ -1,152 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; - -use self::Charset::*; - -/// A Mime charset. -/// -/// The string representation is normalized to upper case. -/// -/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url]. -/// -/// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml -#[derive(Clone, Debug, PartialEq)] -#[allow(non_camel_case_types)] -pub enum Charset { - /// US ASCII - Us_Ascii, - /// ISO-8859-1 - Iso_8859_1, - /// ISO-8859-2 - Iso_8859_2, - /// ISO-8859-3 - Iso_8859_3, - /// ISO-8859-4 - Iso_8859_4, - /// ISO-8859-5 - Iso_8859_5, - /// ISO-8859-6 - Iso_8859_6, - /// ISO-8859-7 - Iso_8859_7, - /// ISO-8859-8 - Iso_8859_8, - /// ISO-8859-9 - Iso_8859_9, - /// ISO-8859-10 - Iso_8859_10, - /// Shift_JIS - Shift_Jis, - /// EUC-JP - Euc_Jp, - /// ISO-2022-KR - Iso_2022_Kr, - /// EUC-KR - Euc_Kr, - /// ISO-2022-JP - Iso_2022_Jp, - /// ISO-2022-JP-2 - Iso_2022_Jp_2, - /// ISO-8859-6-E - Iso_8859_6_E, - /// ISO-8859-6-I - Iso_8859_6_I, - /// ISO-8859-8-E - Iso_8859_8_E, - /// ISO-8859-8-I - Iso_8859_8_I, - /// GB2312 - Gb2312, - /// Big5 - Big5, - /// KOI8-R - Koi8_R, - /// An arbitrary charset specified as a string - Ext(String), -} - -impl Charset { - fn label(&self) -> &str { - match *self { - Us_Ascii => "US-ASCII", - Iso_8859_1 => "ISO-8859-1", - Iso_8859_2 => "ISO-8859-2", - Iso_8859_3 => "ISO-8859-3", - Iso_8859_4 => "ISO-8859-4", - Iso_8859_5 => "ISO-8859-5", - Iso_8859_6 => "ISO-8859-6", - Iso_8859_7 => "ISO-8859-7", - Iso_8859_8 => "ISO-8859-8", - Iso_8859_9 => "ISO-8859-9", - Iso_8859_10 => "ISO-8859-10", - Shift_Jis => "Shift-JIS", - Euc_Jp => "EUC-JP", - Iso_2022_Kr => "ISO-2022-KR", - Euc_Kr => "EUC-KR", - Iso_2022_Jp => "ISO-2022-JP", - Iso_2022_Jp_2 => "ISO-2022-JP-2", - Iso_8859_6_E => "ISO-8859-6-E", - Iso_8859_6_I => "ISO-8859-6-I", - Iso_8859_8_E => "ISO-8859-8-E", - Iso_8859_8_I => "ISO-8859-8-I", - Gb2312 => "GB2312", - Big5 => "big5", - Koi8_R => "KOI8-R", - Ext(ref s) => s, - } - } -} - -impl Display for Charset { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(self.label()) - } -} - -impl FromStr for Charset { - type Err = ::Error; - fn from_str(s: &str) -> ::Result { - Ok(match s.to_ascii_uppercase().as_ref() { - "US-ASCII" => Us_Ascii, - "ISO-8859-1" => Iso_8859_1, - "ISO-8859-2" => Iso_8859_2, - "ISO-8859-3" => Iso_8859_3, - "ISO-8859-4" => Iso_8859_4, - "ISO-8859-5" => Iso_8859_5, - "ISO-8859-6" => Iso_8859_6, - "ISO-8859-7" => Iso_8859_7, - "ISO-8859-8" => Iso_8859_8, - "ISO-8859-9" => Iso_8859_9, - "ISO-8859-10" => Iso_8859_10, - "SHIFT-JIS" => Shift_Jis, - "EUC-JP" => Euc_Jp, - "ISO-2022-KR" => Iso_2022_Kr, - "EUC-KR" => Euc_Kr, - "ISO-2022-JP" => Iso_2022_Jp, - "ISO-2022-JP-2" => Iso_2022_Jp_2, - "ISO-8859-6-E" => Iso_8859_6_E, - "ISO-8859-6-I" => Iso_8859_6_I, - "ISO-8859-8-E" => Iso_8859_8_E, - "ISO-8859-8-I" => Iso_8859_8_I, - "GB2312" => Gb2312, - "big5" => Big5, - "KOI8-R" => Koi8_R, - s => Ext(s.to_owned()), - }) - } -} - -#[test] -fn test_parse() { - assert_eq!(Us_Ascii, "us-ascii".parse().unwrap()); - assert_eq!(Us_Ascii, "US-Ascii".parse().unwrap()); - assert_eq!(Us_Ascii, "US-ASCII".parse().unwrap()); - assert_eq!(Shift_Jis, "Shift-JIS".parse().unwrap()); - assert_eq!(Ext("ABCD".to_owned()), "abcd".parse().unwrap()); -} - -#[test] -fn test_display() { - assert_eq!("US-ASCII", format!("{}", Us_Ascii)); - assert_eq!("ABCD", format!("{}", Ext("ABCD".to_owned()))); -} diff --git a/src/header/shared/encoding.rs b/src/header/shared/encoding.rs deleted file mode 100644 index 64027d8a5..000000000 --- a/src/header/shared/encoding.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::fmt; -use std::str; - -pub use self::Encoding::{ - Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers, -}; - -/// A value to represent an encoding used in `Transfer-Encoding` -/// or `Accept-Encoding` header. -#[derive(Clone, PartialEq, Debug)] -pub enum Encoding { - /// The `chunked` encoding. - Chunked, - /// The `br` encoding. - Brotli, - /// The `gzip` encoding. - Gzip, - /// The `deflate` encoding. - Deflate, - /// The `compress` encoding. - Compress, - /// The `identity` encoding. - Identity, - /// The `trailers` encoding. - Trailers, - /// Some other encoding that is less common, can be any String. - EncodingExt(String), -} - -impl fmt::Display for Encoding { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { - Chunked => "chunked", - Brotli => "br", - Gzip => "gzip", - Deflate => "deflate", - Compress => "compress", - Identity => "identity", - Trailers => "trailers", - EncodingExt(ref s) => s.as_ref(), - }) - } -} - -impl str::FromStr for Encoding { - type Err = ::error::ParseError; - fn from_str(s: &str) -> Result { - match s { - "chunked" => Ok(Chunked), - "br" => Ok(Brotli), - "deflate" => Ok(Deflate), - "gzip" => Ok(Gzip), - "compress" => Ok(Compress), - "identity" => Ok(Identity), - "trailers" => Ok(Trailers), - _ => Ok(EncodingExt(s.to_owned())), - } - } -} diff --git a/src/header/shared/entity.rs b/src/header/shared/entity.rs deleted file mode 100644 index 0d3b0a4ef..000000000 --- a/src/header/shared/entity.rs +++ /dev/null @@ -1,266 +0,0 @@ -use header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer}; -use std::fmt::{self, Display, Write}; -use std::str::FromStr; - -/// check that each char in the slice is either: -/// 1. `%x21`, or -/// 2. in the range `%x23` to `%x7E`, or -/// 3. above `%x80` -fn check_slice_validity(slice: &str) -> bool { - slice - .bytes() - .all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80')) -} - -/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3) -/// -/// An entity tag consists of a string enclosed by two literal double quotes. -/// Preceding the first double quote is an optional weakness indicator, -/// which always looks like `W/`. Examples for valid tags are `"xyzzy"` and -/// `W/"xyzzy"`. -/// -/// # ABNF -/// -/// ```text -/// entity-tag = [ weak ] opaque-tag -/// weak = %x57.2F ; "W/", case-sensitive -/// opaque-tag = DQUOTE *etagc DQUOTE -/// etagc = %x21 / %x23-7E / obs-text -/// ; VCHAR except double quotes, plus obs-text -/// ``` -/// -/// # Comparison -/// To check if two entity tags are equivalent in an application always use the -/// `strong_eq` or `weak_eq` methods based on the context of the Tag. Only use -/// `==` to check if two tags are identical. -/// -/// The example below shows the results for a set of entity-tag pairs and -/// both the weak and strong comparison function results: -/// -/// | `ETag 1`| `ETag 2`| Strong Comparison | Weak Comparison | -/// |---------|---------|-------------------|-----------------| -/// | `W/"1"` | `W/"1"` | no match | match | -/// | `W/"1"` | `W/"2"` | no match | no match | -/// | `W/"1"` | `"1"` | no match | match | -/// | `"1"` | `"1"` | match | match | -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct EntityTag { - /// Weakness indicator for the tag - pub weak: bool, - /// The opaque string in between the DQUOTEs - tag: String, -} - -impl EntityTag { - /// Constructs a new EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn new(weak: bool, tag: String) -> EntityTag { - assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); - EntityTag { weak, tag } - } - - /// Constructs a new weak EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn weak(tag: String) -> EntityTag { - EntityTag::new(true, tag) - } - - /// Constructs a new strong EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn strong(tag: String) -> EntityTag { - EntityTag::new(false, tag) - } - - /// Get the tag. - pub fn tag(&self) -> &str { - self.tag.as_ref() - } - - /// Set the tag. - /// # Panics - /// If the tag contains invalid characters. - pub fn set_tag(&mut self, tag: String) { - assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); - self.tag = tag - } - - /// For strong comparison two entity-tags are equivalent if both are not - /// weak and their opaque-tags match character-by-character. - pub fn strong_eq(&self, other: &EntityTag) -> bool { - !self.weak && !other.weak && self.tag == other.tag - } - - /// For weak comparison two entity-tags are equivalent if their - /// opaque-tags match character-by-character, regardless of either or - /// both being tagged as "weak". - pub fn weak_eq(&self, other: &EntityTag) -> bool { - self.tag == other.tag - } - - /// The inverse of `EntityTag.strong_eq()`. - pub fn strong_ne(&self, other: &EntityTag) -> bool { - !self.strong_eq(other) - } - - /// The inverse of `EntityTag.weak_eq()`. - pub fn weak_ne(&self, other: &EntityTag) -> bool { - !self.weak_eq(other) - } -} - -impl Display for EntityTag { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.weak { - write!(f, "W/\"{}\"", self.tag) - } else { - write!(f, "\"{}\"", self.tag) - } - } -} - -impl FromStr for EntityTag { - type Err = ::error::ParseError; - - fn from_str(s: &str) -> Result { - let length: usize = s.len(); - let slice = &s[..]; - // Early exits if it doesn't terminate in a DQUOTE. - if !slice.ends_with('"') || slice.len() < 2 { - return Err(::error::ParseError::Header); - } - // The etag is weak if its first char is not a DQUOTE. - if slice.len() >= 2 - && slice.starts_with('"') - && check_slice_validity(&slice[1..length - 1]) - { - // No need to check if the last char is a DQUOTE, - // we already did that above. - return Ok(EntityTag { - weak: false, - tag: slice[1..length - 1].to_owned(), - }); - } else if slice.len() >= 4 - && slice.starts_with("W/\"") - && check_slice_validity(&slice[3..length - 1]) - { - return Ok(EntityTag { - weak: true, - tag: slice[3..length - 1].to_owned(), - }); - } - Err(::error::ParseError::Header) - } -} - -impl IntoHeaderValue for EntityTag { - type Error = InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut wrt = Writer::new(); - write!(wrt, "{}", self).unwrap(); - HeaderValue::from_shared(wrt.take()) - } -} - -#[cfg(test)] -mod tests { - use super::EntityTag; - - #[test] - fn test_etag_parse_success() { - // Expected success - assert_eq!( - "\"foobar\"".parse::().unwrap(), - EntityTag::strong("foobar".to_owned()) - ); - assert_eq!( - "\"\"".parse::().unwrap(), - EntityTag::strong("".to_owned()) - ); - assert_eq!( - "W/\"weaktag\"".parse::().unwrap(), - EntityTag::weak("weaktag".to_owned()) - ); - assert_eq!( - "W/\"\x65\x62\"".parse::().unwrap(), - EntityTag::weak("\x65\x62".to_owned()) - ); - assert_eq!( - "W/\"\"".parse::().unwrap(), - EntityTag::weak("".to_owned()) - ); - } - - #[test] - fn test_etag_parse_failures() { - // Expected failures - assert!("no-dquotes".parse::().is_err()); - assert!( - "w/\"the-first-w-is-case-sensitive\"" - .parse::() - .is_err() - ); - assert!("".parse::().is_err()); - assert!("\"unmatched-dquotes1".parse::().is_err()); - assert!("unmatched-dquotes2\"".parse::().is_err()); - assert!("matched-\"dquotes\"".parse::().is_err()); - } - - #[test] - fn test_etag_fmt() { - assert_eq!( - format!("{}", EntityTag::strong("foobar".to_owned())), - "\"foobar\"" - ); - assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); - assert_eq!( - format!("{}", EntityTag::weak("weak-etag".to_owned())), - "W/\"weak-etag\"" - ); - assert_eq!( - format!("{}", EntityTag::weak("\u{0065}".to_owned())), - "W/\"\x65\"" - ); - assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); - } - - #[test] - fn test_cmp() { - // | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison | - // |---------|---------|-------------------|-----------------| - // | `W/"1"` | `W/"1"` | no match | match | - // | `W/"1"` | `W/"2"` | no match | no match | - // | `W/"1"` | `"1"` | no match | match | - // | `"1"` | `"1"` | match | match | - let mut etag1 = EntityTag::weak("1".to_owned()); - let mut etag2 = EntityTag::weak("1".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - - etag1 = EntityTag::weak("1".to_owned()); - etag2 = EntityTag::weak("2".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(!etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(etag1.weak_ne(&etag2)); - - etag1 = EntityTag::weak("1".to_owned()); - etag2 = EntityTag::strong("1".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - - etag1 = EntityTag::strong("1".to_owned()); - etag2 = EntityTag::strong("1".to_owned()); - assert!(etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(!etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - } -} diff --git a/src/header/shared/httpdate.rs b/src/header/shared/httpdate.rs deleted file mode 100644 index 7fd26b121..000000000 --- a/src/header/shared/httpdate.rs +++ /dev/null @@ -1,119 +0,0 @@ -use std::fmt::{self, Display}; -use std::io::Write; -use std::str::FromStr; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -use bytes::{BufMut, BytesMut}; -use http::header::{HeaderValue, InvalidHeaderValueBytes}; -use time; - -use error::ParseError; -use header::IntoHeaderValue; - -/// A timestamp with HTTP formatting and parsing -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct HttpDate(time::Tm); - -impl FromStr for HttpDate { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - match time::strptime(s, "%a, %d %b %Y %T %Z") - .or_else(|_| time::strptime(s, "%A, %d-%b-%y %T %Z")) - .or_else(|_| time::strptime(s, "%c")) - { - Ok(t) => Ok(HttpDate(t)), - Err(_) => Err(ParseError::Header), - } - } -} - -impl Display for HttpDate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0.to_utc().rfc822(), f) - } -} - -impl From for HttpDate { - fn from(tm: time::Tm) -> HttpDate { - HttpDate(tm) - } -} - -impl From for HttpDate { - fn from(sys: SystemTime) -> HttpDate { - let tmspec = match sys.duration_since(UNIX_EPOCH) { - Ok(dur) => { - time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32) - } - Err(err) => { - let neg = err.duration(); - time::Timespec::new( - -(neg.as_secs() as i64), - -(neg.subsec_nanos() as i32), - ) - } - }; - HttpDate(time::at_utc(tmspec)) - } -} - -impl IntoHeaderValue for HttpDate { - type Error = InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut wrt = BytesMut::with_capacity(29).writer(); - write!(wrt, "{}", self.0.rfc822()).unwrap(); - HeaderValue::from_shared(wrt.get_mut().take().freeze()) - } -} - -impl From for SystemTime { - fn from(date: HttpDate) -> SystemTime { - let spec = date.0.to_timespec(); - if spec.sec >= 0 { - UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32) - } else { - UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32) - } - } -} - -#[cfg(test)] -mod tests { - use super::HttpDate; - use time::Tm; - - const NOV_07: HttpDate = HttpDate(Tm { - tm_nsec: 0, - tm_sec: 37, - tm_min: 48, - tm_hour: 8, - tm_mday: 7, - tm_mon: 10, - tm_year: 94, - tm_wday: 0, - tm_isdst: 0, - tm_yday: 0, - tm_utcoff: 0, - }); - - #[test] - fn test_date() { - assert_eq!( - "Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), - NOV_07 - ); - assert_eq!( - "Sunday, 07-Nov-94 08:48:37 GMT" - .parse::() - .unwrap(), - NOV_07 - ); - assert_eq!( - "Sun Nov 7 08:48:37 1994".parse::().unwrap(), - NOV_07 - ); - assert!("this-is-no-date".parse::().is_err()); - } -} diff --git a/src/header/shared/mod.rs b/src/header/shared/mod.rs deleted file mode 100644 index f2bc91634..000000000 --- a/src/header/shared/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Copied for `hyper::header::shared`; - -pub use self::charset::Charset; -pub use self::encoding::Encoding; -pub use self::entity::EntityTag; -pub use self::httpdate::HttpDate; -pub use self::quality_item::{q, qitem, Quality, QualityItem}; -pub use language_tags::LanguageTag; - -mod charset; -mod encoding; -mod entity; -mod httpdate; -mod quality_item; diff --git a/src/header/shared/quality_item.rs b/src/header/shared/quality_item.rs deleted file mode 100644 index 80bd7e1c2..000000000 --- a/src/header/shared/quality_item.rs +++ /dev/null @@ -1,294 +0,0 @@ -use std::cmp; -use std::default::Default; -use std::fmt; -use std::str; - -use self::internal::IntoQuality; - -/// Represents a quality used in quality values. -/// -/// Can be created with the `q` function. -/// -/// # Implementation notes -/// -/// The quality value is defined as a number between 0 and 1 with three decimal -/// places. This means there are 1001 possible values. Since floating point -/// numbers are not exact and the smallest floating point data type (`f32`) -/// consumes four bytes, hyper uses an `u16` value to store the -/// quality internally. For performance reasons you may set quality directly to -/// a value between 0 and 1000 e.g. `Quality(532)` matches the quality -/// `q=0.532`. -/// -/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1) -/// gives more information on quality values in HTTP header fields. -#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct Quality(u16); - -impl Default for Quality { - fn default() -> Quality { - Quality(1000) - } -} - -/// Represents an item with a quality value as defined in -/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1). -#[derive(Clone, PartialEq, Debug)] -pub struct QualityItem { - /// The actual contents of the field. - pub item: T, - /// The quality (client or server preference) for the value. - pub quality: Quality, -} - -impl QualityItem { - /// Creates a new `QualityItem` from an item and a quality. - /// The item can be of any type. - /// The quality should be a value in the range [0, 1]. - pub fn new(item: T, quality: Quality) -> QualityItem { - QualityItem { item, quality } - } -} - -impl cmp::PartialOrd for QualityItem { - fn partial_cmp(&self, other: &QualityItem) -> Option { - self.quality.partial_cmp(&other.quality) - } -} - -impl fmt::Display for QualityItem { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.item, f)?; - match self.quality.0 { - 1000 => Ok(()), - 0 => f.write_str("; q=0"), - x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')), - } - } -} - -impl str::FromStr for QualityItem { - type Err = ::error::ParseError; - - fn from_str(s: &str) -> Result, ::error::ParseError> { - if !s.is_ascii() { - return Err(::error::ParseError::Header); - } - // Set defaults used if parsing fails. - let mut raw_item = s; - let mut quality = 1f32; - - let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect(); - if parts.len() == 2 { - if parts[0].len() < 2 { - return Err(::error::ParseError::Header); - } - let start = &parts[0][0..2]; - if start == "q=" || start == "Q=" { - let q_part = &parts[0][2..parts[0].len()]; - if q_part.len() > 5 { - return Err(::error::ParseError::Header); - } - match q_part.parse::() { - Ok(q_value) => { - if 0f32 <= q_value && q_value <= 1f32 { - quality = q_value; - raw_item = parts[1]; - } else { - return Err(::error::ParseError::Header); - } - } - Err(_) => return Err(::error::ParseError::Header), - } - } - } - match raw_item.parse::() { - // we already checked above that the quality is within range - Ok(item) => Ok(QualityItem::new(item, from_f32(quality))), - Err(_) => Err(::error::ParseError::Header), - } - } -} - -#[inline] -fn from_f32(f: f32) -> Quality { - // this function is only used internally. A check that `f` is within range - // should be done before calling this method. Just in case, this - // debug_assert should catch if we were forgetful - debug_assert!( - f >= 0f32 && f <= 1f32, - "q value must be between 0.0 and 1.0" - ); - Quality((f * 1000f32) as u16) -} - -/// Convenience function to wrap a value in a `QualityItem` -/// Sets `q` to the default 1.0 -pub fn qitem(item: T) -> QualityItem { - QualityItem::new(item, Default::default()) -} - -/// Convenience function to create a `Quality` from a float or integer. -/// -/// Implemented for `u16` and `f32`. Panics if value is out of range. -pub fn q(val: T) -> Quality { - val.into_quality() -} - -mod internal { - use super::Quality; - - // TryFrom is probably better, but it's not stable. For now, we want to - // keep the functionality of the `q` function, while allowing it to be - // generic over `f32` and `u16`. - // - // `q` would panic before, so keep that behavior. `TryFrom` can be - // introduced later for a non-panicking conversion. - - pub trait IntoQuality: Sealed + Sized { - fn into_quality(self) -> Quality; - } - - impl IntoQuality for f32 { - fn into_quality(self) -> Quality { - assert!( - self >= 0f32 && self <= 1f32, - "float must be between 0.0 and 1.0" - ); - super::from_f32(self) - } - } - - impl IntoQuality for u16 { - fn into_quality(self) -> Quality { - assert!(self <= 1000, "u16 must be between 0 and 1000"); - Quality(self) - } - } - - pub trait Sealed {} - impl Sealed for u16 {} - impl Sealed for f32 {} -} - -#[cfg(test)] -mod tests { - use super::super::encoding::*; - use super::*; - - #[test] - fn test_quality_item_fmt_q_1() { - let x = qitem(Chunked); - assert_eq!(format!("{}", x), "chunked"); - } - #[test] - fn test_quality_item_fmt_q_0001() { - let x = QualityItem::new(Chunked, Quality(1)); - assert_eq!(format!("{}", x), "chunked; q=0.001"); - } - #[test] - fn test_quality_item_fmt_q_05() { - // Custom value - let x = QualityItem { - item: EncodingExt("identity".to_owned()), - quality: Quality(500), - }; - assert_eq!(format!("{}", x), "identity; q=0.5"); - } - - #[test] - fn test_quality_item_fmt_q_0() { - // Custom value - let x = QualityItem { - item: EncodingExt("identity".to_owned()), - quality: Quality(0), - }; - assert_eq!(x.to_string(), "identity; q=0"); - } - - #[test] - fn test_quality_item_from_str1() { - let x: Result, _> = "chunked".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Chunked, - quality: Quality(1000), - } - ); - } - #[test] - fn test_quality_item_from_str2() { - let x: Result, _> = "chunked; q=1".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Chunked, - quality: Quality(1000), - } - ); - } - #[test] - fn test_quality_item_from_str3() { - let x: Result, _> = "gzip; q=0.5".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Gzip, - quality: Quality(500), - } - ); - } - #[test] - fn test_quality_item_from_str4() { - let x: Result, _> = "gzip; q=0.273".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Gzip, - quality: Quality(273), - } - ); - } - #[test] - fn test_quality_item_from_str5() { - let x: Result, _> = "gzip; q=0.2739999".parse(); - assert!(x.is_err()); - } - #[test] - fn test_quality_item_from_str6() { - let x: Result, _> = "gzip; q=2".parse(); - assert!(x.is_err()); - } - #[test] - fn test_quality_item_ordering() { - let x: QualityItem = "gzip; q=0.5".parse().ok().unwrap(); - let y: QualityItem = "gzip; q=0.273".parse().ok().unwrap(); - let comparision_result: bool = x.gt(&y); - assert!(comparision_result) - } - - #[test] - fn test_quality() { - assert_eq!(q(0.5), Quality(500)); - } - - #[test] - #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] - fn test_quality_invalid() { - q(-1.0); - } - - #[test] - #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] - fn test_quality_invalid2() { - q(2.0); - } - - #[test] - fn test_fuzzing_bugs() { - assert!("99999;".parse::>().is_err()); - assert!("\x0d;;;=\u{d6aa}==".parse::>().is_err()) - } -} diff --git a/src/httpresponse.rs b/src/httpresponse.rs index cdcdea94a..3c034fae3 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -15,8 +15,6 @@ use serde_json; use body::Body; use error::Error; use header::{ContentEncoding, Header, IntoHeaderValue}; -// use httpmessage::HttpMessage; -// use httprequest::HttpRequest; /// max write buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; @@ -942,8 +940,8 @@ mod tests { use body::Binary; use http; use http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; - use time::Duration; + use header::ContentEncoding; use test::TestRequest; #[test] diff --git a/src/lib.rs b/src/lib.rs index 6215bc4fb..7efb4deab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,7 +88,6 @@ extern crate log; extern crate base64; extern crate byteorder; extern crate bytes; -extern crate regex; extern crate sha1; extern crate time; #[macro_use] @@ -99,21 +98,16 @@ extern crate failure; extern crate lazy_static; #[macro_use] extern crate futures; -#[cfg(feature = "brotli")] -extern crate brotli2; extern crate cookie; extern crate encoding; -#[cfg(feature = "flate2")] -extern crate flate2; extern crate http as modhttp; extern crate httparse; -extern crate language_tags; extern crate mime; -extern crate mime_guess; extern crate net2; extern crate rand; extern crate serde; extern crate serde_urlencoded; +extern crate tokio; extern crate tokio_codec; extern crate tokio_current_thread; extern crate tokio_io; @@ -126,8 +120,6 @@ extern crate url; #[macro_use] extern crate percent_encoding; extern crate serde_json; -extern crate smallvec; -extern crate tokio; #[cfg(test)] #[macro_use] @@ -193,9 +185,6 @@ pub mod http { /// Various http headers pub mod header { pub use header::*; - pub use header::{ - Charset, ContentDisposition, DispositionParam, DispositionType, LanguageTag, - }; } pub use header::ContentEncoding; pub use httpresponse::ConnectionType; diff --git a/src/server/input.rs b/src/server/input.rs deleted file mode 100644 index d23d1e991..000000000 --- a/src/server/input.rs +++ /dev/null @@ -1,288 +0,0 @@ -use std::io::{self, Write}; - -#[cfg(feature = "brotli")] -use brotli2::write::BrotliDecoder; -use bytes::{Bytes, BytesMut}; -use error::PayloadError; -#[cfg(feature = "flate2")] -use flate2::write::{GzDecoder, ZlibDecoder}; -use header::ContentEncoding; -use http::header::{HeaderMap, CONTENT_ENCODING}; -use payload::{PayloadSender, PayloadStatus, PayloadWriter}; - -pub(crate) enum PayloadType { - Sender(PayloadSender), - Encoding(Box), -} - -impl PayloadType { - #[cfg(any(feature = "brotli", feature = "flate2"))] - pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { - // check content-encoding - let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) { - if let Ok(enc) = enc.to_str() { - ContentEncoding::from(enc) - } else { - ContentEncoding::Auto - } - } else { - ContentEncoding::Auto - }; - - match enc { - ContentEncoding::Auto | ContentEncoding::Identity => { - PayloadType::Sender(sender) - } - _ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))), - } - } - - #[cfg(not(any(feature = "brotli", feature = "flate2")))] - pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { - PayloadType::Sender(sender) - } -} - -impl PayloadWriter for PayloadType { - #[inline] - fn set_error(&mut self, err: PayloadError) { - match *self { - PayloadType::Sender(ref mut sender) => sender.set_error(err), - PayloadType::Encoding(ref mut enc) => enc.set_error(err), - } - } - - #[inline] - fn feed_eof(&mut self) { - match *self { - PayloadType::Sender(ref mut sender) => sender.feed_eof(), - PayloadType::Encoding(ref mut enc) => enc.feed_eof(), - } - } - - #[inline] - fn feed_data(&mut self, data: Bytes) { - match *self { - PayloadType::Sender(ref mut sender) => sender.feed_data(data), - PayloadType::Encoding(ref mut enc) => enc.feed_data(data), - } - } - - #[inline] - fn need_read(&self) -> PayloadStatus { - match *self { - PayloadType::Sender(ref sender) => sender.need_read(), - PayloadType::Encoding(ref enc) => enc.need_read(), - } - } -} - -/// Payload wrapper with content decompression support -pub(crate) struct EncodedPayload { - inner: PayloadSender, - error: bool, - payload: PayloadStream, -} - -impl EncodedPayload { - pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload { - EncodedPayload { - inner, - error: false, - payload: PayloadStream::new(enc), - } - } -} - -impl PayloadWriter for EncodedPayload { - fn set_error(&mut self, err: PayloadError) { - self.inner.set_error(err) - } - - fn feed_eof(&mut self) { - if !self.error { - match self.payload.feed_eof() { - Err(err) => { - self.error = true; - self.set_error(PayloadError::Io(err)); - } - Ok(value) => { - if let Some(b) = value { - self.inner.feed_data(b); - } - self.inner.feed_eof(); - } - } - } - } - - fn feed_data(&mut self, data: Bytes) { - if self.error { - return; - } - - match self.payload.feed_data(data) { - Ok(Some(b)) => self.inner.feed_data(b), - Ok(None) => (), - Err(e) => { - self.error = true; - self.set_error(e.into()); - } - } - } - - #[inline] - fn need_read(&self) -> PayloadStatus { - self.inner.need_read() - } -} - -pub(crate) enum Decoder { - #[cfg(feature = "flate2")] - Deflate(Box>), - #[cfg(feature = "flate2")] - Gzip(Box>), - #[cfg(feature = "brotli")] - Br(Box>), - Identity, -} - -pub(crate) struct Writer { - buf: BytesMut, -} - -impl Writer { - fn new() -> Writer { - Writer { - buf: BytesMut::with_capacity(8192), - } - } - fn take(&mut self) -> Bytes { - self.buf.take().freeze() - } -} - -impl io::Write for Writer { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buf.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -/// Payload stream with decompression support -pub(crate) struct PayloadStream { - decoder: Decoder, -} - -impl PayloadStream { - pub fn new(enc: ContentEncoding) -> PayloadStream { - let decoder = match enc { - #[cfg(feature = "brotli")] - ContentEncoding::Br => { - Decoder::Br(Box::new(BrotliDecoder::new(Writer::new()))) - } - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => { - Decoder::Deflate(Box::new(ZlibDecoder::new(Writer::new()))) - } - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => { - Decoder::Gzip(Box::new(GzDecoder::new(Writer::new()))) - } - _ => Decoder::Identity, - }; - PayloadStream { decoder } - } -} - -impl PayloadStream { - pub fn feed_eof(&mut self) -> io::Result> { - match self.decoder { - #[cfg(feature = "brotli")] - Decoder::Br(ref mut decoder) => match decoder.finish() { - Ok(mut writer) => { - let b = writer.take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Gzip(ref mut decoder) => match decoder.try_finish() { - Ok(_) => { - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Deflate(ref mut decoder) => match decoder.try_finish() { - Ok(_) => { - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - Decoder::Identity => Ok(None), - } - } - - pub fn feed_data(&mut self, data: Bytes) -> io::Result> { - match self.decoder { - #[cfg(feature = "brotli")] - Decoder::Br(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Gzip(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - Decoder::Identity => Ok(Some(data)), - } - } -} diff --git a/src/server/mod.rs b/src/server/mod.rs index 0abd7c216..972fbf3fc 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -114,9 +114,6 @@ use tokio_tcp::TcpStream; pub use actix_net::server::{PauseServer, ResumeServer, StopServer}; -pub(crate) mod input; -pub(crate) mod output; - #[doc(hidden)] pub use super::helpers::write_content_length; diff --git a/src/server/service.rs b/src/server/service.rs deleted file mode 100644 index a55c33f72..000000000 --- a/src/server/service.rs +++ /dev/null @@ -1,273 +0,0 @@ -use std::marker::PhantomData; -use std::time::Duration; - -use actix_net::service::{NewService, Service}; -use futures::future::{ok, FutureResult}; -use futures::{Async, Poll}; - -use super::channel::{H1Channel, HttpChannel}; -use super::error::HttpDispatchError; -use super::handler::HttpHandler; -use super::settings::ServiceConfig; -use super::IoStream; -use error::Error; - -/// `NewService` implementation for HTTP1/HTTP2 transports -pub struct HttpService -where - H: HttpHandler, - Io: IoStream, -{ - settings: ServiceConfig, - _t: PhantomData, -} - -impl HttpService -where - H: HttpHandler, - Io: IoStream, -{ - /// Create new `HttpService` instance. - pub fn new(settings: ServiceConfig) -> Self { - HttpService { - settings, - _t: PhantomData, - } - } -} - -impl NewService for HttpService -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = HttpDispatchError; - type InitError = (); - type Service = HttpServiceHandler; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(HttpServiceHandler::new(self.settings.clone())) - } -} - -pub struct HttpServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - settings: ServiceConfig, - _t: PhantomData, -} - -impl HttpServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - fn new(settings: ServiceConfig) -> HttpServiceHandler { - HttpServiceHandler { - settings, - _t: PhantomData, - } - } -} - -impl Service for HttpServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = HttpDispatchError; - type Future = HttpChannel; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - HttpChannel::new(self.settings.clone(), req) - } -} - -/// `NewService` implementation for HTTP1 transport -pub struct H1Service -where - H: HttpHandler, - Io: IoStream, -{ - settings: ServiceConfig, - _t: PhantomData, -} - -impl H1Service -where - H: HttpHandler, - Io: IoStream, -{ - /// Create new `HttpService` instance. - pub fn new(settings: ServiceConfig) -> Self { - H1Service { - settings, - _t: PhantomData, - } - } -} - -impl NewService for H1Service -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = HttpDispatchError; - type InitError = (); - type Service = H1ServiceHandler; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(H1ServiceHandler::new(self.settings.clone())) - } -} - -/// `Service` implementation for HTTP1 transport -pub struct H1ServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - settings: ServiceConfig, - _t: PhantomData, -} - -impl H1ServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - fn new(settings: ServiceConfig) -> H1ServiceHandler { - H1ServiceHandler { - settings, - _t: PhantomData, - } - } -} - -impl Service for H1ServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = HttpDispatchError; - type Future = H1Channel; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - H1Channel::new(self.settings.clone(), req) - } -} - -/// `NewService` implementation for stream configuration service -/// -/// Stream configuration service allows to change some socket level -/// parameters. for example `tcp nodelay` or `tcp keep-alive`. -pub struct StreamConfiguration { - no_delay: Option, - tcp_ka: Option>, - _t: PhantomData<(T, E)>, -} - -impl Default for StreamConfiguration { - fn default() -> Self { - Self::new() - } -} - -impl StreamConfiguration { - /// Create new `StreamConfigurationService` instance. - pub fn new() -> Self { - Self { - no_delay: None, - tcp_ka: None, - _t: PhantomData, - } - } - - /// Sets the value of the `TCP_NODELAY` option on this socket. - pub fn nodelay(mut self, nodelay: bool) -> Self { - self.no_delay = Some(nodelay); - self - } - - /// Sets whether keepalive messages are enabled to be sent on this socket. - pub fn tcp_keepalive(mut self, keepalive: Option) -> Self { - self.tcp_ka = Some(keepalive); - self - } -} - -impl NewService for StreamConfiguration { - type Request = T; - type Response = T; - type Error = E; - type InitError = (); - type Service = StreamConfigurationService; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(StreamConfigurationService { - no_delay: self.no_delay, - tcp_ka: self.tcp_ka, - _t: PhantomData, - }) - } -} - -/// Stream configuration service -/// -/// Stream configuration service allows to change some socket level -/// parameters. for example `tcp nodelay` or `tcp keep-alive`. -pub struct StreamConfigurationService { - no_delay: Option, - tcp_ka: Option>, - _t: PhantomData<(T, E)>, -} - -impl Service for StreamConfigurationService -where - T: IoStream, -{ - type Request = T; - type Response = T; - type Error = E; - type Future = FutureResult; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, mut req: Self::Request) -> Self::Future { - if let Some(no_delay) = self.no_delay { - if req.set_nodelay(no_delay).is_err() { - error!("Can not set socket no-delay option"); - } - } - if let Some(keepalive) = self.tcp_ka { - if req.set_keepalive(keepalive).is_err() { - error!("Can not set socket keep-alive option"); - } - } - - ok(req) - } -} diff --git a/src/ws/context.rs b/src/ws/context.rs deleted file mode 100644 index 4db83df5c..000000000 --- a/src/ws/context.rs +++ /dev/null @@ -1,334 +0,0 @@ -extern crate actix; - -use bytes::Bytes; -use futures::sync::oneshot::{self, Sender}; -use futures::{Async, Future, Poll, Stream}; -use smallvec::SmallVec; - -use self::actix::dev::{ - AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, - ToEnvelope, -}; -use self::actix::fut::ActorFuture; -use self::actix::{ - Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, - Message as ActixMessage, SpawnHandle, -}; - -use body::{Binary, Body}; -use context::{ActorHttpContext, Drain, Frame as ContextFrame}; -use error::{Error, ErrorInternalServerError, PayloadError}; -use httprequest::HttpRequest; - -use ws::frame::{Frame, FramedMessage}; -use ws::proto::{CloseReason, OpCode}; -use ws::{Message, ProtocolError, WsStream, WsWriter}; - -/// Execution context for `WebSockets` actors -pub struct WebsocketContext -where - A: Actor>, -{ - inner: ContextParts, - stream: Option>, - request: HttpRequest, - disconnected: bool, -} - -impl ActorContext for WebsocketContext -where - A: Actor, -{ - fn stop(&mut self) { - self.inner.stop(); - } - fn terminate(&mut self) { - self.inner.terminate() - } - fn state(&self) -> ActorState { - self.inner.state() - } -} - -impl AsyncContext for WebsocketContext -where - A: Actor, -{ - fn spawn(&mut self, fut: F) -> SpawnHandle - where - F: ActorFuture + 'static, - { - self.inner.spawn(fut) - } - - fn wait(&mut self, fut: F) - where - F: ActorFuture + 'static, - { - self.inner.wait(fut) - } - - #[doc(hidden)] - #[inline] - fn waiting(&self) -> bool { - self.inner.waiting() - || self.inner.state() == ActorState::Stopping - || self.inner.state() == ActorState::Stopped - } - - fn cancel_future(&mut self, handle: SpawnHandle) -> bool { - self.inner.cancel_future(handle) - } - - #[inline] - fn address(&self) -> Addr { - self.inner.address() - } -} - -impl WebsocketContext -where - A: Actor, -{ - #[inline] - /// Create a new Websocket context from a request and an actor - pub fn create

    (req: HttpRequest, actor: A, stream: WsStream

    ) -> Body - where - A: StreamHandler, - P: Stream + 'static, - { - let mb = Mailbox::default(); - let mut ctx = WebsocketContext { - inner: ContextParts::new(mb.sender_producer()), - stream: None, - request: req, - disconnected: false, - }; - ctx.add_stream(stream); - - Body::Actor(Box::new(WebsocketContextFut::new(ctx, actor, mb))) - } - - /// Create a new Websocket context - pub fn with_factory(req: HttpRequest, f: F) -> Body - where - F: FnOnce(&mut Self) -> A + 'static, - { - let mb = Mailbox::default(); - let mut ctx = WebsocketContext { - inner: ContextParts::new(mb.sender_producer()), - stream: None, - request: req, - disconnected: false, - }; - - let act = f(&mut ctx); - Body::Actor(Box::new(WebsocketContextFut::new(ctx, act, mb))) - } -} - -impl WebsocketContext -where - A: Actor, -{ - /// Write payload - /// - /// This is a low-level function that accepts framed messages that should - /// be created using `Frame::message()`. If you want to send text or binary - /// data you should prefer the `text()` or `binary()` convenience functions - /// that handle the framing for you. - #[inline] - pub fn write_raw(&mut self, data: FramedMessage) { - if !self.disconnected { - if self.stream.is_none() { - self.stream = Some(SmallVec::new()); - } - let stream = self.stream.as_mut().unwrap(); - stream.push(ContextFrame::Chunk(Some(data.0))); - } else { - warn!("Trying to write to disconnected response"); - } - } - - /// Shared application state - #[inline] - pub fn state(&self) -> &S { - self.request.state() - } - - /// Incoming request - #[inline] - pub fn request(&mut self) -> &mut HttpRequest { - &mut self.request - } - - /// Returns drain future - pub fn drain(&mut self) -> Drain { - let (tx, rx) = oneshot::channel(); - self.add_frame(ContextFrame::Drain(tx)); - Drain::new(rx) - } - - /// Send text frame - #[inline] - pub fn text>(&mut self, text: T) { - self.write_raw(Frame::message(text.into(), OpCode::Text, true, false)); - } - - /// Send binary frame - #[inline] - pub fn binary>(&mut self, data: B) { - self.write_raw(Frame::message(data, OpCode::Binary, true, false)); - } - - /// Send ping frame - #[inline] - pub fn ping(&mut self, message: &str) { - self.write_raw(Frame::message( - Vec::from(message), - OpCode::Ping, - true, - false, - )); - } - - /// Send pong frame - #[inline] - pub fn pong(&mut self, message: &str) { - self.write_raw(Frame::message( - Vec::from(message), - OpCode::Pong, - true, - false, - )); - } - - /// Send close frame - #[inline] - pub fn close(&mut self, reason: Option) { - self.write_raw(Frame::close(reason, false)); - } - - /// Check if connection still open - #[inline] - pub fn connected(&self) -> bool { - !self.disconnected - } - - #[inline] - fn add_frame(&mut self, frame: ContextFrame) { - if self.stream.is_none() { - self.stream = Some(SmallVec::new()); - } - if let Some(s) = self.stream.as_mut() { - s.push(frame) - } - } - - /// Handle of the running future - /// - /// SpawnHandle is the handle returned by `AsyncContext::spawn()` method. - pub fn handle(&self) -> SpawnHandle { - self.inner.curr_handle() - } -} - -impl WsWriter for WebsocketContext -where - A: Actor, - S: 'static, -{ - /// Send text frame - #[inline] - fn send_text>(&mut self, text: T) { - self.text(text) - } - - /// Send binary frame - #[inline] - fn send_binary>(&mut self, data: B) { - self.binary(data) - } - - /// Send ping frame - #[inline] - fn send_ping(&mut self, message: &str) { - self.ping(message) - } - - /// Send pong frame - #[inline] - fn send_pong(&mut self, message: &str) { - self.pong(message) - } - - /// Send close frame - #[inline] - fn send_close(&mut self, reason: Option) { - self.close(reason) - } -} - -impl AsyncContextParts for WebsocketContext -where - A: Actor, -{ - fn parts(&mut self) -> &mut ContextParts { - &mut self.inner - } -} - -struct WebsocketContextFut -where - A: Actor>, -{ - fut: ContextFut>, -} - -impl WebsocketContextFut -where - A: Actor>, -{ - fn new(ctx: WebsocketContext, act: A, mailbox: Mailbox) -> Self { - let fut = ContextFut::new(ctx, act, mailbox); - WebsocketContextFut { fut } - } -} - -impl ActorHttpContext for WebsocketContextFut -where - A: Actor>, - S: 'static, -{ - #[inline] - fn disconnected(&mut self) { - self.fut.ctx().disconnected = true; - self.fut.ctx().stop(); - } - - fn poll(&mut self) -> Poll>, Error> { - if self.fut.alive() && self.fut.poll().is_err() { - return Err(ErrorInternalServerError("error")); - } - - // frames - if let Some(data) = self.fut.ctx().stream.take() { - Ok(Async::Ready(Some(data))) - } else if self.fut.alive() { - Ok(Async::NotReady) - } else { - Ok(Async::Ready(None)) - } - } -} - -impl ToEnvelope for WebsocketContext -where - A: Actor> + Handler, - M: ActixMessage + Send + 'static, - M::Result: Send, -{ - fn pack(msg: M, tx: Option>) -> Envelope { - Envelope::new(msg, tx) - } -} From fbf67544e5e4a4dcb49bff695b2282a76b94221d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 08:00:36 -0700 Subject: [PATCH 013/208] remove unused code --- Cargo.toml | 6 +- src/config.rs | 29 ++++++- src/header.rs | 111 +----------------------- src/lib.rs | 7 +- src/server/mod.rs | 204 --------------------------------------------- tests/test_h1v2.rs | 3 +- 6 files changed, 32 insertions(+), 328 deletions(-) delete mode 100644 src/server/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 870f772e8..455b61488 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,15 +30,12 @@ path = "src/lib.rs" [features] default = ["session", "cell"] -# tls +# native-tls tls = ["native-tls", "tokio-tls", "actix-net/tls"] # openssl ssl = ["openssl", "tokio-openssl", "actix-net/ssl"] -# deprecated, use "ssl" -alpn = ["openssl", "tokio-openssl", "actix-net/ssl"] - # rustls rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots", "actix-net/rust-tls"] @@ -61,7 +58,6 @@ http = "^0.1.8" httparse = "1.3" log = "0.4" mime = "0.3" -percent-encoding = "1.0" rand = "0.5" serde = "1.0" serde_json = "1.0" diff --git a/src/config.rs b/src/config.rs index 36b949c33..4e85044f1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -10,11 +10,36 @@ use time; use tokio_current_thread::spawn; use tokio_timer::{sleep, Delay}; -use server::KeepAlive; - // "Sun, 06 Nov 1994 08:49:37 GMT".len() const DATE_VALUE_LENGTH: usize = 29; +#[derive(Debug, PartialEq, Clone, Copy)] +/// Server keep-alive setting +pub enum KeepAlive { + /// Keep alive in seconds + Timeout(usize), + /// Relay on OS to shutdown tcp connection + Os, + /// Disabled + Disabled, +} + +impl From for KeepAlive { + fn from(keepalive: usize) -> Self { + KeepAlive::Timeout(keepalive) + } +} + +impl From> for KeepAlive { + fn from(keepalive: Option) -> Self { + if let Some(keepalive) = keepalive { + KeepAlive::Timeout(keepalive) + } else { + KeepAlive::Disabled + } + } +} + /// Http service configuration pub struct ServiceConfig(Rc); diff --git a/src/header.rs b/src/header.rs index 7b32d6c24..b1ba6524a 100644 --- a/src/header.rs +++ b/src/header.rs @@ -1,13 +1,8 @@ //! Various http headers -use std::fmt; -use std::str::FromStr; - -use bytes::{Bytes, BytesMut}; +use bytes::Bytes; use mime::Mime; -use modhttp::header::GetAll; use modhttp::Error as HttpError; -use percent_encoding; pub use modhttp::header::*; @@ -159,107 +154,3 @@ impl<'a> From<&'a str> for ContentEncoding { } } } - -#[doc(hidden)] -pub(crate) struct Writer { - buf: BytesMut, -} - -impl Writer { - fn new() -> Writer { - Writer { - buf: BytesMut::new(), - } - } - fn take(&mut self) -> Bytes { - self.buf.take().freeze() - } -} - -impl fmt::Write for Writer { - #[inline] - fn write_str(&mut self, s: &str) -> fmt::Result { - self.buf.extend_from_slice(s.as_bytes()); - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { - fmt::write(self, args) - } -} - -#[inline] -#[doc(hidden)] -/// Reads a comma-delimited raw header into a Vec. -pub fn from_comma_delimited( - all: GetAll, -) -> Result, ParseError> { - let mut result = Vec::new(); - for h in all { - let s = h.to_str().map_err(|_| ParseError::Header)?; - result.extend( - s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y), - }).filter_map(|x| x.trim().parse().ok()), - ) - } - Ok(result) -} - -#[inline] -#[doc(hidden)] -/// Reads a single string when parsing a header. -pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { - if let Some(line) = val { - let line = line.to_str().map_err(|_| ParseError::Header)?; - if !line.is_empty() { - return T::from_str(line).or(Err(ParseError::Header)); - } - } - Err(ParseError::Header) -} - -#[inline] -#[doc(hidden)] -/// Format an array into a comma-delimited string. -pub fn fmt_comma_delimited(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result -where - T: fmt::Display, -{ - let mut iter = parts.iter(); - if let Some(part) = iter.next() { - fmt::Display::fmt(part, f)?; - } - for part in iter { - f.write_str(", ")?; - fmt::Display::fmt(part, f)?; - } - Ok(()) -} - -/// Percent encode a sequence of bytes with a character set defined in -/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] -/// -/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 -pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { - let encoded = - percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); - fmt::Display::fmt(&encoded, f) -} -mod percent_encoding_http { - use percent_encoding; - - // internal module because macro is hard-coded to make a public item - // but we don't want to public export this item - define_encode_set! { - // This encode set is used for HTTP header values and is defined at - // https://tools.ietf.org/html/rfc5987#section-3.2 - pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { - ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', - '[', '\\', ']', '{', '}' - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 7efb4deab..accad2fbb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,6 +106,7 @@ extern crate mime; extern crate net2; extern crate rand; extern crate serde; +extern crate serde_json; extern crate serde_urlencoded; extern crate tokio; extern crate tokio_codec; @@ -117,9 +118,6 @@ extern crate tokio_timer; #[cfg(all(unix, feature = "uds"))] extern crate tokio_uds; extern crate url; -#[macro_use] -extern crate percent_encoding; -extern crate serde_json; #[cfg(test)] #[macro_use] @@ -140,7 +138,6 @@ mod uri; pub mod error; pub mod h1; pub(crate) mod helpers; -pub mod server; pub mod test; //pub mod ws; pub use body::{Binary, Body}; @@ -151,7 +148,7 @@ pub use httpresponse::HttpResponse; pub use json::Json; pub use request::Request; -pub use self::config::{ServiceConfig, ServiceConfigBuilder}; +pub use self::config::{KeepAlive, ServiceConfig, ServiceConfigBuilder}; pub mod dev { //! The `actix-web` prelude for library developers diff --git a/src/server/mod.rs b/src/server/mod.rs deleted file mode 100644 index 972fbf3fc..000000000 --- a/src/server/mod.rs +++ /dev/null @@ -1,204 +0,0 @@ -//! Http server module -//! -//! The module contains everything necessary to setup -//! HTTP server. -//! -//! In order to start HTTP server, first you need to create and configure it -//! using factory that can be supplied to [new](fn.new.html). -//! -//! ## Factory -//! -//! Factory is a function that returns Application, describing how -//! to serve incoming HTTP requests. -//! -//! As the server uses worker pool, the factory function is restricted to trait bounds -//! `Sync + Send + 'static` so that each worker would be able to accept Application -//! without a need for synchronization. -//! -//! If you wish to share part of state among all workers you should -//! wrap it in `Arc` and potentially synchronization primitive like -//! [RwLock](https://doc.rust-lang.org/std/sync/struct.RwLock.html) -//! If the wrapped type is not thread safe. -//! -//! Note though that locking is not advisable for asynchronous programming -//! and you should minimize all locks in your request handlers -//! -//! ## HTTPS Support -//! -//! Actix-web provides support for major crates that provides TLS. -//! Each TLS implementation is provided with [AcceptorService](trait.AcceptorService.html) -//! that describes how HTTP Server accepts connections. -//! -//! For `bind` and `listen` there are corresponding `bind_with` and `listen_with` that accepts -//! these services. -//! -//! By default, acceptor would work with both HTTP2 and HTTP1 protocols. -//! But it can be controlled using [ServerFlags](struct.ServerFlags.html) which -//! can be supplied when creating `AcceptorService`. -//! -//! **NOTE:** `native-tls` doesn't support `HTTP2` yet -//! -//! ## Signal handling and shutdown -//! -//! By default HTTP Server listens for system signals -//! and, gracefully shuts down at most after 30 seconds. -//! -//! Both signal handling and shutdown timeout can be controlled -//! using corresponding methods. -//! -//! If worker, for some reason, unable to shut down within timeout -//! it is forcibly dropped. -//! -//! ## Example -//! -//! ```rust,ignore -//!extern crate actix; -//!extern crate actix_web; -//!extern crate rustls; -//! -//!use actix_web::{http, middleware, server, App, Error, HttpRequest, HttpResponse, Responder}; -//!use std::io::BufReader; -//!use rustls::internal::pemfile::{certs, rsa_private_keys}; -//!use rustls::{NoClientAuth, ServerConfig}; -//! -//!fn index(req: &HttpRequest) -> Result { -//! Ok(HttpResponse::Ok().content_type("text/plain").body("Welcome!")) -//!} -//! -//!fn load_ssl() -> ServerConfig { -//! use std::io::BufReader; -//! -//! const CERT: &'static [u8] = include_bytes!("../cert.pem"); -//! const KEY: &'static [u8] = include_bytes!("../key.pem"); -//! -//! let mut cert = BufReader::new(CERT); -//! let mut key = BufReader::new(KEY); -//! -//! let mut config = ServerConfig::new(NoClientAuth::new()); -//! let cert_chain = certs(&mut cert).unwrap(); -//! let mut keys = rsa_private_keys(&mut key).unwrap(); -//! config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); -//! -//! config -//!} -//! -//!fn main() { -//! let sys = actix::System::new("http-server"); -//! // load ssl keys -//! let config = load_ssl(); -//! -//! // Create acceptor service for only HTTP1 protocol -//! // You can use ::new(config) to leave defaults -//! let acceptor = server::RustlsAcceptor::with_flags(config, actix_web::server::ServerFlags::HTTP1); -//! -//! // create and start server at once -//! server::new(|| { -//! App::new() -//! // register simple handler, handle all methods -//! .resource("/index.html", |r| r.f(index)) -//! })) -//! }).bind_with("127.0.0.1:8080", acceptor) -//! .unwrap() -//! .start(); -//! -//! println!("Started http server: 127.0.0.1:8080"); -//! //Run system so that server would start accepting connections -//! let _ = sys.run(); -//!} -//! ``` -use std::net::SocketAddr; -use std::{io, time}; - -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_tcp::TcpStream; - -pub use actix_net::server::{PauseServer, ResumeServer, StopServer}; - -#[doc(hidden)] -pub use super::helpers::write_content_length; - -// /// max buffer size 64k -// pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; - -#[derive(Debug, PartialEq, Clone, Copy)] -/// Server keep-alive setting -pub enum KeepAlive { - /// Keep alive in seconds - Timeout(usize), - /// Relay on OS to shutdown tcp connection - Os, - /// Disabled - Disabled, -} - -impl From for KeepAlive { - fn from(keepalive: usize) -> Self { - KeepAlive::Timeout(keepalive) - } -} - -impl From> for KeepAlive { - fn from(keepalive: Option) -> Self { - if let Some(keepalive) = keepalive { - KeepAlive::Timeout(keepalive) - } else { - KeepAlive::Disabled - } - } -} - -#[doc(hidden)] -/// Low-level io stream operations -pub trait IoStream: AsyncRead + AsyncWrite + 'static { - /// Returns the socket address of the remote peer of this TCP connection. - fn peer_addr(&self) -> Option { - None - } - - /// Sets the value of the TCP_NODELAY option on this socket. - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()>; - - fn set_linger(&mut self, dur: Option) -> io::Result<()>; - - fn set_keepalive(&mut self, dur: Option) -> io::Result<()>; -} - -#[cfg(all(unix, feature = "uds"))] -impl IoStream for ::tokio_uds::UnixStream { - #[inline] - fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn set_linger(&mut self, _dur: Option) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn set_keepalive(&mut self, _nodelay: bool) -> io::Result<()> { - Ok(()) - } -} - -impl IoStream for TcpStream { - #[inline] - fn peer_addr(&self) -> Option { - TcpStream::peer_addr(self).ok() - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - TcpStream::set_nodelay(self, nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - TcpStream::set_linger(self, dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - TcpStream::set_keepalive(self, dur) - } -} diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs index 77a6ecae5..bb9430659 100644 --- a/tests/test_h1v2.rs +++ b/tests/test_h1v2.rs @@ -11,8 +11,7 @@ use actix_net::server::Server; use actix_web::{client, test}; use futures::future; -use actix_http::server::KeepAlive; -use actix_http::{h1, Error, HttpResponse, ServiceConfig}; +use actix_http::{h1, Error, HttpResponse, KeepAlive, ServiceConfig}; #[test] fn test_h1_v2() { From 2e27d7774089e21aecaea41f59657bc5c73882e4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 10:03:10 -0700 Subject: [PATCH 014/208] fix connection keepalive support --- src/framed/framed.rs | 283 +++++++++++++++++++++++++++++ src/framed/framed_read.rs | 216 ++++++++++++++++++++++ src/framed/framed_write.rs | 243 +++++++++++++++++++++++++ src/framed/mod.rs | 32 ++++ src/h1/codec.rs | 193 ++++++++++---------- src/h1/dispatcher.rs | 15 +- src/h1/{response.rs => encoder.rs} | 8 +- src/h1/mod.rs | 2 +- src/lib.rs | 3 + 9 files changed, 890 insertions(+), 105 deletions(-) create mode 100644 src/framed/framed.rs create mode 100644 src/framed/framed_read.rs create mode 100644 src/framed/framed_write.rs create mode 100644 src/framed/mod.rs rename src/h1/{response.rs => encoder.rs} (98%) diff --git a/src/framed/framed.rs b/src/framed/framed.rs new file mode 100644 index 000000000..f6295d98f --- /dev/null +++ b/src/framed/framed.rs @@ -0,0 +1,283 @@ +#![allow(deprecated)] + +use std::fmt; +use std::io::{self, Read, Write}; + +use bytes::BytesMut; +use futures::{Poll, Sink, StartSend, Stream}; +use tokio_codec::{Decoder, Encoder}; +use tokio_io::{AsyncRead, AsyncWrite}; + +use super::framed_read::{framed_read2, framed_read2_with_buffer, FramedRead2}; +use super::framed_write::{framed_write2, framed_write2_with_buffer, FramedWrite2}; + +/// A unified `Stream` and `Sink` interface to an underlying I/O object, using +/// the `Encoder` and `Decoder` traits to encode and decode frames. +/// +/// You can create a `Framed` instance by using the `AsyncRead::framed` adapter. +pub struct Framed { + inner: FramedRead2>>, +} + +pub struct Fuse(pub T, pub U); + +impl Framed +where + T: AsyncRead + AsyncWrite, + U: Decoder + Encoder, +{ + /// Provides a `Stream` and `Sink` interface for reading and writing to this + /// `Io` object, using `Decode` and `Encode` to read and write the raw data. + /// + /// Raw I/O objects work with byte sequences, but higher-level code usually + /// wants to batch these into meaningful chunks, called "frames". This + /// method layers framing on top of an I/O object, by using the `Codec` + /// traits to handle encoding and decoding of messages frames. Note that + /// the incoming and outgoing frame types may be distinct. + /// + /// This function returns a *single* object that is both `Stream` and + /// `Sink`; grouping this into a single object is often useful for layering + /// things like gzip or TLS, which require both read and write access to the + /// underlying object. + /// + /// If you want to work more directly with the streams and sink, consider + /// calling `split` on the `Framed` returned by this method, which will + /// break them into separate objects, allowing them to interact more easily. + pub fn new(inner: T, codec: U) -> Framed { + Framed { + inner: framed_read2(framed_write2(Fuse(inner, codec))), + } + } +} + +impl Framed { + /// Provides a `Stream` and `Sink` interface for reading and writing to this + /// `Io` object, using `Decode` and `Encode` to read and write the raw data. + /// + /// Raw I/O objects work with byte sequences, but higher-level code usually + /// wants to batch these into meaningful chunks, called "frames". This + /// method layers framing on top of an I/O object, by using the `Codec` + /// traits to handle encoding and decoding of messages frames. Note that + /// the incoming and outgoing frame types may be distinct. + /// + /// This function returns a *single* object that is both `Stream` and + /// `Sink`; grouping this into a single object is often useful for layering + /// things like gzip or TLS, which require both read and write access to the + /// underlying object. + /// + /// This objects takes a stream and a readbuffer and a writebuffer. These field + /// can be obtained from an existing `Framed` with the `into_parts` method. + /// + /// If you want to work more directly with the streams and sink, consider + /// calling `split` on the `Framed` returned by this method, which will + /// break them into separate objects, allowing them to interact more easily. + pub fn from_parts(parts: FramedParts) -> Framed { + Framed { + inner: framed_read2_with_buffer( + framed_write2_with_buffer(Fuse(parts.io, parts.codec), parts.write_buf), + parts.read_buf, + ), + } + } + + /// Returns a reference to the underlying codec. + pub fn get_codec(&self) -> &U { + &self.inner.get_ref().get_ref().1 + } + + /// Returns a mutable reference to the underlying codec. + pub fn get_codec_mut(&mut self) -> &mut U { + &mut self.inner.get_mut().get_mut().1 + } + + /// Returns a reference to the underlying I/O stream wrapped by + /// `Frame`. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn get_ref(&self) -> &T { + &self.inner.get_ref().get_ref().0 + } + + /// Returns a mutable reference to the underlying I/O stream wrapped by + /// `Frame`. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner.get_mut().get_mut().0 + } + + /// Consumes the `Frame`, returning its underlying I/O stream. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn into_inner(self) -> T { + self.inner.into_inner().into_inner().0 + } + + /// Consumes the `Frame`, returning its underlying I/O stream, the buffer + /// with unprocessed data, and the codec. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn into_parts(self) -> FramedParts { + let (inner, read_buf) = self.inner.into_parts(); + let (inner, write_buf) = inner.into_parts(); + + FramedParts { + io: inner.0, + codec: inner.1, + read_buf: read_buf, + write_buf: write_buf, + _priv: (), + } + } +} + +impl Stream for Framed +where + T: AsyncRead, + U: Decoder, +{ + type Item = U::Item; + type Error = U::Error; + + fn poll(&mut self) -> Poll, Self::Error> { + self.inner.poll() + } +} + +impl Sink for Framed +where + T: AsyncWrite, + U: Encoder, + U::Error: From, +{ + type SinkItem = U::Item; + type SinkError = U::Error; + + fn start_send( + &mut self, item: Self::SinkItem, + ) -> StartSend { + self.inner.get_mut().start_send(item) + } + + fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { + self.inner.get_mut().poll_complete() + } + + fn close(&mut self) -> Poll<(), Self::SinkError> { + self.inner.get_mut().close() + } +} + +impl fmt::Debug for Framed +where + T: fmt::Debug, + U: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Framed") + .field("io", &self.inner.get_ref().get_ref().0) + .field("codec", &self.inner.get_ref().get_ref().1) + .finish() + } +} + +// ===== impl Fuse ===== + +impl Read for Fuse { + fn read(&mut self, dst: &mut [u8]) -> io::Result { + self.0.read(dst) + } +} + +impl AsyncRead for Fuse { + unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { + self.0.prepare_uninitialized_buffer(buf) + } +} + +impl Write for Fuse { + fn write(&mut self, src: &[u8]) -> io::Result { + self.0.write(src) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } +} + +impl AsyncWrite for Fuse { + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.0.shutdown() + } +} + +impl Decoder for Fuse { + type Item = U::Item; + type Error = U::Error; + + fn decode( + &mut self, buffer: &mut BytesMut, + ) -> Result, Self::Error> { + self.1.decode(buffer) + } + + fn decode_eof( + &mut self, buffer: &mut BytesMut, + ) -> Result, Self::Error> { + self.1.decode_eof(buffer) + } +} + +impl Encoder for Fuse { + type Item = U::Item; + type Error = U::Error; + + fn encode( + &mut self, item: Self::Item, dst: &mut BytesMut, + ) -> Result<(), Self::Error> { + self.1.encode(item, dst) + } +} + +/// `FramedParts` contains an export of the data of a Framed transport. +/// It can be used to construct a new `Framed` with a different codec. +/// It contains all current buffers and the inner transport. +#[derive(Debug)] +pub struct FramedParts { + /// The inner transport used to read bytes to and write bytes to + pub io: T, + + /// The codec + pub codec: U, + + /// The buffer with read but unprocessed data. + pub read_buf: BytesMut, + + /// A buffer with unprocessed data which are not written yet. + pub write_buf: BytesMut, + + /// This private field allows us to add additional fields in the future in a + /// backwards compatible way. + _priv: (), +} + +impl FramedParts { + /// Create a new, default, `FramedParts` + pub fn new(io: T, codec: U) -> FramedParts { + FramedParts { + io, + codec, + read_buf: BytesMut::new(), + write_buf: BytesMut::new(), + _priv: (), + } + } +} diff --git a/src/framed/framed_read.rs b/src/framed/framed_read.rs new file mode 100644 index 000000000..065e29205 --- /dev/null +++ b/src/framed/framed_read.rs @@ -0,0 +1,216 @@ +use std::fmt; + +use bytes::BytesMut; +use futures::{Async, Poll, Sink, StartSend, Stream}; +use tokio_codec::Decoder; +use tokio_io::AsyncRead; + +use super::framed::Fuse; + +/// A `Stream` of messages decoded from an `AsyncRead`. +pub struct FramedRead { + inner: FramedRead2>, +} + +pub struct FramedRead2 { + inner: T, + eof: bool, + is_readable: bool, + buffer: BytesMut, +} + +const INITIAL_CAPACITY: usize = 8 * 1024; + +// ===== impl FramedRead ===== + +impl FramedRead +where + T: AsyncRead, + D: Decoder, +{ + /// Creates a new `FramedRead` with the given `decoder`. + pub fn new(inner: T, decoder: D) -> FramedRead { + FramedRead { + inner: framed_read2(Fuse(inner, decoder)), + } + } +} + +impl FramedRead { + /// Returns a reference to the underlying I/O stream wrapped by + /// `FramedRead`. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn get_ref(&self) -> &T { + &self.inner.inner.0 + } + + /// Returns a mutable reference to the underlying I/O stream wrapped by + /// `FramedRead`. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner.inner.0 + } + + /// Consumes the `FramedRead`, returning its underlying I/O stream. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn into_inner(self) -> T { + self.inner.inner.0 + } + + /// Returns a reference to the underlying decoder. + pub fn decoder(&self) -> &D { + &self.inner.inner.1 + } + + /// Returns a mutable reference to the underlying decoder. + pub fn decoder_mut(&mut self) -> &mut D { + &mut self.inner.inner.1 + } +} + +impl Stream for FramedRead +where + T: AsyncRead, + D: Decoder, +{ + type Item = D::Item; + type Error = D::Error; + + fn poll(&mut self) -> Poll, Self::Error> { + self.inner.poll() + } +} + +impl Sink for FramedRead +where + T: Sink, +{ + type SinkItem = T::SinkItem; + type SinkError = T::SinkError; + + fn start_send( + &mut self, item: Self::SinkItem, + ) -> StartSend { + self.inner.inner.0.start_send(item) + } + + fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { + self.inner.inner.0.poll_complete() + } + + fn close(&mut self) -> Poll<(), Self::SinkError> { + self.inner.inner.0.close() + } +} + +impl fmt::Debug for FramedRead +where + T: fmt::Debug, + D: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("FramedRead") + .field("inner", &self.inner.inner.0) + .field("decoder", &self.inner.inner.1) + .field("eof", &self.inner.eof) + .field("is_readable", &self.inner.is_readable) + .field("buffer", &self.inner.buffer) + .finish() + } +} + +// ===== impl FramedRead2 ===== + +pub fn framed_read2(inner: T) -> FramedRead2 { + FramedRead2 { + inner: inner, + eof: false, + is_readable: false, + buffer: BytesMut::with_capacity(INITIAL_CAPACITY), + } +} + +pub fn framed_read2_with_buffer(inner: T, mut buf: BytesMut) -> FramedRead2 { + if buf.capacity() < INITIAL_CAPACITY { + let bytes_to_reserve = INITIAL_CAPACITY - buf.capacity(); + buf.reserve(bytes_to_reserve); + } + FramedRead2 { + inner: inner, + eof: false, + is_readable: buf.len() > 0, + buffer: buf, + } +} + +impl FramedRead2 { + pub fn get_ref(&self) -> &T { + &self.inner + } + + pub fn into_inner(self) -> T { + self.inner + } + + pub fn into_parts(self) -> (T, BytesMut) { + (self.inner, self.buffer) + } + + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl Stream for FramedRead2 +where + T: AsyncRead + Decoder, +{ + type Item = T::Item; + type Error = T::Error; + + fn poll(&mut self) -> Poll, Self::Error> { + loop { + // Repeatedly call `decode` or `decode_eof` as long as it is + // "readable". Readable is defined as not having returned `None`. If + // the upstream has returned EOF, and the decoder is no longer + // readable, it can be assumed that the decoder will never become + // readable again, at which point the stream is terminated. + if self.is_readable { + if self.eof { + let frame = try!(self.inner.decode_eof(&mut self.buffer)); + return Ok(Async::Ready(frame)); + } + + trace!("attempting to decode a frame"); + + if let Some(frame) = try!(self.inner.decode(&mut self.buffer)) { + trace!("frame decoded from buffer"); + return Ok(Async::Ready(Some(frame))); + } + + self.is_readable = false; + } + + assert!(!self.eof); + + // Otherwise, try to read more data and try again. Make sure we've + // got room for at least one byte to read to ensure that we don't + // get a spurious 0 that looks like EOF + self.buffer.reserve(1); + if 0 == try_ready!(self.inner.read_buf(&mut self.buffer)) { + self.eof = true; + } + + self.is_readable = true; + } + } +} diff --git a/src/framed/framed_write.rs b/src/framed/framed_write.rs new file mode 100644 index 000000000..310c76307 --- /dev/null +++ b/src/framed/framed_write.rs @@ -0,0 +1,243 @@ +use std::fmt; +use std::io::{self, Read}; + +use bytes::BytesMut; +use futures::{Async, AsyncSink, Poll, Sink, StartSend, Stream}; +use tokio_codec::{Decoder, Encoder}; +use tokio_io::{AsyncRead, AsyncWrite}; + +use super::framed::Fuse; + +/// A `Sink` of frames encoded to an `AsyncWrite`. +pub struct FramedWrite { + inner: FramedWrite2>, +} + +pub struct FramedWrite2 { + inner: T, + buffer: BytesMut, +} + +const INITIAL_CAPACITY: usize = 8 * 1024; +const BACKPRESSURE_BOUNDARY: usize = INITIAL_CAPACITY; + +impl FramedWrite +where + T: AsyncWrite, + E: Encoder, +{ + /// Creates a new `FramedWrite` with the given `encoder`. + pub fn new(inner: T, encoder: E) -> FramedWrite { + FramedWrite { + inner: framed_write2(Fuse(inner, encoder)), + } + } +} + +impl FramedWrite { + /// Returns a reference to the underlying I/O stream wrapped by + /// `FramedWrite`. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn get_ref(&self) -> &T { + &self.inner.inner.0 + } + + /// Returns a mutable reference to the underlying I/O stream wrapped by + /// `FramedWrite`. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner.inner.0 + } + + /// Consumes the `FramedWrite`, returning its underlying I/O stream. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn into_inner(self) -> T { + self.inner.inner.0 + } + + /// Returns a reference to the underlying decoder. + pub fn encoder(&self) -> &E { + &self.inner.inner.1 + } + + /// Returns a mutable reference to the underlying decoder. + pub fn encoder_mut(&mut self) -> &mut E { + &mut self.inner.inner.1 + } +} + +impl Sink for FramedWrite +where + T: AsyncWrite, + E: Encoder, +{ + type SinkItem = E::Item; + type SinkError = E::Error; + + fn start_send(&mut self, item: E::Item) -> StartSend { + self.inner.start_send(item) + } + + fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { + self.inner.poll_complete() + } + + fn close(&mut self) -> Poll<(), Self::SinkError> { + Ok(try!(self.inner.close())) + } +} + +impl Stream for FramedWrite +where + T: Stream, +{ + type Item = T::Item; + type Error = T::Error; + + fn poll(&mut self) -> Poll, Self::Error> { + self.inner.inner.0.poll() + } +} + +impl fmt::Debug for FramedWrite +where + T: fmt::Debug, + U: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("FramedWrite") + .field("inner", &self.inner.get_ref().0) + .field("encoder", &self.inner.get_ref().1) + .field("buffer", &self.inner.buffer) + .finish() + } +} + +// ===== impl FramedWrite2 ===== + +pub fn framed_write2(inner: T) -> FramedWrite2 { + FramedWrite2 { + inner: inner, + buffer: BytesMut::with_capacity(INITIAL_CAPACITY), + } +} + +pub fn framed_write2_with_buffer(inner: T, mut buf: BytesMut) -> FramedWrite2 { + if buf.capacity() < INITIAL_CAPACITY { + let bytes_to_reserve = INITIAL_CAPACITY - buf.capacity(); + buf.reserve(bytes_to_reserve); + } + FramedWrite2 { + inner: inner, + buffer: buf, + } +} + +impl FramedWrite2 { + pub fn get_ref(&self) -> &T { + &self.inner + } + + pub fn into_inner(self) -> T { + self.inner + } + + pub fn into_parts(self) -> (T, BytesMut) { + (self.inner, self.buffer) + } + + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl Sink for FramedWrite2 +where + T: AsyncWrite + Encoder, +{ + type SinkItem = T::Item; + type SinkError = T::Error; + + fn start_send(&mut self, item: T::Item) -> StartSend { + // If the buffer is already over 8KiB, then attempt to flush it. If after flushing it's + // *still* over 8KiB, then apply backpressure (reject the send). + if self.buffer.len() >= BACKPRESSURE_BOUNDARY { + try!(self.poll_complete()); + + if self.buffer.len() >= BACKPRESSURE_BOUNDARY { + return Ok(AsyncSink::NotReady(item)); + } + } + + try!(self.inner.encode(item, &mut self.buffer)); + + Ok(AsyncSink::Ready) + } + + fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { + trace!("flushing framed transport"); + + while !self.buffer.is_empty() { + trace!("writing; remaining={}", self.buffer.len()); + + let n = try_ready!(self.inner.poll_write(&self.buffer)); + + if n == 0 { + return Err(io::Error::new( + io::ErrorKind::WriteZero, + "failed to \ + write frame to transport", + ).into()); + } + + // TODO: Add a way to `bytes` to do this w/o returning the drained + // data. + let _ = self.buffer.split_to(n); + } + + // Try flushing the underlying IO + try_ready!(self.inner.poll_flush()); + + trace!("framed transport flushed"); + return Ok(Async::Ready(())); + } + + fn close(&mut self) -> Poll<(), Self::SinkError> { + try_ready!(self.poll_complete()); + Ok(try!(self.inner.shutdown())) + } +} + +impl Decoder for FramedWrite2 { + type Item = T::Item; + type Error = T::Error; + + fn decode(&mut self, src: &mut BytesMut) -> Result, T::Error> { + self.inner.decode(src) + } + + fn decode_eof(&mut self, src: &mut BytesMut) -> Result, T::Error> { + self.inner.decode_eof(src) + } +} + +impl Read for FramedWrite2 { + fn read(&mut self, dst: &mut [u8]) -> io::Result { + self.inner.read(dst) + } +} + +impl AsyncRead for FramedWrite2 { + unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { + self.inner.prepare_uninitialized_buffer(buf) + } +} diff --git a/src/framed/mod.rs b/src/framed/mod.rs new file mode 100644 index 000000000..cb0308fa0 --- /dev/null +++ b/src/framed/mod.rs @@ -0,0 +1,32 @@ +//! Utilities for encoding and decoding frames. +//! +//! Contains adapters to go from streams of bytes, [`AsyncRead`] and +//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`]. +//! Framed streams are also known as [transports]. +//! +//! [`AsyncRead`]: # +//! [`AsyncWrite`]: # +//! [`Sink`]: # +//! [`Stream`]: # +//! [transports]: # + +#![deny(missing_docs, missing_debug_implementations, warnings)] +#![doc(hidden, html_root_url = "https://docs.rs/tokio-codec/0.1.0")] + +// _tokio_codec are the items that belong in the `tokio_codec` crate. However, because we need to +// maintain backward compatibility until the next major breaking change, they are defined here. +// When the next breaking change comes, they should be moved to the `tokio_codec` crate and become +// independent. +// +// The primary reason we can't move these to `tokio-codec` now is because, again for backward +// compatibility reasons, we need to keep `Decoder` and `Encoder` in tokio_io::codec. And `Decoder` +// and `Encoder` needs to reference `Framed`. So they all still need to still be in the same +// module. + +mod framed; +mod framed_read; +mod framed_write; + +pub use self::framed::{Framed, FramedParts}; +pub use self::framed_read::FramedRead; +pub use self::framed_write::FramedWrite; diff --git a/src/h1/codec.rs b/src/h1/codec.rs index dd6b26bab..40d0a240f 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -6,7 +6,7 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::H1Decoder; pub use super::decoder::InMessage; -use super::response::{ResponseInfo, ResponseLength}; +use super::encoder::{ResponseEncoder, ResponseLength}; use body::Body; use error::ParseError; use helpers; @@ -15,6 +15,17 @@ use http::{Method, Version}; use httpresponse::HttpResponse; use request::RequestPool; +bitflags! { + struct Flags: u8 { + const HEAD = 0b0000_0001; + const UPGRADE = 0b0000_0010; + const KEEPALIVE = 0b0000_0100; + const KEEPALIVE_ENABLED = 0b0001_0000; + } +} + +const AVERAGE_HEADER_SIZE: usize = 30; + /// Http response pub enum OutMessage { /// Http response message @@ -26,91 +37,40 @@ pub enum OutMessage { /// HTTP/1 Codec pub struct Codec { decoder: H1Decoder, - encoder: H1Writer, - head: bool, version: Version, -} -impl Codec { - /// Create HTTP/1 codec - pub fn new() -> Self { - Codec::with_pool(RequestPool::pool()) - } - - /// Create HTTP/1 codec with request's pool - pub(crate) fn with_pool(pool: &'static RequestPool) -> Self { - Codec { - decoder: H1Decoder::with_pool(pool), - encoder: H1Writer::new(), - head: false, - version: Version::HTTP_11, - } - } -} - -impl Decoder for Codec { - type Item = InMessage; - type Error = ParseError; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - let res = self.decoder.decode(src); - - match res { - Ok(Some(InMessage::Message(ref req))) - | Ok(Some(InMessage::MessageWithPayload(ref req))) => { - self.head = req.inner.method == Method::HEAD; - self.version = req.inner.version; - } - _ => (), - } - res - } -} - -impl Encoder for Codec { - type Item = OutMessage; - type Error = io::Error; - - fn encode( - &mut self, item: Self::Item, dst: &mut BytesMut, - ) -> Result<(), Self::Error> { - match item { - OutMessage::Response(res) => { - self.encoder.encode(res, dst, self.head, self.version)?; - } - OutMessage::Payload(bytes) => { - dst.extend_from_slice(&bytes); - } - } - Ok(()) - } -} - -bitflags! { - struct Flags: u8 { - const STARTED = 0b0000_0001; - const UPGRADE = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; - const DISCONNECTED = 0b0000_1000; - } -} - -const AVERAGE_HEADER_SIZE: usize = 30; - -struct H1Writer { + // encoder part flags: Flags, written: u64, headers_size: u32, - info: ResponseInfo, + te: ResponseEncoder, } -impl H1Writer { - fn new() -> H1Writer { - H1Writer { - flags: Flags::empty(), +impl Codec { + /// Create HTTP/1 codec. + /// + /// `keepalive_enabled` how response `connection` header get generated. + pub fn new(keepalive_enabled: bool) -> Self { + Codec::with_pool(RequestPool::pool(), keepalive_enabled) + } + + /// Create HTTP/1 codec with request's pool + pub(crate) fn with_pool( + pool: &'static RequestPool, keepalive_enabled: bool, + ) -> Self { + let flags = if keepalive_enabled { + Flags::KEEPALIVE_ENABLED + } else { + Flags::empty() + }; + Codec { + decoder: H1Decoder::with_pool(pool), + version: Version::HTTP_11, + + flags, written: 0, headers_size: 0, - info: ResponseInfo::default(), + te: ResponseEncoder::default(), } } @@ -118,46 +78,42 @@ impl H1Writer { self.written } - pub fn reset(&mut self) { - self.written = 0; - self.flags = Flags::KEEPALIVE; - } - pub fn upgrade(&self) -> bool { self.flags.contains(Flags::UPGRADE) } pub fn keepalive(&self) -> bool { - self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE) + self.flags.contains(Flags::KEEPALIVE) } - fn encode( - &mut self, mut msg: HttpResponse, buffer: &mut BytesMut, head: bool, - version: Version, + fn encode_response( + &mut self, mut msg: HttpResponse, buffer: &mut BytesMut, ) -> io::Result<()> { - // prepare task - self.info.update(&mut msg, head, version); + // prepare transfer encoding + self.te + .update(&mut msg, self.flags.contains(Flags::HEAD), self.version); - //if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { - //self.flags = Flags::STARTED | Flags::KEEPALIVE; - //} else { - self.flags = Flags::STARTED; - //} + let ka = self.flags.contains(Flags::KEEPALIVE_ENABLED) && msg + .keep_alive() + .unwrap_or_else(|| self.flags.contains(Flags::KEEPALIVE)); // Connection upgrade - let version = msg.version().unwrap_or_else(|| Version::HTTP_11); //req.inner.version); + let version = msg.version().unwrap_or_else(|| self.version); if msg.upgrade() { self.flags.insert(Flags::UPGRADE); + self.flags.remove(Flags::KEEPALIVE); msg.headers_mut() .insert(CONNECTION, HeaderValue::from_static("upgrade")); } // keep-alive - else if self.flags.contains(Flags::KEEPALIVE) { + else if ka { + self.flags.insert(Flags::KEEPALIVE); if version < Version::HTTP_11 { msg.headers_mut() .insert(CONNECTION, HeaderValue::from_static("keep-alive")); } } else if version >= Version::HTTP_11 { + self.flags.remove(Flags::KEEPALIVE); msg.headers_mut() .insert(CONNECTION, HeaderValue::from_static("close")); } @@ -183,7 +139,7 @@ impl H1Writer { buffer.extend_from_slice(reason); // content length - match self.info.length { + match self.te.length { ResponseLength::Chunked => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } @@ -209,7 +165,7 @@ impl H1Writer { for (key, value) in msg.headers() { match *key { TRANSFER_ENCODING => continue, - CONTENT_LENGTH => match self.info.length { + CONTENT_LENGTH => match self.te.length { ResponseLength::None => (), _ => continue, }, @@ -272,3 +228,46 @@ impl H1Writer { Ok(()) } } + +impl Decoder for Codec { + type Item = InMessage; + type Error = ParseError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + let res = self.decoder.decode(src); + + match res { + Ok(Some(InMessage::Message(ref req))) + | Ok(Some(InMessage::MessageWithPayload(ref req))) => { + self.flags + .set(Flags::HEAD, req.inner.method == Method::HEAD); + self.version = req.inner.version; + if self.flags.contains(Flags::KEEPALIVE_ENABLED) { + self.flags.set(Flags::KEEPALIVE, req.keep_alive()); + } + } + _ => (), + } + res + } +} + +impl Encoder for Codec { + type Item = OutMessage; + type Error = io::Error; + + fn encode( + &mut self, item: Self::Item, dst: &mut BytesMut, + ) -> Result<(), Self::Error> { + match item { + OutMessage::Response(res) => { + self.written = 0; + self.encode_response(res, dst)?; + } + OutMessage::Payload(bytes) => { + dst.extend_from_slice(&bytes); + } + } + Ok(()) + } +} diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 2b524556e..183094028 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -6,7 +6,6 @@ use std::time::Instant; use actix_net::service::Service; use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; -use tokio_codec::Framed; // use tokio_current_thread::spawn; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; @@ -17,6 +16,7 @@ use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; use body::Body; use config::ServiceConfig; use error::DispatchError; +use framed::Framed; use httpresponse::HttpResponse; use request::Request; @@ -89,12 +89,13 @@ where pub fn with_timeout( stream: T, config: ServiceConfig, timeout: Option, service: S, ) -> Self { - let flags = if config.keep_alive_enabled() { + let keepalive = config.keep_alive_enabled(); + let flags = if keepalive { Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED | Flags::FLUSHED } else { Flags::FLUSHED }; - let framed = Framed::new(stream, Codec::new()); + let framed = Framed::new(stream, Codec::new(keepalive)); let (ka_expire, ka_timer) = if let Some(delay) = timeout { (delay.deadline(), Some(delay)) @@ -235,6 +236,10 @@ where let msg = item.take().expect("SendResponse is empty"); match self.framed.start_send(msg) { Ok(AsyncSink::Ready) => { + self.flags.set( + Flags::KEEPALIVE, + self.framed.get_codec().keepalive(), + ); self.flags.remove(Flags::FLUSHED); Some(Ok(State::None)) } @@ -249,6 +254,10 @@ where let (msg, body) = item.take().expect("SendResponse is empty"); match self.framed.start_send(msg) { Ok(AsyncSink::Ready) => { + self.flags.set( + Flags::KEEPALIVE, + self.framed.get_codec().keepalive(), + ); self.flags.remove(Flags::FLUSHED); Some(Ok(State::Payload(body))) } diff --git a/src/h1/response.rs b/src/h1/encoder.rs similarity index 98% rename from src/h1/response.rs rename to src/h1/encoder.rs index fc6886840..d17587358 100644 --- a/src/h1/response.rs +++ b/src/h1/encoder.rs @@ -24,15 +24,15 @@ pub(crate) enum ResponseLength { } #[derive(Debug)] -pub(crate) struct ResponseInfo { +pub(crate) struct ResponseEncoder { head: bool, pub length: ResponseLength, pub te: TransferEncoding, } -impl Default for ResponseInfo { +impl Default for ResponseEncoder { fn default() -> Self { - ResponseInfo { + ResponseEncoder { head: false, length: ResponseLength::None, te: TransferEncoding::empty(), @@ -40,7 +40,7 @@ impl Default for ResponseInfo { } } -impl ResponseInfo { +impl ResponseEncoder { pub fn update(&mut self, resp: &mut HttpResponse, head: bool, version: Version) { self.head = head; diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 1653227be..f9abfea5c 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -2,7 +2,7 @@ mod codec; mod decoder; mod dispatcher; -mod response; +mod encoder; mod service; pub use self::codec::{Codec, InMessage, OutMessage}; diff --git a/src/lib.rs b/src/lib.rs index accad2fbb..0544569af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,6 +135,9 @@ mod payload; mod request; mod uri; +#[doc(hidden)] +pub mod framed; + pub mod error; pub mod h1; pub(crate) mod helpers; From d53f3d718793aa9f7aed0feca1f6de74b970a4f7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 10:19:15 -0700 Subject: [PATCH 015/208] re-enable websockets --- src/lib.rs | 2 +- src/ws/client.rs | 602 ----------------------------------------------- src/ws/mod.rs | 71 +----- 3 files changed, 6 insertions(+), 669 deletions(-) delete mode 100644 src/ws/client.rs diff --git a/src/lib.rs b/src/lib.rs index 0544569af..ae5a9f957 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,7 +142,7 @@ pub mod error; pub mod h1; pub(crate) mod helpers; pub mod test; -//pub mod ws; +pub mod ws; pub use body::{Binary, Body}; pub use error::{Error, ResponseError, Result}; pub use extensions::Extensions; diff --git a/src/ws/client.rs b/src/ws/client.rs deleted file mode 100644 index 18789fef8..000000000 --- a/src/ws/client.rs +++ /dev/null @@ -1,602 +0,0 @@ -//! Http client request -use std::cell::RefCell; -use std::rc::Rc; -use std::time::Duration; -use std::{fmt, io, str}; - -use base64; -use bytes::Bytes; -use cookie::Cookie; -use futures::sync::mpsc::{unbounded, UnboundedSender}; -use futures::{Async, Future, Poll, Stream}; -use http::header::{self, HeaderName, HeaderValue}; -use http::{Error as HttpError, HttpTryFrom, StatusCode}; -use rand; -use sha1::Sha1; - -use actix::{Addr, SystemService}; - -use body::{Binary, Body}; -use error::{Error, UrlParseError}; -use header::IntoHeaderValue; -use httpmessage::HttpMessage; -use payload::PayloadBuffer; - -use client::{ - ClientConnector, ClientRequest, ClientRequestBuilder, HttpResponseParserError, - Pipeline, SendRequest, SendRequestError, -}; - -use super::frame::{Frame, FramedMessage}; -use super::proto::{CloseReason, OpCode}; -use super::{Message, ProtocolError, WsWriter}; - -/// Websocket client error -#[derive(Fail, Debug)] -pub enum ClientError { - /// Invalid url - #[fail(display = "Invalid url")] - InvalidUrl, - /// Invalid response status - #[fail(display = "Invalid response status")] - InvalidResponseStatus(StatusCode), - /// Invalid upgrade header - #[fail(display = "Invalid upgrade header")] - InvalidUpgradeHeader, - /// Invalid connection header - #[fail(display = "Invalid connection header")] - InvalidConnectionHeader(HeaderValue), - /// Missing CONNECTION header - #[fail(display = "Missing CONNECTION header")] - MissingConnectionHeader, - /// Missing SEC-WEBSOCKET-ACCEPT header - #[fail(display = "Missing SEC-WEBSOCKET-ACCEPT header")] - MissingWebSocketAcceptHeader, - /// Invalid challenge response - #[fail(display = "Invalid challenge response")] - InvalidChallengeResponse(String, HeaderValue), - /// Http parsing error - #[fail(display = "Http parsing error")] - Http(Error), - /// Url parsing error - #[fail(display = "Url parsing error")] - Url(UrlParseError), - /// Response parsing error - #[fail(display = "Response parsing error")] - ResponseParseError(HttpResponseParserError), - /// Send request error - #[fail(display = "{}", _0)] - SendRequest(SendRequestError), - /// Protocol error - #[fail(display = "{}", _0)] - Protocol(#[cause] ProtocolError), - /// IO Error - #[fail(display = "{}", _0)] - Io(io::Error), - /// "Disconnected" - #[fail(display = "Disconnected")] - Disconnected, -} - -impl From for ClientError { - fn from(err: Error) -> ClientError { - ClientError::Http(err) - } -} - -impl From for ClientError { - fn from(err: UrlParseError) -> ClientError { - ClientError::Url(err) - } -} - -impl From for ClientError { - fn from(err: SendRequestError) -> ClientError { - ClientError::SendRequest(err) - } -} - -impl From for ClientError { - fn from(err: ProtocolError) -> ClientError { - ClientError::Protocol(err) - } -} - -impl From for ClientError { - fn from(err: io::Error) -> ClientError { - ClientError::Io(err) - } -} - -impl From for ClientError { - fn from(err: HttpResponseParserError) -> ClientError { - ClientError::ResponseParseError(err) - } -} - -/// `WebSocket` client -/// -/// Example of `WebSocket` client usage is available in -/// [websocket example]( -/// https://github.com/actix/examples/blob/master/websocket/src/client.rs#L24) -pub struct Client { - request: ClientRequestBuilder, - err: Option, - http_err: Option, - origin: Option, - protocols: Option, - conn: Addr, - max_size: usize, - no_masking: bool, -} - -impl Client { - /// Create new websocket connection - pub fn new>(uri: S) -> Client { - Client::with_connector(uri, ClientConnector::from_registry()) - } - - /// Create new websocket connection with custom `ClientConnector` - pub fn with_connector>(uri: S, conn: Addr) -> Client { - let mut cl = Client { - request: ClientRequest::build(), - err: None, - http_err: None, - origin: None, - protocols: None, - max_size: 65_536, - no_masking: false, - conn, - }; - cl.request.uri(uri.as_ref()); - cl - } - - /// Set supported websocket protocols - pub fn protocols(mut self, protos: U) -> Self - where - U: IntoIterator + 'static, - V: AsRef, - { - let mut protos = protos - .into_iter() - .fold(String::new(), |acc, s| acc + s.as_ref() + ","); - protos.pop(); - self.protocols = Some(protos); - self - } - - /// Set cookie for handshake request - pub fn cookie(mut self, cookie: Cookie) -> Self { - self.request.cookie(cookie); - self - } - - /// Set request Origin - pub fn origin(mut self, origin: V) -> Self - where - HeaderValue: HttpTryFrom, - { - match HeaderValue::try_from(origin) { - Ok(value) => self.origin = Some(value), - Err(e) => self.http_err = Some(e.into()), - } - self - } - - /// Set max frame size - /// - /// By default max size is set to 64kb - pub fn max_frame_size(mut self, size: usize) -> Self { - self.max_size = size; - self - } - - /// Set write buffer capacity - /// - /// Default buffer capacity is 32kb - pub fn write_buffer_capacity(mut self, cap: usize) -> Self { - self.request.write_buffer_capacity(cap); - self - } - - /// Disable payload masking. By default ws client masks frame payload. - pub fn no_masking(mut self) -> Self { - self.no_masking = true; - self - } - - /// Set request header - pub fn header(mut self, key: K, value: V) -> Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - self.request.header(key, value); - self - } - - /// Set websocket handshake timeout - /// - /// Handshake timeout is a total time for successful handshake. - /// Default value is 5 seconds. - pub fn timeout(mut self, timeout: Duration) -> Self { - self.request.timeout(timeout); - self - } - - /// Connect to websocket server and do ws handshake - pub fn connect(&mut self) -> ClientHandshake { - if let Some(e) = self.err.take() { - ClientHandshake::error(e) - } else if let Some(e) = self.http_err.take() { - ClientHandshake::error(Error::from(e).into()) - } else { - // origin - if let Some(origin) = self.origin.take() { - self.request.set_header(header::ORIGIN, origin); - } - - self.request.upgrade(); - self.request.set_header(header::UPGRADE, "websocket"); - self.request.set_header(header::CONNECTION, "upgrade"); - self.request.set_header(header::SEC_WEBSOCKET_VERSION, "13"); - self.request.with_connector(self.conn.clone()); - - if let Some(protocols) = self.protocols.take() { - self.request - .set_header(header::SEC_WEBSOCKET_PROTOCOL, protocols.as_str()); - } - let request = match self.request.finish() { - Ok(req) => req, - Err(err) => return ClientHandshake::error(err.into()), - }; - - if request.uri().host().is_none() { - return ClientHandshake::error(ClientError::InvalidUrl); - } - if let Some(scheme) = request.uri().scheme_part() { - if scheme != "http" - && scheme != "https" - && scheme != "ws" - && scheme != "wss" - { - return ClientHandshake::error(ClientError::InvalidUrl); - } - } else { - return ClientHandshake::error(ClientError::InvalidUrl); - } - - // start handshake - ClientHandshake::new(request, self.max_size, self.no_masking) - } - } -} - -struct Inner { - tx: UnboundedSender, - rx: PayloadBuffer>, - closed: bool, -} - -/// Future that implementes client websocket handshake process. -/// -/// It resolves to a pair of `ClientReader` and `ClientWriter` that -/// can be used for reading and writing websocket frames. -pub struct ClientHandshake { - request: Option, - tx: Option>, - key: String, - error: Option, - max_size: usize, - no_masking: bool, -} - -impl ClientHandshake { - fn new( - mut request: ClientRequest, max_size: usize, no_masking: bool, - ) -> ClientHandshake { - // Generate a random key for the `Sec-WebSocket-Key` header. - // a base64-encoded (see Section 4 of [RFC4648]) value that, - // when decoded, is 16 bytes in length (RFC 6455) - let sec_key: [u8; 16] = rand::random(); - let key = base64::encode(&sec_key); - - request.headers_mut().insert( - header::SEC_WEBSOCKET_KEY, - HeaderValue::try_from(key.as_str()).unwrap(), - ); - - let (tx, rx) = unbounded(); - request.set_body(Body::Streaming(Box::new(rx.map_err(|_| { - io::Error::new(io::ErrorKind::Other, "disconnected").into() - })))); - - ClientHandshake { - key, - max_size, - no_masking, - request: Some(request.send()), - tx: Some(tx), - error: None, - } - } - - fn error(err: ClientError) -> ClientHandshake { - ClientHandshake { - key: String::new(), - request: None, - tx: None, - error: Some(err), - max_size: 0, - no_masking: false, - } - } - - /// Set handshake timeout - /// - /// Handshake timeout is a total time before handshake should be completed. - /// Default value is 5 seconds. - pub fn timeout(mut self, timeout: Duration) -> Self { - if let Some(request) = self.request.take() { - self.request = Some(request.timeout(timeout)); - } - self - } - - /// Set connection timeout - /// - /// Connection timeout includes resolving hostname and actual connection to - /// the host. - /// Default value is 1 second. - pub fn conn_timeout(mut self, timeout: Duration) -> Self { - if let Some(request) = self.request.take() { - self.request = Some(request.conn_timeout(timeout)); - } - self - } -} - -impl Future for ClientHandshake { - type Item = (ClientReader, ClientWriter); - type Error = ClientError; - - fn poll(&mut self) -> Poll { - if let Some(err) = self.error.take() { - return Err(err); - } - - let resp = match self.request.as_mut().unwrap().poll()? { - Async::Ready(response) => { - self.request.take(); - response - } - Async::NotReady => return Ok(Async::NotReady), - }; - - // verify response - if resp.status() != StatusCode::SWITCHING_PROTOCOLS { - return Err(ClientError::InvalidResponseStatus(resp.status())); - } - // Check for "UPGRADE" to websocket header - let has_hdr = if let Some(hdr) = resp.headers().get(header::UPGRADE) { - if let Ok(s) = hdr.to_str() { - s.to_lowercase().contains("websocket") - } else { - false - } - } else { - false - }; - if !has_hdr { - trace!("Invalid upgrade header"); - return Err(ClientError::InvalidUpgradeHeader); - } - // Check for "CONNECTION" header - if let Some(conn) = resp.headers().get(header::CONNECTION) { - if let Ok(s) = conn.to_str() { - if !s.to_lowercase().contains("upgrade") { - trace!("Invalid connection header: {}", s); - return Err(ClientError::InvalidConnectionHeader(conn.clone())); - } - } else { - trace!("Invalid connection header: {:?}", conn); - return Err(ClientError::InvalidConnectionHeader(conn.clone())); - } - } else { - trace!("Missing connection header"); - return Err(ClientError::MissingConnectionHeader); - } - - if let Some(key) = resp.headers().get(header::SEC_WEBSOCKET_ACCEPT) { - // field is constructed by concatenating /key/ - // with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455) - const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - let mut sha1 = Sha1::new(); - sha1.update(self.key.as_ref()); - sha1.update(WS_GUID); - let encoded = base64::encode(&sha1.digest().bytes()); - if key.as_bytes() != encoded.as_bytes() { - trace!( - "Invalid challenge response: expected: {} received: {:?}", - encoded, - key - ); - return Err(ClientError::InvalidChallengeResponse(encoded, key.clone())); - } - } else { - trace!("Missing SEC-WEBSOCKET-ACCEPT header"); - return Err(ClientError::MissingWebSocketAcceptHeader); - }; - - let inner = Inner { - tx: self.tx.take().unwrap(), - rx: PayloadBuffer::new(resp.payload()), - closed: false, - }; - - let inner = Rc::new(RefCell::new(inner)); - Ok(Async::Ready(( - ClientReader { - inner: Rc::clone(&inner), - max_size: self.max_size, - no_masking: self.no_masking, - }, - ClientWriter { inner }, - ))) - } -} - -/// Websocket reader client -pub struct ClientReader { - inner: Rc>, - max_size: usize, - no_masking: bool, -} - -impl fmt::Debug for ClientReader { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ws::ClientReader()") - } -} - -impl Stream for ClientReader { - type Item = Message; - type Error = ProtocolError; - - fn poll(&mut self) -> Poll, Self::Error> { - let max_size = self.max_size; - let no_masking = self.no_masking; - let mut inner = self.inner.borrow_mut(); - if inner.closed { - return Ok(Async::Ready(None)); - } - - // read - match Frame::parse(&mut inner.rx, no_masking, max_size) { - Ok(Async::Ready(Some(frame))) => { - let (_finished, opcode, payload) = frame.unpack(); - - match opcode { - // continuation is not supported - OpCode::Continue => { - inner.closed = true; - Err(ProtocolError::NoContinuation) - } - OpCode::Bad => { - inner.closed = true; - Err(ProtocolError::BadOpCode) - } - OpCode::Close => { - inner.closed = true; - let close_reason = Frame::parse_close_payload(&payload); - Ok(Async::Ready(Some(Message::Close(close_reason)))) - } - OpCode::Ping => Ok(Async::Ready(Some(Message::Ping( - String::from_utf8_lossy(payload.as_ref()).into(), - )))), - OpCode::Pong => Ok(Async::Ready(Some(Message::Pong( - String::from_utf8_lossy(payload.as_ref()).into(), - )))), - OpCode::Binary => Ok(Async::Ready(Some(Message::Binary(payload)))), - OpCode::Text => { - let tmp = Vec::from(payload.as_ref()); - match String::from_utf8(tmp) { - Ok(s) => Ok(Async::Ready(Some(Message::Text(s)))), - Err(_) => { - inner.closed = true; - Err(ProtocolError::BadEncoding) - } - } - } - } - } - Ok(Async::Ready(None)) => Ok(Async::Ready(None)), - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(e) => { - inner.closed = true; - Err(e) - } - } - } -} - -/// Websocket writer client -pub struct ClientWriter { - inner: Rc>, -} - -impl ClientWriter { - /// Write payload - #[inline] - fn write(&mut self, mut data: FramedMessage) { - let inner = self.inner.borrow_mut(); - if !inner.closed { - let _ = inner.tx.unbounded_send(data.0.take()); - } else { - warn!("Trying to write to disconnected response"); - } - } - - /// Send text frame - #[inline] - pub fn text>(&mut self, text: T) { - self.write(Frame::message(text.into(), OpCode::Text, true, true)); - } - - /// Send binary frame - #[inline] - pub fn binary>(&mut self, data: B) { - self.write(Frame::message(data, OpCode::Binary, true, true)); - } - - /// Send ping frame - #[inline] - pub fn ping(&mut self, message: &str) { - self.write(Frame::message(Vec::from(message), OpCode::Ping, true, true)); - } - - /// Send pong frame - #[inline] - pub fn pong(&mut self, message: &str) { - self.write(Frame::message(Vec::from(message), OpCode::Pong, true, true)); - } - - /// Send close frame - #[inline] - pub fn close(&mut self, reason: Option) { - self.write(Frame::close(reason, true)); - } -} - -impl WsWriter for ClientWriter { - /// Send text frame - #[inline] - fn send_text>(&mut self, text: T) { - self.text(text) - } - - /// Send binary frame - #[inline] - fn send_binary>(&mut self, data: B) { - self.binary(data) - } - - /// Send ping frame - #[inline] - fn send_ping(&mut self, message: &str) { - self.ping(message) - } - - /// Send pong frame - #[inline] - fn send_pong(&mut self, message: &str) { - self.pong(message) - } - - /// Send close frame - #[inline] - fn send_close(&mut self, reason: Option) { - self.close(reason); - } -} diff --git a/src/ws/mod.rs b/src/ws/mod.rs index c16f8d6d2..6bb84c189 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -1,69 +1,23 @@ -//! `WebSocket` support for Actix +//! `WebSocket` support. //! //! To setup a `WebSocket`, first do web socket handshake then on success //! convert `Payload` into a `WsStream` stream and then use `WsWriter` to //! communicate with the peer. -//! -//! ## Example -//! -//! ```rust -//! # extern crate actix_web; -//! # use actix_web::actix::*; -//! # use actix_web::*; -//! use actix_web::{ws, HttpRequest, HttpResponse}; -//! -//! // do websocket handshake and start actor -//! fn ws_index(req: &HttpRequest) -> Result { -//! ws::start(req, Ws) -//! } -//! -//! struct Ws; -//! -//! impl Actor for Ws { -//! type Context = ws::WebsocketContext; -//! } -//! -//! // Handler for ws::Message messages -//! impl StreamHandler for Ws { -//! fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { -//! match msg { -//! ws::Message::Ping(msg) => ctx.pong(&msg), -//! ws::Message::Text(text) => ctx.text(text), -//! ws::Message::Binary(bin) => ctx.binary(bin), -//! _ => (), -//! } -//! } -//! } -//! # -//! # fn main() { -//! # App::new() -//! # .resource("/ws/", |r| r.f(ws_index)) // <- register websocket route -//! # .finish(); -//! # } //! ``` use bytes::Bytes; use futures::{Async, Poll, Stream}; use http::{header, Method, StatusCode}; -use super::actix::{Actor, StreamHandler}; - use body::Binary; -use error::{Error, PayloadError, ResponseError}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; +use error::{PayloadError, ResponseError}; use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder}; use payload::PayloadBuffer; +use request::Request; -mod client; -mod context; mod frame; mod mask; mod proto; -pub use self::client::{ - Client, ClientError, ClientHandshake, ClientReader, ClientWriter, -}; -pub use self::context::WebsocketContext; pub use self::frame::{Frame, FramedMessage}; pub use self::proto::{CloseCode, CloseReason, OpCode}; @@ -156,7 +110,7 @@ impl ResponseError for HandshakeError { } /// `WebSocket` Message -#[derive(Debug, PartialEq, Message)] +#[derive(Debug, PartialEq)] pub enum Message { /// Text message Text(String), @@ -170,19 +124,6 @@ pub enum Message { Close(Option), } -/// Do websocket handshake and start actor -pub fn start(req: &HttpRequest, actor: A) -> Result -where - A: Actor> + StreamHandler, - S: 'static, -{ - let mut resp = handshake(req)?; - let stream = WsStream::new(req.payload()); - - let body = WebsocketContext::create(req.clone(), actor, stream); - Ok(resp.body(body)) -} - /// Prepare `WebSocket` handshake response. /// /// This function returns handshake `HttpResponse`, ready to send to peer. @@ -191,9 +132,7 @@ where // /// `protocols` is a sequence of known protocols. On successful handshake, // /// the returned response headers contain the first protocol in this list // /// which the server also knows. -pub fn handshake( - req: &HttpRequest, -) -> Result { +pub fn handshake(req: &Request) -> Result { // WebSocket accepts only GET if *req.method() != Method::GET { return Err(HandshakeError::GetMethodRequired); From 8c2244dd889ce4a1f2e216b379661aa3a22806e5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 11:04:59 -0700 Subject: [PATCH 016/208] rename HttpResponse --- README.md | 2 +- src/error.rs | 133 +++++++------- src/h1/codec.rs | 6 +- src/h1/decoder.rs | 2 +- src/h1/dispatcher.rs | 8 +- src/h1/encoder.rs | 6 +- src/h1/service.rs | 10 +- src/httpcodes.rs | 12 +- src/httpmessage.rs | 16 +- src/json.rs | 6 +- src/lib.rs | 12 +- src/payload.rs | 2 +- src/{httpresponse.rs => response.rs} | 260 +++++++++++++-------------- src/test.rs | 22 +-- src/ws/mod.rs | 52 ++---- tests/test_h1v2.rs | 4 +- 16 files changed, 266 insertions(+), 287 deletions(-) rename src/{httpresponse.rs => response.rs} (81%) diff --git a/README.md b/README.md index 7ddd532e8..b273ea8c5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Actix http [![Build Status](https://travis-ci.org/fafhrd91/actix-http.svg?branch=master)](https://travis-ci.org/fafhrd91/actix-http) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/fafhrd91/actix-http.svg?branch=master)](https://travis-ci.org/fafhrd91/actix-http) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/fafhrd91/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/fafhrd91/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix http diff --git a/src/error.rs b/src/error.rs index fb5df2328..dc2d45b84 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,8 +21,7 @@ pub use url::ParseError as UrlParseError; // re-exports pub use cookie::ParseError as CookieParseError; -// use httprequest::HttpRequest; -use httpresponse::{HttpResponse, HttpResponseParts}; +use response::{Response, ResponseParts}; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// for actix web operations @@ -124,13 +123,13 @@ impl InternalResponseErrorAsFail for T { } } -/// Error that can be converted to `HttpResponse` +/// Error that can be converted to `Response` pub trait ResponseError: Fail + InternalResponseErrorAsFail { /// Create response for error /// /// Internal server error is generated by default. - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR) + fn error_response(&self) -> Response { + Response::new(StatusCode::INTERNAL_SERVER_ERROR) } } @@ -155,10 +154,10 @@ impl fmt::Debug for Error { } } -/// Convert `Error` to a `HttpResponse` instance -impl From for HttpResponse { +/// Convert `Error` to a `Response` instance +impl From for Response { fn from(err: Error) -> Self { - HttpResponse::from_error(err) + Response::from_error(err) } } @@ -202,15 +201,15 @@ impl ResponseError for UrlParseError {} /// Return `BAD_REQUEST` for `de::value::Error` impl ResponseError for DeError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) + fn error_response(&self) -> Response { + Response::new(StatusCode::BAD_REQUEST) } } /// Return `BAD_REQUEST` for `Utf8Error` impl ResponseError for Utf8Error { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) + fn error_response(&self) -> Response { + Response::new(StatusCode::BAD_REQUEST) } } @@ -220,26 +219,26 @@ impl ResponseError for HttpError {} /// Return `InternalServerError` for `io::Error` impl ResponseError for io::Error { - fn error_response(&self) -> HttpResponse { + fn error_response(&self) -> Response { match self.kind() { - io::ErrorKind::NotFound => HttpResponse::new(StatusCode::NOT_FOUND), - io::ErrorKind::PermissionDenied => HttpResponse::new(StatusCode::FORBIDDEN), - _ => HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR), + io::ErrorKind::NotFound => Response::new(StatusCode::NOT_FOUND), + io::ErrorKind::PermissionDenied => Response::new(StatusCode::FORBIDDEN), + _ => Response::new(StatusCode::INTERNAL_SERVER_ERROR), } } } /// `BadRequest` for `InvalidHeaderValue` impl ResponseError for header::InvalidHeaderValue { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) + fn error_response(&self) -> Response { + Response::new(StatusCode::BAD_REQUEST) } } /// `BadRequest` for `InvalidHeaderValue` impl ResponseError for header::InvalidHeaderValueBytes { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) + fn error_response(&self) -> Response { + Response::new(StatusCode::BAD_REQUEST) } } @@ -288,8 +287,8 @@ pub enum ParseError { /// Return `BadRequest` for `ParseError` impl ResponseError for ParseError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) + fn error_response(&self) -> Response { + Response::new(StatusCode::BAD_REQUEST) } } @@ -362,18 +361,18 @@ impl From for PayloadError { /// - `Overflow` returns `PayloadTooLarge` /// - Other errors returns `BadRequest` impl ResponseError for PayloadError { - fn error_response(&self) -> HttpResponse { + fn error_response(&self) -> Response { match *self { - PayloadError::Overflow => HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE), - _ => HttpResponse::new(StatusCode::BAD_REQUEST), + PayloadError::Overflow => Response::new(StatusCode::PAYLOAD_TOO_LARGE), + _ => Response::new(StatusCode::BAD_REQUEST), } } } /// Return `BadRequest` for `cookie::ParseError` impl ResponseError for cookie::ParseError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) + fn error_response(&self) -> Response { + Response::new(StatusCode::BAD_REQUEST) } } @@ -443,8 +442,8 @@ pub enum ContentTypeError { /// Return `BadRequest` for `ContentTypeError` impl ResponseError for ContentTypeError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) + fn error_response(&self) -> Response { + Response::new(StatusCode::BAD_REQUEST) } } @@ -475,15 +474,11 @@ pub enum UrlencodedError { /// Return `BadRequest` for `UrlencodedError` impl ResponseError for UrlencodedError { - fn error_response(&self) -> HttpResponse { + fn error_response(&self) -> Response { match *self { - UrlencodedError::Overflow => { - HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) - } - UrlencodedError::UnknownLength => { - HttpResponse::new(StatusCode::LENGTH_REQUIRED) - } - _ => HttpResponse::new(StatusCode::BAD_REQUEST), + UrlencodedError::Overflow => Response::new(StatusCode::PAYLOAD_TOO_LARGE), + UrlencodedError::UnknownLength => Response::new(StatusCode::LENGTH_REQUIRED), + _ => Response::new(StatusCode::BAD_REQUEST), } } } @@ -513,12 +508,10 @@ pub enum JsonPayloadError { /// Return `BadRequest` for `UrlencodedError` impl ResponseError for JsonPayloadError { - fn error_response(&self) -> HttpResponse { + fn error_response(&self) -> Response { match *self { - JsonPayloadError::Overflow => { - HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) - } - _ => HttpResponse::new(StatusCode::BAD_REQUEST), + JsonPayloadError::Overflow => Response::new(StatusCode::PAYLOAD_TOO_LARGE), + _ => Response::new(StatusCode::BAD_REQUEST), } } } @@ -606,7 +599,7 @@ pub struct InternalError { enum InternalErrorType { Status(StatusCode), - Response(Box>>), + Response(Box>>), } impl InternalError { @@ -619,8 +612,8 @@ impl InternalError { } } - /// Create `InternalError` with predefined `HttpResponse`. - pub fn from_response(cause: T, response: HttpResponse) -> Self { + /// Create `InternalError` with predefined `Response`. + pub fn from_response(cause: T, response: Response) -> Self { let resp = response.into_parts(); InternalError { cause, @@ -661,14 +654,14 @@ impl ResponseError for InternalError where T: Send + Sync + fmt::Debug + fmt::Display + 'static, { - fn error_response(&self) -> HttpResponse { + fn error_response(&self) -> Response { match self.status { - InternalErrorType::Status(st) => HttpResponse::new(st), + InternalErrorType::Status(st) => Response::new(st), InternalErrorType::Response(ref resp) => { if let Some(resp) = resp.lock().unwrap().take() { - HttpResponse::from_parts(resp) + Response::from_parts(resp) } else { - HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR) + Response::new(StatusCode::INTERNAL_SERVER_ERROR) } } } @@ -838,14 +831,14 @@ mod tests { #[test] fn test_into_response() { - let resp: HttpResponse = ParseError::Incomplete.error_response(); + let resp: Response = ParseError::Incomplete.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = CookieParseError::EmptyName.error_response(); + let resp: Response = CookieParseError::EmptyName.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into(); - let resp: HttpResponse = err.error_response(); + let resp: Response = err.error_response(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } @@ -883,7 +876,7 @@ mod tests { fn test_error_http_response() { let orig = io::Error::new(io::ErrorKind::Other, "other"); let e = Error::from(orig); - let resp: HttpResponse = e.into(); + let resp: Response = e.into(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } @@ -944,8 +937,8 @@ mod tests { #[test] fn test_internal_error() { let err = - InternalError::from_response(ParseError::Method, HttpResponse::Ok().into()); - let resp: HttpResponse = err.error_response(); + InternalError::from_response(ParseError::Method, Response::Ok().into()); + let resp: Response = err.error_response(); assert_eq!(resp.status(), StatusCode::OK); } @@ -977,49 +970,49 @@ mod tests { #[test] fn test_error_helpers() { - let r: HttpResponse = ErrorBadRequest("err").into(); + let r: Response = ErrorBadRequest("err").into(); assert_eq!(r.status(), StatusCode::BAD_REQUEST); - let r: HttpResponse = ErrorUnauthorized("err").into(); + let r: Response = ErrorUnauthorized("err").into(); assert_eq!(r.status(), StatusCode::UNAUTHORIZED); - let r: HttpResponse = ErrorForbidden("err").into(); + let r: Response = ErrorForbidden("err").into(); assert_eq!(r.status(), StatusCode::FORBIDDEN); - let r: HttpResponse = ErrorNotFound("err").into(); + let r: Response = ErrorNotFound("err").into(); assert_eq!(r.status(), StatusCode::NOT_FOUND); - let r: HttpResponse = ErrorMethodNotAllowed("err").into(); + let r: Response = ErrorMethodNotAllowed("err").into(); assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED); - let r: HttpResponse = ErrorRequestTimeout("err").into(); + let r: Response = ErrorRequestTimeout("err").into(); assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT); - let r: HttpResponse = ErrorConflict("err").into(); + let r: Response = ErrorConflict("err").into(); assert_eq!(r.status(), StatusCode::CONFLICT); - let r: HttpResponse = ErrorGone("err").into(); + let r: Response = ErrorGone("err").into(); assert_eq!(r.status(), StatusCode::GONE); - let r: HttpResponse = ErrorPreconditionFailed("err").into(); + let r: Response = ErrorPreconditionFailed("err").into(); assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED); - let r: HttpResponse = ErrorExpectationFailed("err").into(); + let r: Response = ErrorExpectationFailed("err").into(); assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED); - let r: HttpResponse = ErrorInternalServerError("err").into(); + let r: Response = ErrorInternalServerError("err").into(); assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR); - let r: HttpResponse = ErrorNotImplemented("err").into(); + let r: Response = ErrorNotImplemented("err").into(); assert_eq!(r.status(), StatusCode::NOT_IMPLEMENTED); - let r: HttpResponse = ErrorBadGateway("err").into(); + let r: Response = ErrorBadGateway("err").into(); assert_eq!(r.status(), StatusCode::BAD_GATEWAY); - let r: HttpResponse = ErrorServiceUnavailable("err").into(); + let r: Response = ErrorServiceUnavailable("err").into(); assert_eq!(r.status(), StatusCode::SERVICE_UNAVAILABLE); - let r: HttpResponse = ErrorGatewayTimeout("err").into(); + let r: Response = ErrorGatewayTimeout("err").into(); assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT); } } diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 40d0a240f..a27e6472c 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -12,8 +12,8 @@ use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{Method, Version}; -use httpresponse::HttpResponse; use request::RequestPool; +use response::Response; bitflags! { struct Flags: u8 { @@ -29,7 +29,7 @@ const AVERAGE_HEADER_SIZE: usize = 30; /// Http response pub enum OutMessage { /// Http response message - Response(HttpResponse), + Response(Response), /// Payload chunk Payload(Bytes), } @@ -87,7 +87,7 @@ impl Codec { } fn encode_response( - &mut self, mut msg: HttpResponse, buffer: &mut BytesMut, + &mut self, mut msg: Response, buffer: &mut BytesMut, ) -> io::Result<()> { // prepare transfer encoding self.te diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 90946b453..48776226b 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -628,7 +628,7 @@ mod tests { // let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); // let readbuf = BytesMut::new(); - // let mut h1 = Dispatcher::new(buf, |req| ok(HttpResponse::Ok().finish())); + // let mut h1 = Dispatcher::new(buf, |req| ok(Response::Ok().finish())); // assert!(h1.poll_io().is_ok()); // assert!(h1.poll_io().is_ok()); // assert!(h1.flags.contains(Flags::READ_DISCONNECTED)); diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 183094028..728a78b9f 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -17,8 +17,8 @@ use body::Body; use config::ServiceConfig; use error::DispatchError; use framed::Framed; -use httpresponse::HttpResponse; use request::Request; +use response::Response; use super::codec::{Codec, InMessage, OutMessage}; @@ -77,7 +77,7 @@ impl State { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Error: Debug + Display, { /// Create http/1 dispatcher. @@ -415,7 +415,7 @@ where .insert(Flags::STARTED | Flags::READ_DISCONNECTED); self.state = State::SendResponse(Some(OutMessage::Response( - HttpResponse::RequestTimeout().finish(), + Response::RequestTimeout().finish(), ))); } else { trace!("Keep-alive timeout, close connection"); @@ -452,7 +452,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Error: Debug + Display, { type Item = (); diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index d17587358..1544b2404 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -11,8 +11,8 @@ use http::{StatusCode, Version}; use body::{Binary, Body}; use header::ContentEncoding; use http::Method; -use httpresponse::HttpResponse; use request::Request; +use response::Response; #[derive(Debug)] pub(crate) enum ResponseLength { @@ -41,7 +41,7 @@ impl Default for ResponseEncoder { } impl ResponseEncoder { - pub fn update(&mut self, resp: &mut HttpResponse, head: bool, version: Version) { + pub fn update(&mut self, resp: &mut Response, head: bool, version: Version) { self.head = head; let version = resp.version().unwrap_or_else(|| version); @@ -98,7 +98,7 @@ impl ResponseEncoder { } fn streaming_encoding( - &mut self, version: Version, resp: &mut HttpResponse, + &mut self, version: Version, resp: &mut Response, ) -> TransferEncoding { match resp.chunked() { Some(true) => { diff --git a/src/h1/service.rs b/src/h1/service.rs index 436e77a55..8038c9fb8 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -7,8 +7,8 @@ use tokio_io::{AsyncRead, AsyncWrite}; use config::ServiceConfig; use error::DispatchError; -use httpresponse::HttpResponse; use request::Request; +use response::Response; use super::dispatcher::Dispatcher; @@ -36,7 +36,7 @@ where impl NewService for H1Service where T: AsyncRead + AsyncWrite, - S: NewService + Clone, + S: NewService + Clone, S::Service: Clone, S::Error: Debug + Display, { @@ -65,7 +65,7 @@ pub struct H1ServiceResponse { impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService, S::Service: Clone, S::Error: Debug + Display, { @@ -90,7 +90,7 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where - S: Service + Clone, + S: Service + Clone, S::Error: Debug + Display, { fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { @@ -105,7 +105,7 @@ where impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service + Clone, + S: Service + Clone, S::Error: Debug + Display, { type Request = T; diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 41e57d1ee..7d42a1cc3 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -1,18 +1,18 @@ //! Basic http responses #![allow(non_upper_case_globals)] use http::StatusCode; -use httpresponse::{HttpResponse, HttpResponseBuilder}; +use response::{Response, ResponseBuilder}; macro_rules! STATIC_RESP { ($name:ident, $status:expr) => { #[allow(non_snake_case, missing_docs)] - pub fn $name() -> HttpResponseBuilder { - HttpResponse::build($status) + pub fn $name() -> ResponseBuilder { + Response::build($status) } }; } -impl HttpResponse { +impl Response { STATIC_RESP!(Ok, StatusCode::OK); STATIC_RESP!(Created, StatusCode::CREATED); STATIC_RESP!(Accepted, StatusCode::ACCEPTED); @@ -74,11 +74,11 @@ impl HttpResponse { mod tests { use body::Body; use http::StatusCode; - use httpresponse::HttpResponse; + use response::Response; #[test] fn test_build() { - let resp = HttpResponse::Ok().body(Body::Empty); + let resp = Response::Ok().body(Body::Empty); assert_eq!(resp.status(), StatusCode::OK); } } diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 531aa1a72..f68f3650b 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -112,18 +112,18 @@ pub trait HttpMessage: Sized { /// # extern crate futures; /// # #[macro_use] extern crate serde_derive; /// use actix_web::{ - /// AsyncResponder, FutureResponse, HttpMessage, HttpRequest, HttpResponse, + /// AsyncResponder, FutureResponse, HttpMessage, HttpRequest, Response, /// }; /// use bytes::Bytes; /// use futures::future::Future; /// - /// fn index(mut req: HttpRequest) -> FutureResponse { + /// fn index(mut req: HttpRequest) -> FutureResponse { /// req.body() // <- get Body future /// .limit(1024) // <- change max size of the body to a 1kb /// .from_err() /// .and_then(|bytes: Bytes| { // <- complete body /// println!("==== BODY ==== {:?}", bytes); - /// Ok(HttpResponse::Ok().into()) + /// Ok(Response::Ok().into()) /// }).responder() /// } /// # fn main() {} @@ -148,15 +148,15 @@ pub trait HttpMessage: Sized { /// # extern crate futures; /// # use futures::Future; /// # use std::collections::HashMap; - /// use actix_web::{FutureResponse, HttpMessage, HttpRequest, HttpResponse}; + /// use actix_web::{FutureResponse, HttpMessage, HttpRequest, Response}; /// - /// fn index(mut req: HttpRequest) -> FutureResponse { + /// fn index(mut req: HttpRequest) -> FutureResponse { /// Box::new( /// req.urlencoded::>() // <- get UrlEncoded future /// .from_err() /// .and_then(|params| { // <- url encoded parameters /// println!("==== BODY ==== {:?}", params); - /// Ok(HttpResponse::Ok().into()) + /// Ok(Response::Ok().into()) /// }), /// ) /// } @@ -188,12 +188,12 @@ pub trait HttpMessage: Sized { /// name: String, /// } /// - /// fn index(mut req: HttpRequest) -> Box> { + /// fn index(mut req: HttpRequest) -> Box> { /// req.json() // <- get JsonBody future /// .from_err() /// .and_then(|val: MyObj| { // <- deserialized value /// println!("==== BODY ==== {:?}", val); - /// Ok(HttpResponse::Ok().into()) + /// Ok(Response::Ok().into()) /// }).responder() /// } /// # fn main() {} diff --git a/src/json.rs b/src/json.rs index 5c64b9bdd..a52884894 100644 --- a/src/json.rs +++ b/src/json.rs @@ -123,7 +123,7 @@ where /// # extern crate actix_web; /// # extern crate futures; /// # #[macro_use] extern crate serde_derive; -/// use actix_web::{AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse}; +/// use actix_web::{AsyncResponder, Error, HttpMessage, HttpRequest, Response}; /// use futures::future::Future; /// /// #[derive(Deserialize, Debug)] @@ -131,12 +131,12 @@ where /// name: String, /// } /// -/// fn index(mut req: HttpRequest) -> Box> { +/// fn index(mut req: HttpRequest) -> Box> { /// req.json() // <- get JsonBody future /// .from_err() /// .and_then(|val: MyObj| { // <- deserialized value /// println!("==== BODY ==== {:?}", val); -/// Ok(HttpResponse::Ok().into()) +/// Ok(Response::Ok().into()) /// }).responder() /// } /// # fn main() {} diff --git a/src/lib.rs b/src/lib.rs index ae5a9f957..74e7ced78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,8 +41,8 @@ //! represents an HTTP server instance and is used to instantiate and //! configure servers. //! -//! * [HttpRequest](struct.HttpRequest.html) and -//! [HttpResponse](struct.HttpResponse.html): These structs +//! * [Request](struct.Request.html) and +//! [Response](struct.Response.html): These structs //! represent HTTP requests and responses and expose various methods //! for inspecting, creating and otherwise utilizing them. //! @@ -129,10 +129,10 @@ mod extensions; mod header; mod httpcodes; mod httpmessage; -mod httpresponse; mod json; mod payload; mod request; +mod response; mod uri; #[doc(hidden)] @@ -147,9 +147,9 @@ pub use body::{Binary, Body}; pub use error::{Error, ResponseError, Result}; pub use extensions::Extensions; pub use httpmessage::HttpMessage; -pub use httpresponse::HttpResponse; pub use json::Json; pub use request::Request; +pub use response::Response; pub use self::config::{KeepAlive, ServiceConfig, ServiceConfigBuilder}; @@ -166,9 +166,9 @@ pub mod dev { pub use body::BodyStream; pub use httpmessage::{MessageBody, Readlines, UrlEncoded}; - pub use httpresponse::HttpResponseBuilder; pub use json::JsonBody; pub use payload::{Payload, PayloadBuffer}; + pub use response::ResponseBuilder; } pub mod http { @@ -187,5 +187,5 @@ pub mod http { pub use header::*; } pub use header::ContentEncoding; - pub use httpresponse::ConnectionType; + pub use response::ConnectionType; } diff --git a/src/payload.rs b/src/payload.rs index 2131e3c3c..3f51f6ec0 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -27,7 +27,7 @@ pub(crate) enum PayloadStatus { /// `.readany()` method. Payload stream is not thread safe. Payload does not /// notify current task when new data is available. /// -/// Payload stream can be used as `HttpResponse` body stream. +/// Payload stream can be used as `Response` body stream. #[derive(Debug)] pub struct Payload { inner: Rc>, diff --git a/src/httpresponse.rs b/src/response.rs similarity index 81% rename from src/httpresponse.rs rename to src/response.rs index 3c034fae3..d0136b408 100644 --- a/src/httpresponse.rs +++ b/src/response.rs @@ -31,54 +31,54 @@ pub enum ConnectionType { } /// An HTTP Response -pub struct HttpResponse(Box, &'static HttpResponsePool); +pub struct Response(Box, &'static ResponsePool); -impl HttpResponse { +impl Response { #[inline] - fn get_ref(&self) -> &InnerHttpResponse { + fn get_ref(&self) -> &InnerResponse { self.0.as_ref() } #[inline] - fn get_mut(&mut self) -> &mut InnerHttpResponse { + fn get_mut(&mut self) -> &mut InnerResponse { self.0.as_mut() } /// Create http response builder with specific status. #[inline] - pub fn build(status: StatusCode) -> HttpResponseBuilder { - HttpResponsePool::get(status) + pub fn build(status: StatusCode) -> ResponseBuilder { + ResponsePool::get(status) } /// Create http response builder #[inline] - pub fn build_from>(source: T) -> HttpResponseBuilder { + pub fn build_from>(source: T) -> ResponseBuilder { source.into() } /// Constructs a response #[inline] - pub fn new(status: StatusCode) -> HttpResponse { - HttpResponsePool::with_body(status, Body::Empty) + pub fn new(status: StatusCode) -> Response { + ResponsePool::with_body(status, Body::Empty) } /// Constructs a response with body #[inline] - pub fn with_body>(status: StatusCode, body: B) -> HttpResponse { - HttpResponsePool::with_body(status, body.into()) + pub fn with_body>(status: StatusCode, body: B) -> Response { + ResponsePool::with_body(status, body.into()) } /// Constructs an error response #[inline] - pub fn from_error(error: Error) -> HttpResponse { + pub fn from_error(error: Error) -> Response { let mut resp = error.as_response_error().error_response(); resp.get_mut().error = Some(error); resp } - /// Convert `HttpResponse` to a `HttpResponseBuilder` + /// Convert `Response` to a `ResponseBuilder` #[inline] - pub fn into_builder(self) -> HttpResponseBuilder { + pub fn into_builder(self) -> ResponseBuilder { // If this response has cookies, load them into a jar let mut jar: Option = None; for c in self.cookies() { @@ -91,7 +91,7 @@ impl HttpResponse { } } - HttpResponseBuilder { + ResponseBuilder { pool: self.1, response: Some(self.0), err: None, @@ -282,23 +282,23 @@ impl HttpResponse { self.1.release(self.0); } - pub(crate) fn into_parts(self) -> HttpResponseParts { + pub(crate) fn into_parts(self) -> ResponseParts { self.0.into_parts() } - pub(crate) fn from_parts(parts: HttpResponseParts) -> HttpResponse { - HttpResponse( - Box::new(InnerHttpResponse::from_parts(parts)), - HttpResponsePool::get_pool(), + pub(crate) fn from_parts(parts: ResponseParts) -> Response { + Response( + Box::new(InnerResponse::from_parts(parts)), + ResponsePool::get_pool(), ) } } -impl fmt::Debug for HttpResponse { +impl fmt::Debug for Response { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let res = writeln!( f, - "\nHttpResponse {:?} {}{}", + "\nResponse {:?} {}{}", self.get_ref().version, self.get_ref().status, self.get_ref().reason.unwrap_or("") @@ -332,16 +332,16 @@ impl<'a> Iterator for CookieIter<'a> { /// An HTTP response builder /// -/// This type can be used to construct an instance of `HttpResponse` through a +/// This type can be used to construct an instance of `Response` through a /// builder-like pattern. -pub struct HttpResponseBuilder { - pool: &'static HttpResponsePool, - response: Option>, +pub struct ResponseBuilder { + pool: &'static ResponsePool, + response: Option>, err: Option, cookies: Option, } -impl HttpResponseBuilder { +impl ResponseBuilder { /// Set HTTP status code of this response. #[inline] pub fn status(&mut self, status: StatusCode) -> &mut Self { @@ -366,10 +366,10 @@ impl HttpResponseBuilder { /// /// ```rust,ignore /// # extern crate actix_web; - /// use actix_web::{http, HttpRequest, HttpResponse, Result}; + /// use actix_web::{http, Request, Response, Result}; /// - /// fn index(req: HttpRequest) -> Result { - /// Ok(HttpResponse::Ok() + /// fn index(req: HttpRequest) -> Result { + /// Ok(Response::Ok() /// .set(http::header::IfModifiedSince( /// "Sun, 07 Nov 1994 08:48:37 GMT".parse()?, /// )) @@ -394,10 +394,10 @@ impl HttpResponseBuilder { /// /// ```rust,ignore /// # extern crate actix_web; - /// use actix_web::{http, HttpRequest, HttpResponse}; + /// use actix_web::{http, Request, Response}; /// - /// fn index(req: HttpRequest) -> HttpResponse { - /// HttpResponse::Ok() + /// fn index(req: HttpRequest) -> Response { + /// Response::Ok() /// .header("X-TEST", "value") /// .header(http::header::CONTENT_TYPE, "application/json") /// .finish() @@ -516,10 +516,10 @@ impl HttpResponseBuilder { /// /// ```rust,ignore /// # extern crate actix_web; - /// use actix_web::{http, HttpRequest, HttpResponse, Result}; + /// use actix_web::{http, HttpRequest, Response, Result}; /// - /// fn index(req: HttpRequest) -> HttpResponse { - /// HttpResponse::Ok() + /// fn index(req: HttpRequest) -> Response { + /// Response::Ok() /// .cookie( /// http::Cookie::build("name", "value") /// .domain("www.rust-lang.org") @@ -546,10 +546,10 @@ impl HttpResponseBuilder { /// /// ```rust,ignore /// # extern crate actix_web; - /// use actix_web::{http, HttpRequest, HttpResponse, Result}; + /// use actix_web::{http, HttpRequest, Response, Result}; /// - /// fn index(req: &HttpRequest) -> HttpResponse { - /// let mut builder = HttpResponse::Ok(); + /// fn index(req: &HttpRequest) -> Response { + /// let mut builder = Response::Ok(); /// /// if let Some(ref cookie) = req.cookie("name") { /// builder.del_cookie(cookie); @@ -575,7 +575,7 @@ impl HttpResponseBuilder { /// true. pub fn if_true(&mut self, value: bool, f: F) -> &mut Self where - F: FnOnce(&mut HttpResponseBuilder), + F: FnOnce(&mut ResponseBuilder), { if value { f(self); @@ -587,7 +587,7 @@ impl HttpResponseBuilder { /// Some. pub fn if_some(&mut self, value: Option, f: F) -> &mut Self where - F: FnOnce(T, &mut HttpResponseBuilder), + F: FnOnce(T, &mut ResponseBuilder), { if let Some(val) = value { f(val, self); @@ -609,10 +609,10 @@ impl HttpResponseBuilder { self } - /// Set a body and generate `HttpResponse`. + /// Set a body and generate `Response`. /// - /// `HttpResponseBuilder` can not be used after this call. - pub fn body>(&mut self, body: B) -> HttpResponse { + /// `ResponseBuilder` can not be used after this call. + pub fn body>(&mut self, body: B) -> Response { if let Some(e) = self.err.take() { return Error::from(e).into(); } @@ -626,14 +626,14 @@ impl HttpResponseBuilder { } } response.body = body.into(); - HttpResponse(response, self.pool) + Response(response, self.pool) } #[inline] - /// Set a streaming body and generate `HttpResponse`. + /// Set a streaming body and generate `Response`. /// - /// `HttpResponseBuilder` can not be used after this call. - pub fn streaming(&mut self, stream: S) -> HttpResponse + /// `ResponseBuilder` can not be used after this call. + pub fn streaming(&mut self, stream: S) -> Response where S: Stream + 'static, E: Into, @@ -641,17 +641,17 @@ impl HttpResponseBuilder { self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) } - /// Set a json body and generate `HttpResponse` + /// Set a json body and generate `Response` /// - /// `HttpResponseBuilder` can not be used after this call. - pub fn json(&mut self, value: T) -> HttpResponse { + /// `ResponseBuilder` can not be used after this call. + pub fn json(&mut self, value: T) -> Response { self.json2(&value) } - /// Set a json body and generate `HttpResponse` + /// Set a json body and generate `Response` /// - /// `HttpResponseBuilder` can not be used after this call. - pub fn json2(&mut self, value: &T) -> HttpResponse { + /// `ResponseBuilder` can not be used after this call. + pub fn json2(&mut self, value: &T) -> Response { match serde_json::to_string(value) { Ok(body) => { let contains = if let Some(parts) = parts(&mut self.response, &self.err) @@ -671,16 +671,16 @@ impl HttpResponseBuilder { } #[inline] - /// Set an empty body and generate `HttpResponse` + /// Set an empty body and generate `Response` /// - /// `HttpResponseBuilder` can not be used after this call. - pub fn finish(&mut self) -> HttpResponse { + /// `ResponseBuilder` can not be used after this call. + pub fn finish(&mut self) -> Response { self.body(Body::Empty) } - /// This method construct new `HttpResponseBuilder` - pub fn take(&mut self) -> HttpResponseBuilder { - HttpResponseBuilder { + /// This method construct new `ResponseBuilder` + pub fn take(&mut self) -> ResponseBuilder { + ResponseBuilder { pool: self.pool, response: self.response.take(), err: self.err.take(), @@ -692,8 +692,8 @@ impl HttpResponseBuilder { #[inline] #[cfg_attr(feature = "cargo-clippy", allow(clippy::borrowed_box))] fn parts<'a>( - parts: &'a mut Option>, err: &Option, -) -> Option<&'a mut Box> { + parts: &'a mut Option>, err: &Option, +) -> Option<&'a mut Box> { if err.is_some() { return None; } @@ -701,7 +701,7 @@ fn parts<'a>( } /// Helper converters -impl, E: Into> From> for HttpResponse { +impl, E: Into> From> for Response { fn from(res: Result) -> Self { match res { Ok(val) => val.into(), @@ -710,62 +710,62 @@ impl, E: Into> From> for HttpResponse } } -impl From for HttpResponse { - fn from(mut builder: HttpResponseBuilder) -> Self { +impl From for Response { + fn from(mut builder: ResponseBuilder) -> Self { builder.finish() } } -impl From<&'static str> for HttpResponse { +impl From<&'static str> for Response { fn from(val: &'static str) -> Self { - HttpResponse::Ok() + Response::Ok() .content_type("text/plain; charset=utf-8") .body(val) } } -impl From<&'static [u8]> for HttpResponse { +impl From<&'static [u8]> for Response { fn from(val: &'static [u8]) -> Self { - HttpResponse::Ok() + Response::Ok() .content_type("application/octet-stream") .body(val) } } -impl From for HttpResponse { +impl From for Response { fn from(val: String) -> Self { - HttpResponse::Ok() + Response::Ok() .content_type("text/plain; charset=utf-8") .body(val) } } -impl<'a> From<&'a String> for HttpResponse { +impl<'a> From<&'a String> for Response { fn from(val: &'a String) -> Self { - HttpResponse::build(StatusCode::OK) + Response::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(val) } } -impl From for HttpResponse { +impl From for Response { fn from(val: Bytes) -> Self { - HttpResponse::Ok() + Response::Ok() .content_type("application/octet-stream") .body(val) } } -impl From for HttpResponse { +impl From for Response { fn from(val: BytesMut) -> Self { - HttpResponse::Ok() + Response::Ok() .content_type("application/octet-stream") .body(val) } } #[derive(Debug)] -struct InnerHttpResponse { +struct InnerResponse { version: Option, headers: HeaderMap, status: StatusCode, @@ -779,7 +779,7 @@ struct InnerHttpResponse { error: Option, } -pub(crate) struct HttpResponseParts { +pub(crate) struct ResponseParts { version: Option, headers: HeaderMap, status: StatusCode, @@ -790,10 +790,10 @@ pub(crate) struct HttpResponseParts { error: Option, } -impl InnerHttpResponse { +impl InnerResponse { #[inline] - fn new(status: StatusCode, body: Body) -> InnerHttpResponse { - InnerHttpResponse { + fn new(status: StatusCode, body: Body) -> InnerResponse { + InnerResponse { status, body, version: None, @@ -809,7 +809,7 @@ impl InnerHttpResponse { } /// This is for failure, we can not have Send + Sync on Streaming and Actor response - fn into_parts(mut self) -> HttpResponseParts { + fn into_parts(mut self) -> ResponseParts { let body = match mem::replace(&mut self.body, Body::Empty) { Body::Empty => None, Body::Binary(mut bin) => Some(bin.take()), @@ -819,7 +819,7 @@ impl InnerHttpResponse { } }; - HttpResponseParts { + ResponseParts { body, version: self.version, headers: self.headers, @@ -831,14 +831,14 @@ impl InnerHttpResponse { } } - fn from_parts(parts: HttpResponseParts) -> InnerHttpResponse { + fn from_parts(parts: ResponseParts) -> InnerResponse { let body = if let Some(ref body) = parts.body { Body::Binary(body.clone().into()) } else { Body::Empty }; - InnerHttpResponse { + InnerResponse { body, status: parts.status, version: parts.version, @@ -855,35 +855,35 @@ impl InnerHttpResponse { } /// Internal use only! -pub(crate) struct HttpResponsePool(RefCell>>); +pub(crate) struct ResponsePool(RefCell>>); -thread_local!(static POOL: &'static HttpResponsePool = HttpResponsePool::pool()); +thread_local!(static POOL: &'static ResponsePool = ResponsePool::pool()); -impl HttpResponsePool { - fn pool() -> &'static HttpResponsePool { - let pool = HttpResponsePool(RefCell::new(VecDeque::with_capacity(128))); +impl ResponsePool { + fn pool() -> &'static ResponsePool { + let pool = ResponsePool(RefCell::new(VecDeque::with_capacity(128))); Box::leak(Box::new(pool)) } - pub fn get_pool() -> &'static HttpResponsePool { + pub fn get_pool() -> &'static ResponsePool { POOL.with(|p| *p) } #[inline] pub fn get_builder( - pool: &'static HttpResponsePool, status: StatusCode, - ) -> HttpResponseBuilder { + pool: &'static ResponsePool, status: StatusCode, + ) -> ResponseBuilder { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { msg.status = status; - HttpResponseBuilder { + ResponseBuilder { pool, response: Some(msg), err: None, cookies: None, } } else { - let msg = Box::new(InnerHttpResponse::new(status, Body::Empty)); - HttpResponseBuilder { + let msg = Box::new(InnerResponse::new(status, Body::Empty)); + ResponseBuilder { pool, response: Some(msg), err: None, @@ -894,30 +894,30 @@ impl HttpResponsePool { #[inline] pub fn get_response( - pool: &'static HttpResponsePool, status: StatusCode, body: Body, - ) -> HttpResponse { + pool: &'static ResponsePool, status: StatusCode, body: Body, + ) -> Response { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { msg.status = status; msg.body = body; - HttpResponse(msg, pool) + Response(msg, pool) } else { - let msg = Box::new(InnerHttpResponse::new(status, body)); - HttpResponse(msg, pool) + let msg = Box::new(InnerResponse::new(status, body)); + Response(msg, pool) } } #[inline] - fn get(status: StatusCode) -> HttpResponseBuilder { - POOL.with(|pool| HttpResponsePool::get_builder(pool, status)) + fn get(status: StatusCode) -> ResponseBuilder { + POOL.with(|pool| ResponsePool::get_builder(pool, status)) } #[inline] - fn with_body(status: StatusCode, body: Body) -> HttpResponse { - POOL.with(|pool| HttpResponsePool::get_response(pool, status, body)) + fn with_body(status: StatusCode, body: Body) -> Response { + POOL.with(|pool| ResponsePool::get_response(pool, status, body)) } #[inline] - fn release(&self, mut inner: Box) { + fn release(&self, mut inner: Box) { let mut p = self.0.borrow_mut(); if p.len() < 128 { inner.headers.clear(); @@ -946,12 +946,12 @@ mod tests { #[test] fn test_debug() { - let resp = HttpResponse::Ok() + let resp = Response::Ok() .header(COOKIE, HeaderValue::from_static("cookie1=value1; ")) .header(COOKIE, HeaderValue::from_static("cookie2=value2; ")) .finish(); let dbg = format!("{:?}", resp); - assert!(dbg.contains("HttpResponse")); + assert!(dbg.contains("Response")); } // #[test] @@ -962,7 +962,7 @@ mod tests { // .finish(); // let cookies = req.cookies().unwrap(); - // let resp = HttpResponse::Ok() + // let resp = Response::Ok() // .cookie( // http::Cookie::build("name", "value") // .domain("www.rust-lang.org") @@ -989,7 +989,7 @@ mod tests { #[test] fn test_update_response_cookies() { - let mut r = HttpResponse::Ok() + let mut r = Response::Ok() .cookie(http::Cookie::new("original", "val100")) .finish(); @@ -1012,7 +1012,7 @@ mod tests { #[test] fn test_basic_builder() { - let resp = HttpResponse::Ok() + let resp = Response::Ok() .header("X-TEST", "value") .version(Version::HTTP_10) .finish(); @@ -1022,19 +1022,19 @@ mod tests { #[test] fn test_upgrade() { - let resp = HttpResponse::build(StatusCode::OK).upgrade().finish(); + let resp = Response::build(StatusCode::OK).upgrade().finish(); assert!(resp.upgrade()) } #[test] fn test_force_close() { - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + let resp = Response::build(StatusCode::OK).force_close().finish(); assert!(!resp.keep_alive().unwrap()) } #[test] fn test_content_type() { - let resp = HttpResponse::build(StatusCode::OK) + let resp = Response::build(StatusCode::OK) .content_type("text/plain") .body(Body::Empty); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") @@ -1042,18 +1042,18 @@ mod tests { #[test] fn test_content_encoding() { - let resp = HttpResponse::build(StatusCode::OK).finish(); + let resp = Response::build(StatusCode::OK).finish(); assert_eq!(resp.content_encoding(), None); #[cfg(feature = "brotli")] { - let resp = HttpResponse::build(StatusCode::OK) + let resp = Response::build(StatusCode::OK) .content_encoding(ContentEncoding::Br) .finish(); assert_eq!(resp.content_encoding(), Some(ContentEncoding::Br)); } - let resp = HttpResponse::build(StatusCode::OK) + let resp = Response::build(StatusCode::OK) .content_encoding(ContentEncoding::Gzip) .finish(); assert_eq!(resp.content_encoding(), Some(ContentEncoding::Gzip)); @@ -1061,7 +1061,7 @@ mod tests { #[test] fn test_json() { - let resp = HttpResponse::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]); + let resp = Response::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!( @@ -1072,7 +1072,7 @@ mod tests { #[test] fn test_json_ct() { - let resp = HttpResponse::build(StatusCode::OK) + let resp = Response::build(StatusCode::OK) .header(CONTENT_TYPE, "text/json") .json(vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); @@ -1085,7 +1085,7 @@ mod tests { #[test] fn test_json2() { - let resp = HttpResponse::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]); + let resp = Response::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!( @@ -1096,7 +1096,7 @@ mod tests { #[test] fn test_json2_ct() { - let resp = HttpResponse::build(StatusCode::OK) + let resp = Response::build(StatusCode::OK) .header(CONTENT_TYPE, "text/json") .json2(&vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); @@ -1120,7 +1120,7 @@ mod tests { fn test_into_response() { let req = TestRequest::default().finish(); - let resp: HttpResponse = "test".into(); + let resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1129,7 +1129,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), &Binary::from("test")); - let resp: HttpResponse = b"test".as_ref().into(); + let resp: Response = b"test".as_ref().into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1138,7 +1138,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref())); - let resp: HttpResponse = "test".to_owned().into(); + let resp: Response = "test".to_owned().into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1147,7 +1147,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned())); - let resp: HttpResponse = (&"test".to_owned()).into(); + let resp: Response = (&"test".to_owned()).into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1157,7 +1157,7 @@ mod tests { assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned())); let b = Bytes::from_static(b"test"); - let resp: HttpResponse = b.into(); + let resp: Response = b.into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1170,7 +1170,7 @@ mod tests { ); let b = Bytes::from_static(b"test"); - let resp: HttpResponse = b.into(); + let resp: Response = b.into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1180,7 +1180,7 @@ mod tests { assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test"))); let b = BytesMut::from("test"); - let resp: HttpResponse = b.into(); + let resp: Response = b.into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1192,7 +1192,7 @@ mod tests { #[test] fn test_into_builder() { - let mut resp: HttpResponse = "test".into(); + let mut resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); resp.add_cookie(&http::Cookie::new("cookie1", "val100")) diff --git a/src/test.rs b/src/test.rs index 3c48df643..71145cee8 100644 --- a/src/test.rs +++ b/src/test.rs @@ -25,12 +25,12 @@ use uri::Url as InnerUrl; /// /// # Examples /// -/// ```rust +/// ```rust,ignore /// # extern crate actix_web; /// # use actix_web::*; /// # -/// # fn my_handler(req: &HttpRequest) -> HttpResponse { -/// # HttpResponse::Ok().into() +/// # fn my_handler(req: &HttpRequest) -> Response { +/// # Response::Ok().into() /// # } /// # /// # fn main() { @@ -248,20 +248,20 @@ impl Drop for TestServer { // } // } -/// Test `HttpRequest` builder +/// Test `Request` builder /// -/// ```rust +/// ```rust,ignore /// # extern crate http; /// # extern crate actix_web; /// # use http::{header, StatusCode}; /// # use actix_web::*; /// use actix_web::test::TestRequest; /// -/// fn index(req: &HttpRequest) -> HttpResponse { +/// fn index(req: &HttpRequest) -> Response { /// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { -/// HttpResponse::Ok().into() +/// Response::Ok().into() /// } else { -/// HttpResponse::BadRequest().into() +/// Response::BadRequest().into() /// } /// } /// @@ -403,7 +403,7 @@ impl TestRequest { // /// This method generates `HttpRequest` instance and runs handler // /// with generated request. - // pub fn run>(self, h: &H) -> Result { + // pub fn run>(self, h: &H) -> Result { // let req = self.finish(); // let resp = h.handle(&req); @@ -424,7 +424,7 @@ impl TestRequest { // /// with generated request. // /// // /// This method panics is handler returns actor. - // pub fn run_async(self, h: H) -> Result + // pub fn run_async(self, h: H) -> Result // where // H: Fn(HttpRequest) -> F + 'static, // F: Future + 'static, @@ -467,7 +467,7 @@ impl TestRequest { // } // /// This method generates `HttpRequest` instance and executes handler - // pub fn execute(self, f: F) -> Result + // pub fn execute(self, f: F) -> Result // where // F: FnOnce(&HttpRequest) -> R, // R: Responder + 'static, diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 6bb84c189..61fad543c 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -10,9 +10,9 @@ use http::{header, Method, StatusCode}; use body::Binary; use error::{PayloadError, ResponseError}; -use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder}; use payload::PayloadBuffer; use request::Request; +use response::{ConnectionType, Response, ResponseBuilder}; mod frame; mod mask; @@ -85,26 +85,26 @@ pub enum HandshakeError { } impl ResponseError for HandshakeError { - fn error_response(&self) -> HttpResponse { + fn error_response(&self) -> Response { match *self { - HandshakeError::GetMethodRequired => HttpResponse::MethodNotAllowed() + HandshakeError::GetMethodRequired => Response::MethodNotAllowed() .header(header::ALLOW, "GET") .finish(), - HandshakeError::NoWebsocketUpgrade => HttpResponse::BadRequest() + HandshakeError::NoWebsocketUpgrade => Response::BadRequest() .reason("No WebSocket UPGRADE header found") .finish(), - HandshakeError::NoConnectionUpgrade => HttpResponse::BadRequest() + HandshakeError::NoConnectionUpgrade => Response::BadRequest() .reason("No CONNECTION upgrade") .finish(), - HandshakeError::NoVersionHeader => HttpResponse::BadRequest() + HandshakeError::NoVersionHeader => Response::BadRequest() .reason("Websocket version header is required") .finish(), - HandshakeError::UnsupportedVersion => HttpResponse::BadRequest() + HandshakeError::UnsupportedVersion => Response::BadRequest() .reason("Unsupported version") .finish(), - HandshakeError::BadWebsocketKey => HttpResponse::BadRequest() - .reason("Handshake error") - .finish(), + HandshakeError::BadWebsocketKey => { + Response::BadRequest().reason("Handshake error").finish() + } } } } @@ -126,13 +126,13 @@ pub enum Message { /// Prepare `WebSocket` handshake response. /// -/// This function returns handshake `HttpResponse`, ready to send to peer. +/// This function returns handshake `Response`, ready to send to peer. /// It does not perform any IO. /// // /// `protocols` is a sequence of known protocols. On successful handshake, // /// the returned response headers contain the first protocol in this list // /// which the server also knows. -pub fn handshake(req: &Request) -> Result { +pub fn handshake(req: &Request) -> Result { // WebSocket accepts only GET if *req.method() != Method::GET { return Err(HandshakeError::GetMethodRequired); @@ -181,7 +181,7 @@ pub fn handshake(req: &Request) -> Result { proto::hash_key(key.as_ref()) }; - Ok(HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS) + Ok(Response::build(StatusCode::SWITCHING_PROTOCOLS) .connection_type(ConnectionType::Upgrade) .header(header::UPGRADE, "websocket") .header(header::TRANSFER_ENCODING, "chunked") @@ -280,20 +280,6 @@ where } } -/// Common writing methods for a websocket. -pub trait WsWriter { - /// Send a text - fn send_text>(&mut self, text: T); - /// Send a binary - fn send_binary>(&mut self, data: B); - /// Send a ping message - fn send_ping(&mut self, message: &str); - /// Send a pong message - fn send_pong(&mut self, message: &str); - /// Close the connection - fn send_close(&mut self, reason: Option); -} - #[cfg(test)] mod tests { use super::*; @@ -399,17 +385,17 @@ mod tests { #[test] fn test_wserror_http_response() { - let resp: HttpResponse = HandshakeError::GetMethodRequired.error_response(); + let resp: Response = HandshakeError::GetMethodRequired.error_response(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let resp: HttpResponse = HandshakeError::NoWebsocketUpgrade.error_response(); + let resp: Response = HandshakeError::NoWebsocketUpgrade.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = HandshakeError::NoConnectionUpgrade.error_response(); + let resp: Response = HandshakeError::NoConnectionUpgrade.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = HandshakeError::NoVersionHeader.error_response(); + let resp: Response = HandshakeError::NoVersionHeader.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = HandshakeError::UnsupportedVersion.error_response(); + let resp: Response = HandshakeError::UnsupportedVersion.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = HandshakeError::BadWebsocketKey.error_response(); + let resp: Response = HandshakeError::BadWebsocketKey.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } } diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs index bb9430659..1866f29b4 100644 --- a/tests/test_h1v2.rs +++ b/tests/test_h1v2.rs @@ -11,7 +11,7 @@ use actix_net::server::Server; use actix_web::{client, test}; use futures::future; -use actix_http::{h1, Error, HttpResponse, KeepAlive, ServiceConfig}; +use actix_http::{h1, Error, KeepAlive, Response, ServiceConfig}; #[test] fn test_h1_v2() { @@ -29,7 +29,7 @@ fn test_h1_v2() { h1::H1Service::new(settings, |req| { println!("REQ: {:?}", req); - future::ok::<_, Error>(HttpResponse::Ok().finish()) + future::ok::<_, Error>(Response::Ok().finish()) }) }).unwrap() .run(); From 5c0a2066cc901c136062121345684332a2e98ac3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 12:47:22 -0700 Subject: [PATCH 017/208] refactor ws to a websocket codec --- src/body.rs | 2 - src/ws/codec.rs | 119 +++++++++++++++ src/ws/frame.rs | 382 ++++++++++++++---------------------------------- src/ws/mod.rs | 133 ++--------------- 4 files changed, 238 insertions(+), 398 deletions(-) create mode 100644 src/ws/codec.rs diff --git a/src/body.rs b/src/body.rs index db06bef22..c10b067a2 100644 --- a/src/body.rs +++ b/src/body.rs @@ -17,8 +17,6 @@ pub enum Body { /// Unspecified streaming response. Developer is responsible for setting /// right `Content-Length` or `Transfer-Encoding` headers. Streaming(BodyStream), - // /// Special body type for actor response. - // Actor(Box), } /// Represents various types of binary body. diff --git a/src/ws/codec.rs b/src/ws/codec.rs new file mode 100644 index 000000000..6e2b12090 --- /dev/null +++ b/src/ws/codec.rs @@ -0,0 +1,119 @@ +use bytes::BytesMut; +use tokio_codec::{Decoder, Encoder}; + +use super::frame::Frame; +use super::proto::{CloseReason, OpCode}; +use super::ProtocolError; +use body::Binary; + +/// `WebSocket` Message +#[derive(Debug, PartialEq)] +pub enum Message { + /// Text message + Text(String), + /// Binary message + Binary(Binary), + /// Ping message + Ping(String), + /// Pong message + Pong(String), + /// Close message with optional reason + Close(Option), +} + +/// WebSockets protocol codec +pub struct Codec { + max_size: usize, + server: bool, +} + +impl Codec { + /// Create new websocket frames decoder + pub fn new() -> Codec { + Codec { + max_size: 65_536, + server: true, + } + } + + /// Set max frame size + /// + /// By default max size is set to 64kb + pub fn max_size(mut self, size: usize) -> Self { + self.max_size = size; + self + } + + /// Set decoder to client mode. + /// + /// By default decoder works in server mode. + pub fn client_mode(mut self) -> Self { + self.server = false; + self + } +} + +impl Encoder for Codec { + type Item = Message; + type Error = ProtocolError; + + fn encode(&mut self, item: Message, dst: &mut BytesMut) -> Result<(), Self::Error> { + match item { + Message::Text(txt) => { + Frame::write_message(dst, txt, OpCode::Text, true, !self.server) + } + Message::Binary(bin) => { + Frame::write_message(dst, bin, OpCode::Binary, true, !self.server) + } + Message::Ping(txt) => { + Frame::write_message(dst, txt, OpCode::Ping, true, !self.server) + } + Message::Pong(txt) => { + Frame::write_message(dst, txt, OpCode::Pong, true, !self.server) + } + Message::Close(reason) => Frame::write_close(dst, reason, !self.server), + } + Ok(()) + } +} + +impl Decoder for Codec { + type Item = Message; + type Error = ProtocolError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + match Frame::parse(src, self.server, self.max_size) { + Ok(Some((finished, opcode, payload))) => { + // continuation is not supported + if !finished { + return Err(ProtocolError::NoContinuation); + } + + match opcode { + OpCode::Continue => Err(ProtocolError::NoContinuation), + OpCode::Bad => Err(ProtocolError::BadOpCode), + OpCode::Close => { + let close_reason = Frame::parse_close_payload(&payload); + Ok(Some(Message::Close(close_reason))) + } + OpCode::Ping => Ok(Some(Message::Ping( + String::from_utf8_lossy(payload.as_ref()).into(), + ))), + OpCode::Pong => Ok(Some(Message::Pong( + String::from_utf8_lossy(payload.as_ref()).into(), + ))), + OpCode::Binary => Ok(Some(Message::Binary(payload))), + OpCode::Text => { + let tmp = Vec::from(payload.as_ref()); + match String::from_utf8(tmp) { + Ok(s) => Ok(Some(Message::Text(s))), + Err(_) => Err(ProtocolError::BadEncoding), + } + } + } + } + Ok(None) => Ok(None), + Err(e) => Err(e), + } + } +} diff --git a/src/ws/frame.rs b/src/ws/frame.rs index d5fa98272..38bebc283 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -1,144 +1,29 @@ use byteorder::{ByteOrder, LittleEndian, NetworkEndian}; -use bytes::{BufMut, Bytes, BytesMut}; -use futures::{Async, Poll, Stream}; +use bytes::{BufMut, BytesMut}; use rand; -use std::fmt; use body::Binary; -use error::PayloadError; -use payload::PayloadBuffer; - use ws::mask::apply_mask; use ws::proto::{CloseCode, CloseReason, OpCode}; use ws::ProtocolError; /// A struct representing a `WebSocket` frame. #[derive(Debug)] -pub struct Frame { - finished: bool, - opcode: OpCode, - payload: Binary, -} +pub struct Frame; impl Frame { - /// Destruct frame - pub fn unpack(self) -> (bool, OpCode, Binary) { - (self.finished, self.opcode, self.payload) - } - - /// Create a new Close control frame. - #[inline] - pub fn close(reason: Option, genmask: bool) -> FramedMessage { - let payload = match reason { - None => Vec::new(), - Some(reason) => { - let mut code_bytes = [0; 2]; - NetworkEndian::write_u16(&mut code_bytes, reason.code.into()); - - let mut payload = Vec::from(&code_bytes[..]); - if let Some(description) = reason.description { - payload.extend(description.as_bytes()); - } - payload - } - }; - - Frame::message(payload, OpCode::Close, true, genmask) - } - - #[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))] - fn read_copy_md( - pl: &mut PayloadBuffer, server: bool, max_size: usize, - ) -> Poll)>, ProtocolError> - where - S: Stream, - { - let mut idx = 2; - let buf = match pl.copy(2)? { - Async::Ready(Some(buf)) => buf, - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::NotReady => return Ok(Async::NotReady), - }; - let first = buf[0]; - let second = buf[1]; - let finished = first & 0x80 != 0; - - // check masking - let masked = second & 0x80 != 0; - if !masked && server { - return Err(ProtocolError::UnmaskedFrame); - } else if masked && !server { - return Err(ProtocolError::MaskedFrame); - } - - // Op code - let opcode = OpCode::from(first & 0x0F); - - if let OpCode::Bad = opcode { - return Err(ProtocolError::InvalidOpcode(first & 0x0F)); - } - - let len = second & 0x7F; - let length = if len == 126 { - let buf = match pl.copy(4)? { - Async::Ready(Some(buf)) => buf, - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::NotReady => return Ok(Async::NotReady), - }; - let len = NetworkEndian::read_uint(&buf[idx..], 2) as usize; - idx += 2; - len - } else if len == 127 { - let buf = match pl.copy(10)? { - Async::Ready(Some(buf)) => buf, - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::NotReady => return Ok(Async::NotReady), - }; - let len = NetworkEndian::read_uint(&buf[idx..], 8); - if len > max_size as u64 { - return Err(ProtocolError::Overflow); - } - idx += 8; - len as usize - } else { - len as usize - }; - - // check for max allowed size - if length > max_size { - return Err(ProtocolError::Overflow); - } - - let mask = if server { - let buf = match pl.copy(idx + 4)? { - Async::Ready(Some(buf)) => buf, - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::NotReady => return Ok(Async::NotReady), - }; - - let mask: &[u8] = &buf[idx..idx + 4]; - let mask_u32 = LittleEndian::read_u32(mask); - idx += 4; - Some(mask_u32) - } else { - None - }; - - Ok(Async::Ready(Some((idx, finished, opcode, length, mask)))) - } - - fn read_chunk_md( - chunk: &[u8], server: bool, max_size: usize, - ) -> Poll<(usize, bool, OpCode, usize, Option), ProtocolError> { - let chunk_len = chunk.len(); + fn parse_metadata( + src: &[u8], server: bool, max_size: usize, + ) -> Result)>, ProtocolError> { + let chunk_len = src.len(); let mut idx = 2; if chunk_len < 2 { - return Ok(Async::NotReady); + return Ok(None); } - let first = chunk[0]; - let second = chunk[1]; + let first = src[0]; + let second = src[1]; let finished = first & 0x80 != 0; // check masking @@ -159,16 +44,16 @@ impl Frame { let len = second & 0x7F; let length = if len == 126 { if chunk_len < 4 { - return Ok(Async::NotReady); + return Ok(None); } - let len = NetworkEndian::read_uint(&chunk[idx..], 2) as usize; + let len = NetworkEndian::read_uint(&src[idx..], 2) as usize; idx += 2; len } else if len == 127 { if chunk_len < 10 { - return Ok(Async::NotReady); + return Ok(None); } - let len = NetworkEndian::read_uint(&chunk[idx..], 8); + let len = NetworkEndian::read_uint(&src[idx..], 8); if len > max_size as u64 { return Err(ProtocolError::Overflow); } @@ -185,10 +70,10 @@ impl Frame { let mask = if server { if chunk_len < idx + 4 { - return Ok(Async::NotReady); + return Ok(None); } - let mask: &[u8] = &chunk[idx..idx + 4]; + let mask: &[u8] = &src[idx..idx + 4]; let mask_u32 = LittleEndian::read_u32(mask); idx += 4; Some(mask_u32) @@ -196,56 +81,34 @@ impl Frame { None }; - Ok(Async::Ready((idx, finished, opcode, length, mask))) + Ok(Some((idx, finished, opcode, length, mask))) } /// Parse the input stream into a frame. - pub fn parse( - pl: &mut PayloadBuffer, server: bool, max_size: usize, - ) -> Poll, ProtocolError> - where - S: Stream, - { - // try to parse ws frame md from one chunk - let result = match pl.get_chunk()? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::Ready(Some(chunk)) => Frame::read_chunk_md(chunk, server, max_size)?, - }; + pub fn parse( + src: &mut BytesMut, server: bool, max_size: usize, + ) -> Result, ProtocolError> { + // try to parse ws frame metadata + let (idx, finished, opcode, length, mask) = + match Frame::parse_metadata(src, server, max_size)? { + None => return Ok(None), + Some(res) => res, + }; - let (idx, finished, opcode, length, mask) = match result { - // we may need to join several chunks - Async::NotReady => match Frame::read_copy_md(pl, server, max_size)? { - Async::Ready(Some(item)) => item, - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(None) => return Ok(Async::Ready(None)), - }, - Async::Ready(item) => item, - }; - - match pl.can_read(idx + length)? { - Async::Ready(Some(true)) => (), - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::Ready(Some(false)) | Async::NotReady => return Ok(Async::NotReady), + // not enough data + if src.len() < idx + length { + return Ok(None); } // remove prefix - pl.drop_bytes(idx); + src.split_to(idx); // no need for body if length == 0 { - return Ok(Async::Ready(Some(Frame { - finished, - opcode, - payload: Binary::from(""), - }))); + return Ok(Some((finished, opcode, Binary::from("")))); } - let data = match pl.read_exact(length)? { - Async::Ready(Some(buf)) => buf, - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::NotReady => panic!(), - }; + let mut data = src.split_to(length); // control frames must have length <= 125 match opcode { @@ -254,26 +117,17 @@ impl Frame { } OpCode::Close if length > 125 => { debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); - return Ok(Async::Ready(Some(Frame::default()))); + return Ok(Some((true, OpCode::Close, Binary::from("")))); } _ => (), } // unmask - let data = if let Some(mask) = mask { - let mut buf = BytesMut::new(); - buf.extend_from_slice(&data); - apply_mask(&mut buf, mask); - buf.freeze() - } else { - data - }; + if let Some(mask) = mask { + apply_mask(&mut data, mask); + } - Ok(Async::Ready(Some(Frame { - finished, - opcode, - payload: data.into(), - }))) + Ok(Some((finished, opcode, data.into()))) } /// Parse the payload of a close frame. @@ -293,120 +147,101 @@ impl Frame { } /// Generate binary representation - pub fn message>( - data: B, code: OpCode, finished: bool, genmask: bool, - ) -> FramedMessage { - let payload = data.into(); - let one: u8 = if finished { - 0x80 | Into::::into(code) + pub fn write_message>( + dst: &mut BytesMut, pl: B, op: OpCode, fin: bool, mask: bool, + ) { + let payload = pl.into(); + let one: u8 = if fin { + 0x80 | Into::::into(op) } else { - code.into() + op.into() }; let payload_len = payload.len(); - let (two, p_len) = if genmask { + let (two, p_len) = if mask { (0x80, payload_len + 4) } else { (0, payload_len) }; - let mut buf = if payload_len < 126 { - let mut buf = BytesMut::with_capacity(p_len + 2); - buf.put_slice(&[one, two | payload_len as u8]); - buf + if payload_len < 126 { + dst.put_slice(&[one, two | payload_len as u8]); } else if payload_len <= 65_535 { - let mut buf = BytesMut::with_capacity(p_len + 4); - buf.put_slice(&[one, two | 126]); - buf.put_u16_be(payload_len as u16); - buf + dst.reserve(p_len + 4); + dst.put_slice(&[one, two | 126]); + dst.put_u16_be(payload_len as u16); } else { - let mut buf = BytesMut::with_capacity(p_len + 10); - buf.put_slice(&[one, two | 127]); - buf.put_u64_be(payload_len as u64); - buf + dst.reserve(p_len + 10); + dst.put_slice(&[one, two | 127]); + dst.put_u64_be(payload_len as u64); }; - let binary = if genmask { + if mask { let mask = rand::random::(); - buf.put_u32_le(mask); - buf.extend_from_slice(payload.as_ref()); - let pos = buf.len() - payload_len; - apply_mask(&mut buf[pos..], mask); - buf.into() + dst.put_u32_le(mask); + dst.extend_from_slice(payload.as_ref()); + let pos = dst.len() - payload_len; + apply_mask(&mut dst[pos..], mask); } else { - buf.put_slice(payload.as_ref()); - buf.into() - }; - - FramedMessage(binary) - } -} - -impl Default for Frame { - fn default() -> Frame { - Frame { - finished: true, - opcode: OpCode::Close, - payload: Binary::from(&b""[..]), + dst.put_slice(payload.as_ref()); } } -} -impl fmt::Display for Frame { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - " - - final: {} - opcode: {} - payload length: {} - payload: 0x{} -", - self.finished, - self.opcode, - self.payload.len(), - self.payload - .as_ref() - .iter() - .map(|byte| format!("{:x}", byte)) - .collect::() - ) + /// Create a new Close control frame. + #[inline] + pub fn write_close(dst: &mut BytesMut, reason: Option, mask: bool) { + let payload = match reason { + None => Vec::new(), + Some(reason) => { + let mut code_bytes = [0; 2]; + NetworkEndian::write_u16(&mut code_bytes, reason.code.into()); + + let mut payload = Vec::from(&code_bytes[..]); + if let Some(description) = reason.description { + payload.extend(description.as_bytes()); + } + payload + } + }; + + Frame::write_message(dst, payload, OpCode::Close, true, mask) } } -/// `WebSocket` message with framing. -#[derive(Debug)] -pub struct FramedMessage(pub(crate) Binary); - #[cfg(test)] mod tests { use super::*; - use futures::stream::once; - fn is_none(frm: &Poll, ProtocolError>) -> bool { + struct F { + finished: bool, + opcode: OpCode, + payload: Binary, + } + + fn is_none(frm: &Result, ProtocolError>) -> bool { match *frm { - Ok(Async::Ready(None)) => true, + Ok(None) => true, _ => false, } } - fn extract(frm: Poll, ProtocolError>) -> Frame { + fn extract(frm: Result, ProtocolError>) -> F { match frm { - Ok(Async::Ready(Some(frame))) => frame, + Ok(Some((finished, opcode, payload))) => F { + finished, + opcode, + payload, + }, _ => unreachable!("error"), } } #[test] fn test_parse() { - let mut buf = PayloadBuffer::new(once(Ok(BytesMut::from( - &[0b0000_0001u8, 0b0000_0001u8][..], - ).freeze()))); + let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); assert!(is_none(&Frame::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); buf.extend(b"1"); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); let frame = extract(Frame::parse(&mut buf, false, 1024)); assert!(!frame.finished); @@ -416,9 +251,7 @@ mod tests { #[test] fn test_parse_length0() { - let buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0000u8][..]); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); - + let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0000u8][..]); let frame = extract(Frame::parse(&mut buf, false, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); @@ -427,14 +260,12 @@ mod tests { #[test] fn test_parse_length2() { - let buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); + let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); assert!(is_none(&Frame::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); buf.extend(&[0u8, 4u8][..]); buf.extend(b"1234"); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); let frame = extract(Frame::parse(&mut buf, false, 1024)); assert!(!frame.finished); @@ -444,14 +275,12 @@ mod tests { #[test] fn test_parse_length4() { - let buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); + let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); assert!(is_none(&Frame::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]); buf.extend(b"1234"); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); let frame = extract(Frame::parse(&mut buf, false, 1024)); assert!(!frame.finished); @@ -464,7 +293,6 @@ mod tests { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b1000_0001u8][..]); buf.extend(b"0001"); buf.extend(b"1"); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); assert!(Frame::parse(&mut buf, false, 1024).is_err()); @@ -478,7 +306,6 @@ mod tests { fn test_parse_frame_no_mask() { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); buf.extend(&[1u8]); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); assert!(Frame::parse(&mut buf, true, 1024).is_err()); @@ -492,7 +319,6 @@ mod tests { fn test_parse_frame_max_size() { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0010u8][..]); buf.extend(&[1u8, 1u8]); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); assert!(Frame::parse(&mut buf, true, 1).is_err()); @@ -504,35 +330,39 @@ mod tests { #[test] fn test_ping_frame() { - let frame = Frame::message(Vec::from("data"), OpCode::Ping, true, false); + let mut buf = BytesMut::new(); + Frame::write_message(&mut buf, Vec::from("data"), OpCode::Ping, true, false); let mut v = vec![137u8, 4u8]; v.extend(b"data"); - assert_eq!(frame.0, v.into()); + assert_eq!(&buf[..], &v[..]); } #[test] fn test_pong_frame() { - let frame = Frame::message(Vec::from("data"), OpCode::Pong, true, false); + let mut buf = BytesMut::new(); + Frame::write_message(&mut buf, Vec::from("data"), OpCode::Pong, true, false); let mut v = vec![138u8, 4u8]; v.extend(b"data"); - assert_eq!(frame.0, v.into()); + assert_eq!(&buf[..], &v[..]); } #[test] fn test_close_frame() { + let mut buf = BytesMut::new(); let reason = (CloseCode::Normal, "data"); - let frame = Frame::close(Some(reason.into()), false); + Frame::write_close(&mut buf, Some(reason.into()), false); let mut v = vec![136u8, 6u8, 3u8, 232u8]; v.extend(b"data"); - assert_eq!(frame.0, v.into()); + assert_eq!(&buf[..], &v[..]); } #[test] fn test_empty_close_frame() { - let frame = Frame::close(None, false); - assert_eq!(frame.0, vec![0x88, 0x00].into()); + let mut buf = BytesMut::new(); + Frame::write_close(&mut buf, None, false); + assert_eq!(&buf[..], &vec![0x88, 0x00][..]); } } diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 61fad543c..e8bf3870d 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -1,24 +1,23 @@ -//! `WebSocket` support. +//! WebSocket protocol support. //! //! To setup a `WebSocket`, first do web socket handshake then on success //! convert `Payload` into a `WsStream` stream and then use `WsWriter` to //! communicate with the peer. //! ``` -use bytes::Bytes; -use futures::{Async, Poll, Stream}; -use http::{header, Method, StatusCode}; +use std::io; -use body::Binary; -use error::{PayloadError, ResponseError}; -use payload::PayloadBuffer; +use error::ResponseError; +use http::{header, Method, StatusCode}; use request::Request; use response::{ConnectionType, Response, ResponseBuilder}; +mod codec; mod frame; mod mask; mod proto; -pub use self::frame::{Frame, FramedMessage}; +pub use self::codec::Message; +pub use self::frame::Frame; pub use self::proto::{CloseCode, CloseReason, OpCode}; /// Websocket protocol errors @@ -48,16 +47,16 @@ pub enum ProtocolError { /// Bad utf-8 encoding #[fail(display = "Bad utf-8 encoding.")] BadEncoding, - /// Payload error - #[fail(display = "Payload error: {}", _0)] - Payload(#[cause] PayloadError), + /// Io error + #[fail(display = "io error: {}", _0)] + Io(#[cause] io::Error), } impl ResponseError for ProtocolError {} -impl From for ProtocolError { - fn from(err: PayloadError) -> ProtocolError { - ProtocolError::Payload(err) +impl From for ProtocolError { + fn from(err: io::Error) -> ProtocolError { + ProtocolError::Io(err) } } @@ -109,21 +108,6 @@ impl ResponseError for HandshakeError { } } -/// `WebSocket` Message -#[derive(Debug, PartialEq)] -pub enum Message { - /// Text message - Text(String), - /// Binary message - Binary(Binary), - /// Ping message - Ping(String), - /// Pong message - Pong(String), - /// Close message with optional reason - Close(Option), -} - /// Prepare `WebSocket` handshake response. /// /// This function returns handshake `Response`, ready to send to peer. @@ -189,97 +173,6 @@ pub fn handshake(req: &Request) -> Result { .take()) } -/// Maps `Payload` stream into stream of `ws::Message` items -pub struct WsStream { - rx: PayloadBuffer, - closed: bool, - max_size: usize, -} - -impl WsStream -where - S: Stream, -{ - /// Create new websocket frames stream - pub fn new(stream: S) -> WsStream { - WsStream { - rx: PayloadBuffer::new(stream), - closed: false, - max_size: 65_536, - } - } - - /// Set max frame size - /// - /// By default max size is set to 64kb - pub fn max_size(mut self, size: usize) -> Self { - self.max_size = size; - self - } -} - -impl Stream for WsStream -where - S: Stream, -{ - type Item = Message; - type Error = ProtocolError; - - fn poll(&mut self) -> Poll, Self::Error> { - if self.closed { - return Ok(Async::Ready(None)); - } - - match Frame::parse(&mut self.rx, true, self.max_size) { - Ok(Async::Ready(Some(frame))) => { - let (finished, opcode, payload) = frame.unpack(); - - // continuation is not supported - if !finished { - self.closed = true; - return Err(ProtocolError::NoContinuation); - } - - match opcode { - OpCode::Continue => Err(ProtocolError::NoContinuation), - OpCode::Bad => { - self.closed = true; - Err(ProtocolError::BadOpCode) - } - OpCode::Close => { - self.closed = true; - let close_reason = Frame::parse_close_payload(&payload); - Ok(Async::Ready(Some(Message::Close(close_reason)))) - } - OpCode::Ping => Ok(Async::Ready(Some(Message::Ping( - String::from_utf8_lossy(payload.as_ref()).into(), - )))), - OpCode::Pong => Ok(Async::Ready(Some(Message::Pong( - String::from_utf8_lossy(payload.as_ref()).into(), - )))), - OpCode::Binary => Ok(Async::Ready(Some(Message::Binary(payload)))), - OpCode::Text => { - let tmp = Vec::from(payload.as_ref()); - match String::from_utf8(tmp) { - Ok(s) => Ok(Async::Ready(Some(Message::Text(s)))), - Err(_) => { - self.closed = true; - Err(ProtocolError::BadEncoding) - } - } - } - } - } - Ok(Async::Ready(None)) => Ok(Async::Ready(None)), - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(e) => { - self.closed = true; - Err(e) - } - } - } -} - #[cfg(test)] mod tests { use super::*; From 7e135b798b1f20368b62a9d2f7f03dbbc361bffe Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 14:30:40 -0700 Subject: [PATCH 018/208] add websocket transport and test --- src/framed/framed.rs | 283 ------------------------------------- src/framed/framed_read.rs | 216 ---------------------------- src/framed/framed_write.rs | 243 ------------------------------- src/framed/mod.rs | 32 ----- src/h1/dispatcher.rs | 4 +- src/lib.rs | 3 - src/ws/mod.rs | 4 +- src/ws/transport.rs | 50 +++++++ tests/test_ws.rs | 111 +++++++++++++++ 9 files changed, 165 insertions(+), 781 deletions(-) delete mode 100644 src/framed/framed.rs delete mode 100644 src/framed/framed_read.rs delete mode 100644 src/framed/framed_write.rs delete mode 100644 src/framed/mod.rs create mode 100644 src/ws/transport.rs create mode 100644 tests/test_ws.rs diff --git a/src/framed/framed.rs b/src/framed/framed.rs deleted file mode 100644 index f6295d98f..000000000 --- a/src/framed/framed.rs +++ /dev/null @@ -1,283 +0,0 @@ -#![allow(deprecated)] - -use std::fmt; -use std::io::{self, Read, Write}; - -use bytes::BytesMut; -use futures::{Poll, Sink, StartSend, Stream}; -use tokio_codec::{Decoder, Encoder}; -use tokio_io::{AsyncRead, AsyncWrite}; - -use super::framed_read::{framed_read2, framed_read2_with_buffer, FramedRead2}; -use super::framed_write::{framed_write2, framed_write2_with_buffer, FramedWrite2}; - -/// A unified `Stream` and `Sink` interface to an underlying I/O object, using -/// the `Encoder` and `Decoder` traits to encode and decode frames. -/// -/// You can create a `Framed` instance by using the `AsyncRead::framed` adapter. -pub struct Framed { - inner: FramedRead2>>, -} - -pub struct Fuse(pub T, pub U); - -impl Framed -where - T: AsyncRead + AsyncWrite, - U: Decoder + Encoder, -{ - /// Provides a `Stream` and `Sink` interface for reading and writing to this - /// `Io` object, using `Decode` and `Encode` to read and write the raw data. - /// - /// Raw I/O objects work with byte sequences, but higher-level code usually - /// wants to batch these into meaningful chunks, called "frames". This - /// method layers framing on top of an I/O object, by using the `Codec` - /// traits to handle encoding and decoding of messages frames. Note that - /// the incoming and outgoing frame types may be distinct. - /// - /// This function returns a *single* object that is both `Stream` and - /// `Sink`; grouping this into a single object is often useful for layering - /// things like gzip or TLS, which require both read and write access to the - /// underlying object. - /// - /// If you want to work more directly with the streams and sink, consider - /// calling `split` on the `Framed` returned by this method, which will - /// break them into separate objects, allowing them to interact more easily. - pub fn new(inner: T, codec: U) -> Framed { - Framed { - inner: framed_read2(framed_write2(Fuse(inner, codec))), - } - } -} - -impl Framed { - /// Provides a `Stream` and `Sink` interface for reading and writing to this - /// `Io` object, using `Decode` and `Encode` to read and write the raw data. - /// - /// Raw I/O objects work with byte sequences, but higher-level code usually - /// wants to batch these into meaningful chunks, called "frames". This - /// method layers framing on top of an I/O object, by using the `Codec` - /// traits to handle encoding and decoding of messages frames. Note that - /// the incoming and outgoing frame types may be distinct. - /// - /// This function returns a *single* object that is both `Stream` and - /// `Sink`; grouping this into a single object is often useful for layering - /// things like gzip or TLS, which require both read and write access to the - /// underlying object. - /// - /// This objects takes a stream and a readbuffer and a writebuffer. These field - /// can be obtained from an existing `Framed` with the `into_parts` method. - /// - /// If you want to work more directly with the streams and sink, consider - /// calling `split` on the `Framed` returned by this method, which will - /// break them into separate objects, allowing them to interact more easily. - pub fn from_parts(parts: FramedParts) -> Framed { - Framed { - inner: framed_read2_with_buffer( - framed_write2_with_buffer(Fuse(parts.io, parts.codec), parts.write_buf), - parts.read_buf, - ), - } - } - - /// Returns a reference to the underlying codec. - pub fn get_codec(&self) -> &U { - &self.inner.get_ref().get_ref().1 - } - - /// Returns a mutable reference to the underlying codec. - pub fn get_codec_mut(&mut self) -> &mut U { - &mut self.inner.get_mut().get_mut().1 - } - - /// Returns a reference to the underlying I/O stream wrapped by - /// `Frame`. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn get_ref(&self) -> &T { - &self.inner.get_ref().get_ref().0 - } - - /// Returns a mutable reference to the underlying I/O stream wrapped by - /// `Frame`. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn get_mut(&mut self) -> &mut T { - &mut self.inner.get_mut().get_mut().0 - } - - /// Consumes the `Frame`, returning its underlying I/O stream. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn into_inner(self) -> T { - self.inner.into_inner().into_inner().0 - } - - /// Consumes the `Frame`, returning its underlying I/O stream, the buffer - /// with unprocessed data, and the codec. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn into_parts(self) -> FramedParts { - let (inner, read_buf) = self.inner.into_parts(); - let (inner, write_buf) = inner.into_parts(); - - FramedParts { - io: inner.0, - codec: inner.1, - read_buf: read_buf, - write_buf: write_buf, - _priv: (), - } - } -} - -impl Stream for Framed -where - T: AsyncRead, - U: Decoder, -{ - type Item = U::Item; - type Error = U::Error; - - fn poll(&mut self) -> Poll, Self::Error> { - self.inner.poll() - } -} - -impl Sink for Framed -where - T: AsyncWrite, - U: Encoder, - U::Error: From, -{ - type SinkItem = U::Item; - type SinkError = U::Error; - - fn start_send( - &mut self, item: Self::SinkItem, - ) -> StartSend { - self.inner.get_mut().start_send(item) - } - - fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { - self.inner.get_mut().poll_complete() - } - - fn close(&mut self) -> Poll<(), Self::SinkError> { - self.inner.get_mut().close() - } -} - -impl fmt::Debug for Framed -where - T: fmt::Debug, - U: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Framed") - .field("io", &self.inner.get_ref().get_ref().0) - .field("codec", &self.inner.get_ref().get_ref().1) - .finish() - } -} - -// ===== impl Fuse ===== - -impl Read for Fuse { - fn read(&mut self, dst: &mut [u8]) -> io::Result { - self.0.read(dst) - } -} - -impl AsyncRead for Fuse { - unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { - self.0.prepare_uninitialized_buffer(buf) - } -} - -impl Write for Fuse { - fn write(&mut self, src: &[u8]) -> io::Result { - self.0.write(src) - } - - fn flush(&mut self) -> io::Result<()> { - self.0.flush() - } -} - -impl AsyncWrite for Fuse { - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.0.shutdown() - } -} - -impl Decoder for Fuse { - type Item = U::Item; - type Error = U::Error; - - fn decode( - &mut self, buffer: &mut BytesMut, - ) -> Result, Self::Error> { - self.1.decode(buffer) - } - - fn decode_eof( - &mut self, buffer: &mut BytesMut, - ) -> Result, Self::Error> { - self.1.decode_eof(buffer) - } -} - -impl Encoder for Fuse { - type Item = U::Item; - type Error = U::Error; - - fn encode( - &mut self, item: Self::Item, dst: &mut BytesMut, - ) -> Result<(), Self::Error> { - self.1.encode(item, dst) - } -} - -/// `FramedParts` contains an export of the data of a Framed transport. -/// It can be used to construct a new `Framed` with a different codec. -/// It contains all current buffers and the inner transport. -#[derive(Debug)] -pub struct FramedParts { - /// The inner transport used to read bytes to and write bytes to - pub io: T, - - /// The codec - pub codec: U, - - /// The buffer with read but unprocessed data. - pub read_buf: BytesMut, - - /// A buffer with unprocessed data which are not written yet. - pub write_buf: BytesMut, - - /// This private field allows us to add additional fields in the future in a - /// backwards compatible way. - _priv: (), -} - -impl FramedParts { - /// Create a new, default, `FramedParts` - pub fn new(io: T, codec: U) -> FramedParts { - FramedParts { - io, - codec, - read_buf: BytesMut::new(), - write_buf: BytesMut::new(), - _priv: (), - } - } -} diff --git a/src/framed/framed_read.rs b/src/framed/framed_read.rs deleted file mode 100644 index 065e29205..000000000 --- a/src/framed/framed_read.rs +++ /dev/null @@ -1,216 +0,0 @@ -use std::fmt; - -use bytes::BytesMut; -use futures::{Async, Poll, Sink, StartSend, Stream}; -use tokio_codec::Decoder; -use tokio_io::AsyncRead; - -use super::framed::Fuse; - -/// A `Stream` of messages decoded from an `AsyncRead`. -pub struct FramedRead { - inner: FramedRead2>, -} - -pub struct FramedRead2 { - inner: T, - eof: bool, - is_readable: bool, - buffer: BytesMut, -} - -const INITIAL_CAPACITY: usize = 8 * 1024; - -// ===== impl FramedRead ===== - -impl FramedRead -where - T: AsyncRead, - D: Decoder, -{ - /// Creates a new `FramedRead` with the given `decoder`. - pub fn new(inner: T, decoder: D) -> FramedRead { - FramedRead { - inner: framed_read2(Fuse(inner, decoder)), - } - } -} - -impl FramedRead { - /// Returns a reference to the underlying I/O stream wrapped by - /// `FramedRead`. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn get_ref(&self) -> &T { - &self.inner.inner.0 - } - - /// Returns a mutable reference to the underlying I/O stream wrapped by - /// `FramedRead`. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn get_mut(&mut self) -> &mut T { - &mut self.inner.inner.0 - } - - /// Consumes the `FramedRead`, returning its underlying I/O stream. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn into_inner(self) -> T { - self.inner.inner.0 - } - - /// Returns a reference to the underlying decoder. - pub fn decoder(&self) -> &D { - &self.inner.inner.1 - } - - /// Returns a mutable reference to the underlying decoder. - pub fn decoder_mut(&mut self) -> &mut D { - &mut self.inner.inner.1 - } -} - -impl Stream for FramedRead -where - T: AsyncRead, - D: Decoder, -{ - type Item = D::Item; - type Error = D::Error; - - fn poll(&mut self) -> Poll, Self::Error> { - self.inner.poll() - } -} - -impl Sink for FramedRead -where - T: Sink, -{ - type SinkItem = T::SinkItem; - type SinkError = T::SinkError; - - fn start_send( - &mut self, item: Self::SinkItem, - ) -> StartSend { - self.inner.inner.0.start_send(item) - } - - fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { - self.inner.inner.0.poll_complete() - } - - fn close(&mut self) -> Poll<(), Self::SinkError> { - self.inner.inner.0.close() - } -} - -impl fmt::Debug for FramedRead -where - T: fmt::Debug, - D: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("FramedRead") - .field("inner", &self.inner.inner.0) - .field("decoder", &self.inner.inner.1) - .field("eof", &self.inner.eof) - .field("is_readable", &self.inner.is_readable) - .field("buffer", &self.inner.buffer) - .finish() - } -} - -// ===== impl FramedRead2 ===== - -pub fn framed_read2(inner: T) -> FramedRead2 { - FramedRead2 { - inner: inner, - eof: false, - is_readable: false, - buffer: BytesMut::with_capacity(INITIAL_CAPACITY), - } -} - -pub fn framed_read2_with_buffer(inner: T, mut buf: BytesMut) -> FramedRead2 { - if buf.capacity() < INITIAL_CAPACITY { - let bytes_to_reserve = INITIAL_CAPACITY - buf.capacity(); - buf.reserve(bytes_to_reserve); - } - FramedRead2 { - inner: inner, - eof: false, - is_readable: buf.len() > 0, - buffer: buf, - } -} - -impl FramedRead2 { - pub fn get_ref(&self) -> &T { - &self.inner - } - - pub fn into_inner(self) -> T { - self.inner - } - - pub fn into_parts(self) -> (T, BytesMut) { - (self.inner, self.buffer) - } - - pub fn get_mut(&mut self) -> &mut T { - &mut self.inner - } -} - -impl Stream for FramedRead2 -where - T: AsyncRead + Decoder, -{ - type Item = T::Item; - type Error = T::Error; - - fn poll(&mut self) -> Poll, Self::Error> { - loop { - // Repeatedly call `decode` or `decode_eof` as long as it is - // "readable". Readable is defined as not having returned `None`. If - // the upstream has returned EOF, and the decoder is no longer - // readable, it can be assumed that the decoder will never become - // readable again, at which point the stream is terminated. - if self.is_readable { - if self.eof { - let frame = try!(self.inner.decode_eof(&mut self.buffer)); - return Ok(Async::Ready(frame)); - } - - trace!("attempting to decode a frame"); - - if let Some(frame) = try!(self.inner.decode(&mut self.buffer)) { - trace!("frame decoded from buffer"); - return Ok(Async::Ready(Some(frame))); - } - - self.is_readable = false; - } - - assert!(!self.eof); - - // Otherwise, try to read more data and try again. Make sure we've - // got room for at least one byte to read to ensure that we don't - // get a spurious 0 that looks like EOF - self.buffer.reserve(1); - if 0 == try_ready!(self.inner.read_buf(&mut self.buffer)) { - self.eof = true; - } - - self.is_readable = true; - } - } -} diff --git a/src/framed/framed_write.rs b/src/framed/framed_write.rs deleted file mode 100644 index 310c76307..000000000 --- a/src/framed/framed_write.rs +++ /dev/null @@ -1,243 +0,0 @@ -use std::fmt; -use std::io::{self, Read}; - -use bytes::BytesMut; -use futures::{Async, AsyncSink, Poll, Sink, StartSend, Stream}; -use tokio_codec::{Decoder, Encoder}; -use tokio_io::{AsyncRead, AsyncWrite}; - -use super::framed::Fuse; - -/// A `Sink` of frames encoded to an `AsyncWrite`. -pub struct FramedWrite { - inner: FramedWrite2>, -} - -pub struct FramedWrite2 { - inner: T, - buffer: BytesMut, -} - -const INITIAL_CAPACITY: usize = 8 * 1024; -const BACKPRESSURE_BOUNDARY: usize = INITIAL_CAPACITY; - -impl FramedWrite -where - T: AsyncWrite, - E: Encoder, -{ - /// Creates a new `FramedWrite` with the given `encoder`. - pub fn new(inner: T, encoder: E) -> FramedWrite { - FramedWrite { - inner: framed_write2(Fuse(inner, encoder)), - } - } -} - -impl FramedWrite { - /// Returns a reference to the underlying I/O stream wrapped by - /// `FramedWrite`. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn get_ref(&self) -> &T { - &self.inner.inner.0 - } - - /// Returns a mutable reference to the underlying I/O stream wrapped by - /// `FramedWrite`. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn get_mut(&mut self) -> &mut T { - &mut self.inner.inner.0 - } - - /// Consumes the `FramedWrite`, returning its underlying I/O stream. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn into_inner(self) -> T { - self.inner.inner.0 - } - - /// Returns a reference to the underlying decoder. - pub fn encoder(&self) -> &E { - &self.inner.inner.1 - } - - /// Returns a mutable reference to the underlying decoder. - pub fn encoder_mut(&mut self) -> &mut E { - &mut self.inner.inner.1 - } -} - -impl Sink for FramedWrite -where - T: AsyncWrite, - E: Encoder, -{ - type SinkItem = E::Item; - type SinkError = E::Error; - - fn start_send(&mut self, item: E::Item) -> StartSend { - self.inner.start_send(item) - } - - fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { - self.inner.poll_complete() - } - - fn close(&mut self) -> Poll<(), Self::SinkError> { - Ok(try!(self.inner.close())) - } -} - -impl Stream for FramedWrite -where - T: Stream, -{ - type Item = T::Item; - type Error = T::Error; - - fn poll(&mut self) -> Poll, Self::Error> { - self.inner.inner.0.poll() - } -} - -impl fmt::Debug for FramedWrite -where - T: fmt::Debug, - U: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("FramedWrite") - .field("inner", &self.inner.get_ref().0) - .field("encoder", &self.inner.get_ref().1) - .field("buffer", &self.inner.buffer) - .finish() - } -} - -// ===== impl FramedWrite2 ===== - -pub fn framed_write2(inner: T) -> FramedWrite2 { - FramedWrite2 { - inner: inner, - buffer: BytesMut::with_capacity(INITIAL_CAPACITY), - } -} - -pub fn framed_write2_with_buffer(inner: T, mut buf: BytesMut) -> FramedWrite2 { - if buf.capacity() < INITIAL_CAPACITY { - let bytes_to_reserve = INITIAL_CAPACITY - buf.capacity(); - buf.reserve(bytes_to_reserve); - } - FramedWrite2 { - inner: inner, - buffer: buf, - } -} - -impl FramedWrite2 { - pub fn get_ref(&self) -> &T { - &self.inner - } - - pub fn into_inner(self) -> T { - self.inner - } - - pub fn into_parts(self) -> (T, BytesMut) { - (self.inner, self.buffer) - } - - pub fn get_mut(&mut self) -> &mut T { - &mut self.inner - } -} - -impl Sink for FramedWrite2 -where - T: AsyncWrite + Encoder, -{ - type SinkItem = T::Item; - type SinkError = T::Error; - - fn start_send(&mut self, item: T::Item) -> StartSend { - // If the buffer is already over 8KiB, then attempt to flush it. If after flushing it's - // *still* over 8KiB, then apply backpressure (reject the send). - if self.buffer.len() >= BACKPRESSURE_BOUNDARY { - try!(self.poll_complete()); - - if self.buffer.len() >= BACKPRESSURE_BOUNDARY { - return Ok(AsyncSink::NotReady(item)); - } - } - - try!(self.inner.encode(item, &mut self.buffer)); - - Ok(AsyncSink::Ready) - } - - fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { - trace!("flushing framed transport"); - - while !self.buffer.is_empty() { - trace!("writing; remaining={}", self.buffer.len()); - - let n = try_ready!(self.inner.poll_write(&self.buffer)); - - if n == 0 { - return Err(io::Error::new( - io::ErrorKind::WriteZero, - "failed to \ - write frame to transport", - ).into()); - } - - // TODO: Add a way to `bytes` to do this w/o returning the drained - // data. - let _ = self.buffer.split_to(n); - } - - // Try flushing the underlying IO - try_ready!(self.inner.poll_flush()); - - trace!("framed transport flushed"); - return Ok(Async::Ready(())); - } - - fn close(&mut self) -> Poll<(), Self::SinkError> { - try_ready!(self.poll_complete()); - Ok(try!(self.inner.shutdown())) - } -} - -impl Decoder for FramedWrite2 { - type Item = T::Item; - type Error = T::Error; - - fn decode(&mut self, src: &mut BytesMut) -> Result, T::Error> { - self.inner.decode(src) - } - - fn decode_eof(&mut self, src: &mut BytesMut) -> Result, T::Error> { - self.inner.decode_eof(src) - } -} - -impl Read for FramedWrite2 { - fn read(&mut self, dst: &mut [u8]) -> io::Result { - self.inner.read(dst) - } -} - -impl AsyncRead for FramedWrite2 { - unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { - self.inner.prepare_uninitialized_buffer(buf) - } -} diff --git a/src/framed/mod.rs b/src/framed/mod.rs deleted file mode 100644 index cb0308fa0..000000000 --- a/src/framed/mod.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Utilities for encoding and decoding frames. -//! -//! Contains adapters to go from streams of bytes, [`AsyncRead`] and -//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`]. -//! Framed streams are also known as [transports]. -//! -//! [`AsyncRead`]: # -//! [`AsyncWrite`]: # -//! [`Sink`]: # -//! [`Stream`]: # -//! [transports]: # - -#![deny(missing_docs, missing_debug_implementations, warnings)] -#![doc(hidden, html_root_url = "https://docs.rs/tokio-codec/0.1.0")] - -// _tokio_codec are the items that belong in the `tokio_codec` crate. However, because we need to -// maintain backward compatibility until the next major breaking change, they are defined here. -// When the next breaking change comes, they should be moved to the `tokio_codec` crate and become -// independent. -// -// The primary reason we can't move these to `tokio-codec` now is because, again for backward -// compatibility reasons, we need to keep `Decoder` and `Encoder` in tokio_io::codec. And `Decoder` -// and `Encoder` needs to reference `Framed`. So they all still need to still be in the same -// module. - -mod framed; -mod framed_read; -mod framed_write; - -pub use self::framed::{Framed, FramedParts}; -pub use self::framed_read::FramedRead; -pub use self::framed_write::FramedWrite; diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 728a78b9f..f20013020 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -1,12 +1,11 @@ -// #![allow(unused_imports, unused_variables, dead_code)] use std::collections::VecDeque; use std::fmt::{Debug, Display}; use std::time::Instant; +use actix_net::codec::Framed; use actix_net::service::Service; use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; -// use tokio_current_thread::spawn; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; @@ -16,7 +15,6 @@ use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; use body::Body; use config::ServiceConfig; use error::DispatchError; -use framed::Framed; use request::Request; use response::Response; diff --git a/src/lib.rs b/src/lib.rs index 74e7ced78..9acdf3cdb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,9 +135,6 @@ mod request; mod response; mod uri; -#[doc(hidden)] -pub mod framed; - pub mod error; pub mod h1; pub(crate) mod helpers; diff --git a/src/ws/mod.rs b/src/ws/mod.rs index e8bf3870d..bd657d944 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -15,10 +15,12 @@ mod codec; mod frame; mod mask; mod proto; +mod transport; -pub use self::codec::Message; +pub use self::codec::{Codec, Message}; pub use self::frame::Frame; pub use self::proto::{CloseCode, CloseReason, OpCode}; +pub use self::transport::Transport; /// Websocket protocol errors #[derive(Fail, Debug)] diff --git a/src/ws/transport.rs b/src/ws/transport.rs new file mode 100644 index 000000000..aabeb5d5a --- /dev/null +++ b/src/ws/transport.rs @@ -0,0 +1,50 @@ +use actix_net::codec::Framed; +use actix_net::framed::{FramedTransport, FramedTransportError}; +use actix_net::service::{IntoService, Service}; +use futures::{Future, Poll}; +use tokio_io::{AsyncRead, AsyncWrite}; + +use super::{Codec, Message}; + +pub struct Transport +where + S: Service, + T: AsyncRead + AsyncWrite, +{ + inner: FramedTransport, +} + +impl Transport +where + T: AsyncRead + AsyncWrite, + S: Service, + S::Future: 'static, + S::Error: 'static, +{ + pub fn new>(io: T, service: F) -> Self { + Transport { + inner: FramedTransport::new(Framed::new(io, Codec::new()), service), + } + } + + pub fn with>(framed: Framed, service: F) -> Self { + Transport { + inner: FramedTransport::new(framed, service), + } + } +} + +impl Future for Transport +where + T: AsyncRead + AsyncWrite, + S: Service, + S::Future: 'static, + S::Error: 'static, +{ + type Item = (); + type Error = FramedTransportError; + + fn poll(&mut self) -> Poll { + self.inner.poll() + } +} diff --git a/tests/test_ws.rs b/tests/test_ws.rs new file mode 100644 index 000000000..86a309191 --- /dev/null +++ b/tests/test_ws.rs @@ -0,0 +1,111 @@ +extern crate actix; +extern crate actix_http; +extern crate actix_net; +extern crate actix_web; +extern crate bytes; +extern crate futures; + +use std::{io, thread}; + +use actix::System; +use actix_net::codec::Framed; +use actix_net::server::Server; +use actix_net::service::IntoNewService; +use actix_web::{test, ws as web_ws}; +use bytes::Bytes; +use futures::future::{ok, Either}; +use futures::{Future, Sink, Stream}; + +use actix_http::{h1, ws, ResponseError}; + +fn ws_handler(req: ws::Message) -> impl Future { + match req { + ws::Message::Ping(msg) => ok(ws::Message::Pong(msg)), + ws::Message::Text(text) => ok(ws::Message::Text(text)), + ws::Message::Binary(bin) => ok(ws::Message::Binary(bin)), + ws::Message::Close(reason) => ok(ws::Message::Close(reason)), + _ => ok(ws::Message::Close(None)), + } +} + +#[test] +fn test_simple() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + (|io| { + // read http request + let framed = Framed::new(io, h1::Codec::new(false)); + framed + .into_future() + .map_err(|_| ()) + .and_then(|(req, framed)| { + // validate request + if let Some(h1::InMessage::MessageWithPayload(req)) = req { + match ws::handshake(&req) { + Err(e) => { + // validation failed + let resp = e.error_response(); + Either::A( + framed + .send(h1::OutMessage::Response(resp)) + .map_err(|_| ()) + .map(|_| ()), + ) + } + Ok(mut resp) => Either::B( + // send response + framed + .send(h1::OutMessage::Response( + resp.finish(), + )).map_err(|_| ()) + .and_then(|framed| { + // start websocket service + let framed = + framed.into_framed(ws::Codec::new()); + ws::Transport::with(framed, ws_handler) + .map_err(|_| ()) + }), + ), + } + } else { + panic!() + } + }) + }).into_new_service() + }).unwrap() + .run(); + }); + + let mut sys = System::new("test"); + { + let (reader, mut writer) = sys + .block_on(web_ws::Client::new(format!("http://{}/", addr)).connect()) + .unwrap(); + + writer.text("text"); + let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + assert_eq!(item, Some(web_ws::Message::Text("text".to_owned()))); + + writer.binary(b"text".as_ref()); + let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + assert_eq!( + item, + Some(web_ws::Message::Binary(Bytes::from_static(b"text").into())) + ); + + writer.ping("ping"); + let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + assert_eq!(item, Some(web_ws::Message::Pong("ping".to_owned()))); + + writer.close(Some(web_ws::CloseCode::Normal.into())); + let (item, _) = sys.block_on(reader.into_future()).unwrap(); + assert_eq!( + item, + Some(web_ws::Message::Close(Some( + web_ws::CloseCode::Normal.into() + ))) + ); + } +} From c0699a070efc7b72f120cb380a03cf5abaaa3bd8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 15:39:35 -0700 Subject: [PATCH 019/208] add TakeRequest service; update ws test case --- src/h1/mod.rs | 2 +- src/h1/service.rs | 81 +++++++++++++++++++++++++++++++++++++++++++++-- src/ws/mod.rs | 1 - tests/test_ws.rs | 73 ++++++++++++++++++++---------------------- 4 files changed, 114 insertions(+), 43 deletions(-) diff --git a/src/h1/mod.rs b/src/h1/mod.rs index f9abfea5c..266ebf39c 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -7,4 +7,4 @@ mod service; pub use self::codec::{Codec, InMessage, OutMessage}; pub use self::dispatcher::Dispatcher; -pub use self::service::{H1Service, H1ServiceHandler}; +pub use self::service::{H1Service, H1ServiceHandler, TakeRequest}; diff --git a/src/h1/service.rs b/src/h1/service.rs index 8038c9fb8..02535fc8c 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -1,15 +1,17 @@ use std::fmt::{Debug, Display}; use std::marker::PhantomData; +use actix_net::codec::Framed; use actix_net::service::{IntoNewService, NewService, Service}; -use futures::{Async, Future, Poll}; +use futures::{future, Async, Future, Poll, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; use config::ServiceConfig; -use error::DispatchError; +use error::{DispatchError, ParseError}; use request::Request; use response::Response; +use super::codec::{Codec, InMessage}; use super::dispatcher::Dispatcher; /// `NewService` implementation for HTTP1 transport @@ -121,3 +123,78 @@ where Dispatcher::new(req, self.cfg.clone(), self.srv.clone()) } } + +/// `NewService` that implements, read one request from framed object feature. +pub struct TakeRequest { + _t: PhantomData, +} + +impl TakeRequest { + /// Create new `TakeRequest` instance. + pub fn new() -> Self { + TakeRequest { _t: PhantomData } + } +} + +impl NewService for TakeRequest +where + T: AsyncRead + AsyncWrite, +{ + type Request = Framed; + type Response = (Option, Framed); + type Error = ParseError; + type InitError = (); + type Service = TakeRequestService; + type Future = future::FutureResult; + + fn new_service(&self) -> Self::Future { + future::ok(TakeRequestService { _t: PhantomData }) + } +} + +/// `NewService` that implements, read one request from framed object feature. +pub struct TakeRequestService { + _t: PhantomData, +} + +impl Service for TakeRequestService +where + T: AsyncRead + AsyncWrite, +{ + type Request = Framed; + type Response = (Option, Framed); + type Error = ParseError; + type Future = TakeRequestServiceResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, framed: Self::Request) -> Self::Future { + TakeRequestServiceResponse { + framed: Some(framed), + } + } +} + +pub struct TakeRequestServiceResponse +where + T: AsyncRead + AsyncWrite, +{ + framed: Option>, +} + +impl Future for TakeRequestServiceResponse +where + T: AsyncRead + AsyncWrite, +{ + type Item = (Option, Framed); + type Error = ParseError; + + fn poll(&mut self) -> Poll { + match self.framed.as_mut().unwrap().poll()? { + Async::Ready(item) => Ok(Async::Ready((item, self.framed.take().unwrap()))), + Async::NotReady => Ok(Async::NotReady), + } + } +} diff --git a/src/ws/mod.rs b/src/ws/mod.rs index bd657d944..5ebb502bb 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -3,7 +3,6 @@ //! To setup a `WebSocket`, first do web socket handshake then on success //! convert `Payload` into a `WsStream` stream and then use `WsWriter` to //! communicate with the peer. -//! ``` use std::io; use error::ResponseError; diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 86a309191..a2a18ff28 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -9,8 +9,9 @@ use std::{io, thread}; use actix::System; use actix_net::codec::Framed; +use actix_net::framed::IntoFramed; use actix_net::server::Server; -use actix_net::service::IntoNewService; +use actix_net::service::NewServiceExt; use actix_web::{test, ws as web_ws}; use bytes::Bytes; use futures::future::{ok, Either}; @@ -18,7 +19,7 @@ use futures::{Future, Sink, Stream}; use actix_http::{h1, ws, ResponseError}; -fn ws_handler(req: ws::Message) -> impl Future { +fn ws_service(req: ws::Message) -> impl Future { match req { ws::Message::Ping(msg) => ok(ws::Message::Pong(msg)), ws::Message::Text(text) => ok(ws::Message::Text(text)), @@ -34,46 +35,40 @@ fn test_simple() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - (|io| { - // read http request - let framed = Framed::new(io, h1::Codec::new(false)); - framed - .into_future() - .map_err(|_| ()) - .and_then(|(req, framed)| { - // validate request - if let Some(h1::InMessage::MessageWithPayload(req)) = req { - match ws::handshake(&req) { - Err(e) => { - // validation failed - let resp = e.error_response(); - Either::A( - framed - .send(h1::OutMessage::Response(resp)) - .map_err(|_| ()) - .map(|_| ()), - ) - } - Ok(mut resp) => Either::B( - // send response + IntoFramed::new(|| h1::Codec::new(false)) + .and_then(h1::TakeRequest::new().map_err(|_| ())) + .and_then(|(req, framed): (_, Framed<_, _>)| { + // validate request + if let Some(h1::InMessage::MessageWithPayload(req)) = req { + match ws::handshake(&req) { + Err(e) => { + // validation failed + let resp = e.error_response(); + Either::A( framed - .send(h1::OutMessage::Response( - resp.finish(), - )).map_err(|_| ()) - .and_then(|framed| { - // start websocket service - let framed = - framed.into_framed(ws::Codec::new()); - ws::Transport::with(framed, ws_handler) - .map_err(|_| ()) - }), - ), + .send(h1::OutMessage::Response(resp)) + .map_err(|_| ()) + .map(|_| ()), + ) } - } else { - panic!() + Ok(mut resp) => Either::B( + // send response + framed + .send(h1::OutMessage::Response(resp.finish())) + .map_err(|_| ()) + .and_then(|framed| { + // start websocket service + let framed = + framed.into_framed(ws::Codec::new()); + ws::Transport::with(framed, ws_service) + .map_err(|_| ()) + }), + ), } - }) - }).into_new_service() + } else { + panic!() + } + }) }).unwrap() .run(); }); From ee62814216fb67697f2451803f31f006085c05fc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Oct 2018 20:31:22 -0700 Subject: [PATCH 020/208] split request decoder and payload decoder --- src/h1/codec.rs | 57 ++++--- src/h1/decoder.rs | 389 +++++++++++++++++++--------------------------- 2 files changed, 199 insertions(+), 247 deletions(-) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index a27e6472c..8ab8f2528 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -4,15 +4,14 @@ use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; -use super::decoder::H1Decoder; -pub use super::decoder::InMessage; +use super::decoder::{PayloadDecoder, PayloadItem, RequestDecoder}; use super::encoder::{ResponseEncoder, ResponseLength}; use body::Body; use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{Method, Version}; -use request::RequestPool; +use request::{Request, RequestPool}; use response::Response; bitflags! { @@ -34,9 +33,23 @@ pub enum OutMessage { Payload(Bytes), } +/// Incoming http/1 request +#[derive(Debug)] +pub enum InMessage { + /// Request + Message(Request), + /// Request with payload + MessageWithPayload(Request), + /// Payload chunk + Chunk(Bytes), + /// End of payload + Eof, +} + /// HTTP/1 Codec pub struct Codec { - decoder: H1Decoder, + decoder: RequestDecoder, + payload: Option, version: Version, // encoder part @@ -64,7 +77,8 @@ impl Codec { Flags::empty() }; Codec { - decoder: H1Decoder::with_pool(pool), + decoder: RequestDecoder::with_pool(pool), + payload: None, version: Version::HTTP_11, flags, @@ -234,21 +248,28 @@ impl Decoder for Codec { type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - let res = self.decoder.decode(src); - - match res { - Ok(Some(InMessage::Message(ref req))) - | Ok(Some(InMessage::MessageWithPayload(ref req))) => { - self.flags - .set(Flags::HEAD, req.inner.method == Method::HEAD); - self.version = req.inner.version; - if self.flags.contains(Flags::KEEPALIVE_ENABLED) { - self.flags.set(Flags::KEEPALIVE, req.keep_alive()); - } + if self.payload.is_some() { + Ok(match self.payload.as_mut().unwrap().decode(src)? { + Some(PayloadItem::Chunk(chunk)) => Some(InMessage::Chunk(chunk)), + Some(PayloadItem::Eof) => Some(InMessage::Eof), + None => None, + }) + } else if let Some((req, payload)) = self.decoder.decode(src)? { + self.flags + .set(Flags::HEAD, req.inner.method == Method::HEAD); + self.version = req.inner.version; + if self.flags.contains(Flags::KEEPALIVE_ENABLED) { + self.flags.set(Flags::KEEPALIVE, req.keep_alive()); } - _ => (), + self.payload = payload; + if self.payload.is_some() { + Ok(Some(InMessage::MessageWithPayload(req))) + } else { + Ok(Some(InMessage::Message(req))) + } + } else { + Ok(None) } - res } } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 48776226b..fb29f033c 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -3,6 +3,7 @@ use std::{io, mem}; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; use httparse; +use tokio_codec::Decoder; use error::ParseError; use http::header::{HeaderName, HeaderValue}; @@ -13,75 +14,25 @@ use uri::Url; const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; -pub(crate) struct H1Decoder { - decoder: Option, - pool: &'static RequestPool, +pub struct RequestDecoder(&'static RequestPool); + +impl RequestDecoder { + pub(crate) fn with_pool(pool: &'static RequestPool) -> RequestDecoder { + RequestDecoder(pool) + } } -/// Incoming http/1 request -#[derive(Debug)] -pub enum InMessage { - /// Request - Message(Request), - /// Request with payload - MessageWithPayload(Request), - /// Payload chunk - Chunk(Bytes), - /// End of payload - Eof, +impl Default for RequestDecoder { + fn default() -> RequestDecoder { + RequestDecoder::with_pool(RequestPool::pool()) + } } -impl H1Decoder { - #[cfg(test)] - pub fn new() -> H1Decoder { - H1Decoder::with_pool(RequestPool::pool()) - } +impl Decoder for RequestDecoder { + type Item = (Request, Option); + type Error = ParseError; - pub fn with_pool(pool: &'static RequestPool) -> H1Decoder { - H1Decoder { - pool, - decoder: None, - } - } - - pub fn decode( - &mut self, src: &mut BytesMut, - ) -> Result, ParseError> { - // read payload - if self.decoder.is_some() { - match self.decoder.as_mut().unwrap().decode(src)? { - Async::Ready(Some(bytes)) => return Ok(Some(InMessage::Chunk(bytes))), - Async::Ready(None) => { - self.decoder.take(); - return Ok(Some(InMessage::Eof)); - } - Async::NotReady => return Ok(None), - } - } - - match self.parse_message(src)? { - Async::Ready((msg, decoder)) => { - self.decoder = decoder; - if self.decoder.is_some() { - Ok(Some(InMessage::MessageWithPayload(msg))) - } else { - Ok(Some(InMessage::Message(msg))) - } - } - Async::NotReady => { - if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - Err(ParseError::TooLarge) - } else { - Ok(None) - } - } - } - } - - fn parse_message( - &self, buf: &mut BytesMut, - ) -> Poll<(Request, Option), ParseError> { + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { // Parse http message let mut has_upgrade = false; let mut chunked = false; @@ -98,7 +49,7 @@ impl H1Decoder { unsafe { mem::uninitialized() }; let mut req = httparse::Request::new(&mut parsed); - match req.parse(buf)? { + match req.parse(src)? { httparse::Status::Complete(len) => { let method = Method::from_bytes(req.method.unwrap().as_bytes()) .map_err(|_| ParseError::Method)?; @@ -108,18 +59,18 @@ impl H1Decoder { } else { Version::HTTP_10 }; - HeaderIndex::record(buf, req.headers, &mut headers); + HeaderIndex::record(src, req.headers, &mut headers); (len, method, path, version, req.headers.len()) } - httparse::Status::Partial => return Ok(Async::NotReady), + httparse::Status::Partial => return Ok(None), } }; - let slice = buf.split_to(len).freeze(); + let slice = src.split_to(len).freeze(); // convert headers - let mut msg = RequestPool::get(self.pool); + let mut msg = RequestPool::get(self.0); { let inner = msg.inner_mut(); inner @@ -198,18 +149,21 @@ impl H1Decoder { // https://tools.ietf.org/html/rfc7230#section-3.3.3 let decoder = if chunked { // Chunked encoding - Some(EncodingDecoder::chunked()) + Some(PayloadDecoder::chunked()) } else if let Some(len) = content_length { // Content-Length - Some(EncodingDecoder::length(len)) + Some(PayloadDecoder::length(len)) } else if has_upgrade || msg.inner.method == Method::CONNECT { // upgrade(websocket) or connect - Some(EncodingDecoder::eof()) + Some(PayloadDecoder::eof()) + } else if src.len() >= MAX_BUFFER_SIZE { + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + return Err(ParseError::TooLarge); } else { None }; - Ok(Async::Ready((msg, decoder))) + Ok(Some((msg, decoder))) } } @@ -235,30 +189,37 @@ impl HeaderIndex { } } +#[derive(Debug, Clone)] +/// Http payload item +pub enum PayloadItem { + Chunk(Bytes), + Eof, +} + /// Decoders to handle different Transfer-Encodings. /// /// If a message body does not include a Transfer-Encoding, it *should* /// include a Content-Length header. #[derive(Debug, Clone, PartialEq)] -pub struct EncodingDecoder { +pub struct PayloadDecoder { kind: Kind, } -impl EncodingDecoder { - pub fn length(x: u64) -> EncodingDecoder { - EncodingDecoder { +impl PayloadDecoder { + pub fn length(x: u64) -> PayloadDecoder { + PayloadDecoder { kind: Kind::Length(x), } } - pub fn chunked() -> EncodingDecoder { - EncodingDecoder { + pub fn chunked() -> PayloadDecoder { + PayloadDecoder { kind: Kind::Chunked(ChunkedState::Size, 0), } } - pub fn eof() -> EncodingDecoder { - EncodingDecoder { + pub fn eof() -> PayloadDecoder { + PayloadDecoder { kind: Kind::Eof(false), } } @@ -302,53 +263,59 @@ enum ChunkedState { End, } -impl EncodingDecoder { - pub fn decode(&mut self, body: &mut BytesMut) -> Poll, io::Error> { +impl Decoder for PayloadDecoder { + type Item = PayloadItem; + type Error = io::Error; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { match self.kind { Kind::Length(ref mut remaining) => { if *remaining == 0 { - Ok(Async::Ready(None)) + Ok(Some(PayloadItem::Eof)) } else { - if body.is_empty() { - return Ok(Async::NotReady); + if src.is_empty() { + return Ok(None); } - let len = body.len() as u64; + let len = src.len() as u64; let buf; if *remaining > len { - buf = body.take().freeze(); + buf = src.take().freeze(); *remaining -= len; } else { - buf = body.split_to(*remaining as usize).freeze(); + buf = src.split_to(*remaining as usize).freeze(); *remaining = 0; - } + }; trace!("Length read: {}", buf.len()); - Ok(Async::Ready(Some(buf))) + Ok(Some(PayloadItem::Chunk(buf))) } } Kind::Chunked(ref mut state, ref mut size) => { loop { let mut buf = None; // advances the chunked state - *state = try_ready!(state.step(body, size, &mut buf)); + *state = match state.step(src, size, &mut buf)? { + Async::NotReady => return Ok(None), + Async::Ready(state) => state, + }; if *state == ChunkedState::End { trace!("End of chunked stream"); - return Ok(Async::Ready(None)); + return Ok(Some(PayloadItem::Eof)); } if let Some(buf) = buf { - return Ok(Async::Ready(Some(buf))); + return Ok(Some(PayloadItem::Chunk(buf))); } - if body.is_empty() { - return Ok(Async::NotReady); + if src.is_empty() { + return Ok(None); } } } Kind::Eof(ref mut is_eof) => { if *is_eof { - Ok(Async::Ready(None)) - } else if !body.is_empty() { - Ok(Async::Ready(Some(body.take().freeze()))) + Ok(Some(PayloadItem::Eof)) + } else if !src.is_empty() { + Ok(Some(PayloadItem::Chunk(src.take().freeze()))) } else { - Ok(Async::NotReady) + Ok(None) } } } @@ -536,15 +503,18 @@ mod tests { _ => panic!("error"), } } + } + + impl PayloadItem { fn chunk(self) -> Bytes { match self { - InMessage::Chunk(chunk) => chunk, + PayloadItem::Chunk(chunk) => chunk, _ => panic!("error"), } } fn eof(&self) -> bool { match *self { - InMessage::Eof => true, + PayloadItem::Eof => true, _ => false, } } @@ -552,8 +522,8 @@ mod tests { macro_rules! parse_ready { ($e:expr) => {{ - match H1Decoder::new().decode($e) { - Ok(Some(msg)) => msg.message(), + match RequestDecoder::default().decode($e) { + Ok(Some((msg, _))) => msg, Ok(_) => unreachable!("Eof during parsing http request"), Err(err) => unreachable!("Error during parsing http request: {:?}", err), } @@ -562,7 +532,7 @@ mod tests { macro_rules! expect_parse_err { ($e:expr) => {{ - match H1Decoder::new().decode($e) { + match RequestDecoder::default().decode($e) { Err(err) => match err { ParseError::Io(_) => unreachable!("Parse error expected"), _ => (), @@ -641,10 +611,9 @@ mod tests { fn test_parse() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); - let mut reader = H1Decoder::new(); + let mut reader = RequestDecoder::default(); match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let req = msg.message(); + Ok(Some((req, _))) => { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); @@ -657,38 +626,25 @@ mod tests { fn test_parse_partial() { let mut buf = BytesMut::from("PUT /test HTTP/1"); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf) { - Ok(None) => (), - _ => unreachable!("Error"), - } + let mut reader = RequestDecoder::default(); + assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b".1\r\n\r\n"); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::PUT); - assert_eq!(req.path(), "/test"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::PUT); + assert_eq!(req.path(), "/test"); } #[test] fn test_parse_post() { let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_10); - assert_eq!(*req.method(), Method::POST); - assert_eq!(req.path(), "/test2"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } + let mut reader = RequestDecoder::default(); + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(req.version(), Version::HTTP_10); + assert_eq!(*req.method(), Method::POST); + assert_eq!(req.path(), "/test2"); } #[test] @@ -696,20 +652,16 @@ mod tests { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"body" - ); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } + let mut reader = RequestDecoder::default(); + let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); + assert_eq!( + pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + b"body" + ); } #[test] @@ -717,45 +669,36 @@ mod tests { let mut buf = BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"body" - ); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } + let mut reader = RequestDecoder::default(); + let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); + assert_eq!( + pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + b"body" + ); } #[test] fn test_parse_partial_eof() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let mut reader = H1Decoder::new(); + let mut reader = RequestDecoder::default(); assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b"\r\n"); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); } #[test] fn test_headers_split_field() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let mut reader = H1Decoder::new(); + let mut reader = RequestDecoder::default(); assert!{ reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"t"); @@ -765,16 +708,11 @@ mod tests { assert!{ reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"t: value\r\n\r\n"); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); + assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); } #[test] @@ -784,9 +722,8 @@ mod tests { Set-Cookie: c1=cookie1\r\n\ Set-Cookie: c2=cookie2\r\n\r\n", ); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - let req = msg.message(); + let mut reader = RequestDecoder::default(); + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); let val: Vec<_> = req .headers() @@ -985,14 +922,13 @@ mod tests { upgrade: websocket\r\n\r\n\ some raw data", ); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); + let mut reader = RequestDecoder::default(); + let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); assert!(!req.keep_alive()); assert!(req.upgrade()); assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), b"some raw data" ); } @@ -1039,22 +975,21 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n", ); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); + let mut reader = RequestDecoder::default(); + let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); assert!(req.chunked().unwrap()); buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), b"data" ); assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), b"line" ); - assert!(reader.decode(&mut buf).unwrap().unwrap().eof()); + assert!(pl.decode(&mut buf).unwrap().unwrap().eof()); } #[test] @@ -1063,10 +998,9 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n", ); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); + let mut reader = RequestDecoder::default(); + let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); assert!(req.chunked().unwrap()); buf.extend( @@ -1075,19 +1009,17 @@ mod tests { transfer-encoding: chunked\r\n\r\n" .iter(), ); - let msg = reader.decode(&mut buf).unwrap().unwrap(); + let msg = pl.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"data"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); + let msg = pl.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"line"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); + let msg = pl.decode(&mut buf).unwrap().unwrap(); assert!(msg.eof()); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req2 = msg.message(); - assert!(req2.chunked().unwrap()); - assert_eq!(*req2.method(), Method::POST); - assert!(req2.chunked().unwrap()); + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert!(req.chunked().unwrap()); + assert_eq!(*req.method(), Method::POST); + assert!(req.chunked().unwrap()); } #[test] @@ -1097,30 +1029,29 @@ mod tests { transfer-encoding: chunked\r\n\r\n", ); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); + let mut reader = RequestDecoder::default(); + let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); assert!(req.chunked().unwrap()); buf.extend(b"4\r\n1111\r\n"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); + let msg = pl.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"1111"); buf.extend(b"4\r\ndata\r"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); + let msg = pl.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"data"); buf.extend(b"\n4"); - assert!(reader.decode(&mut buf).unwrap().is_none()); + assert!(pl.decode(&mut buf).unwrap().is_none()); buf.extend(b"\r"); - assert!(reader.decode(&mut buf).unwrap().is_none()); + assert!(pl.decode(&mut buf).unwrap().is_none()); buf.extend(b"\n"); - assert!(reader.decode(&mut buf).unwrap().is_none()); + assert!(pl.decode(&mut buf).unwrap().is_none()); buf.extend(b"li"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); + let msg = pl.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"li"); //trailers @@ -1128,12 +1059,12 @@ mod tests { //not_ready!(reader.parse(&mut buf, &mut readbuf)); buf.extend(b"ne\r\n0\r\n"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); + let msg = pl.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"ne"); - assert!(reader.decode(&mut buf).unwrap().is_none()); + assert!(pl.decode(&mut buf).unwrap().is_none()); buf.extend(b"\r\n"); - assert!(reader.decode(&mut buf).unwrap().unwrap().eof()); + assert!(pl.decode(&mut buf).unwrap().unwrap().eof()); } #[test] @@ -1143,17 +1074,17 @@ mod tests { transfer-encoding: chunked\r\n\r\n"[..], ); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - assert!(msg.message().chunked().unwrap()); + let mut reader = RequestDecoder::default(); + let (msg, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); + assert!(msg.chunked().unwrap()); buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") - let chunk = reader.decode(&mut buf).unwrap().unwrap().chunk(); + let chunk = pl.decode(&mut buf).unwrap().unwrap().chunk(); assert_eq!(chunk, Bytes::from_static(b"data")); - let chunk = reader.decode(&mut buf).unwrap().unwrap().chunk(); + let chunk = pl.decode(&mut buf).unwrap().unwrap().chunk(); assert_eq!(chunk, Bytes::from_static(b"line")); - let msg = reader.decode(&mut buf).unwrap().unwrap(); + let msg = pl.decode(&mut buf).unwrap().unwrap(); assert!(msg.eof()); } } From c368abdf5fcce199b7b2ff3560a7476e63da90ba Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Oct 2018 20:34:19 -0700 Subject: [PATCH 021/208] remove Json type --- src/json.rs | 101 ---------------------------------------------------- src/lib.rs | 1 - 2 files changed, 102 deletions(-) diff --git a/src/json.rs b/src/json.rs index a52884894..e2c99ba3f 100644 --- a/src/json.rs +++ b/src/json.rs @@ -1,8 +1,6 @@ use bytes::BytesMut; use futures::{Future, Poll, Stream}; use http::header::CONTENT_LENGTH; -use std::fmt; -use std::ops::{Deref, DerefMut}; use mime; use serde::de::DeserializeOwned; @@ -11,105 +9,6 @@ use serde_json; use error::JsonPayloadError; use httpmessage::HttpMessage; -/// Json helper -/// -/// Json can be used for two different purpose. First is for json response -/// generation and second is for extracting typed information from request's -/// payload. -/// -/// To extract typed information from request's body, the type `T` must -/// implement the `Deserialize` trait from *serde*. -/// -/// [**JsonConfig**](dev/struct.JsonConfig.html) allows to configure extraction -/// process. -/// -/// ## Example -/// -/// ```rust,ignore -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Json, Result, http}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// deserialize `Info` from request's body -/// fn index(info: Json) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| r.method(http::Method::POST).with(index)); // <- use `with` extractor -/// } -/// ``` -/// -/// The `Json` type allows you to respond with well-formed JSON data: simply -/// return a value of type Json where T is the type of a structure -/// to serialize into *JSON*. The type `T` must implement the `Serialize` -/// trait from *serde*. -/// -/// ```rust,ignore -/// # extern crate actix_web; -/// # #[macro_use] extern crate serde_derive; -/// # use actix_web::*; -/// # -/// #[derive(Serialize)] -/// struct MyObj { -/// name: String, -/// } -/// -/// fn index(req: HttpRequest) -> Result> { -/// Ok(Json(MyObj { -/// name: req.match_info().query("name")?, -/// })) -/// } -/// # fn main() {} -/// ``` -pub struct Json(pub T); - -impl Json { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl Deref for Json { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Json { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl fmt::Debug for Json -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Json: {:?}", self.0) - } -} - -impl fmt::Display for Json -where - T: fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - /// Request payload json parser that resolves to a deserialized `T` value. /// /// Returns error: diff --git a/src/lib.rs b/src/lib.rs index 9acdf3cdb..32f78517b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,7 +144,6 @@ pub use body::{Binary, Body}; pub use error::{Error, ResponseError, Result}; pub use extensions::Extensions; pub use httpmessage::HttpMessage; -pub use json::Json; pub use request::Request; pub use response::Response; From 87b83a34034416dcaed9ded3fea442a21c48f211 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Oct 2018 21:07:32 -0700 Subject: [PATCH 022/208] update tests, remove unused deps --- Cargo.toml | 2 - src/error.rs | 27 ------------- src/h1/dispatcher.rs | 88 +++++++++++++++++++++---------------------- src/lib.rs | 2 - tests/test_h1v2.rs | 46 ---------------------- tests/test_server.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 133 insertions(+), 122 deletions(-) delete mode 100644 tests/test_h1v2.rs create mode 100644 tests/test_server.rs diff --git a/Cargo.toml b/Cargo.toml index 455b61488..48f199ed8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,6 @@ time = "0.1" encoding = "0.2" lazy_static = "1.0" serde_urlencoded = "^0.5.3" -url = { version="1.7", features=["query_encoding"] } cookie = { version="0.11", features=["percent-encode"] } failure = "^0.1.2" @@ -81,7 +80,6 @@ tokio = "0.1" tokio-io = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" -tokio-reactor = "0.1" tokio-current-thread = "0.1" # native-tls diff --git a/src/error.rs b/src/error.rs index dc2d45b84..26b3ca56d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,7 +16,6 @@ use serde::de::value::Error as DeError; use serde_json::error::Error as JsonError; use serde_urlencoded::ser::Error as FormError; use tokio_timer::Error as TimerError; -pub use url::ParseError as UrlParseError; // re-exports pub use cookie::ParseError as CookieParseError; @@ -196,9 +195,6 @@ impl ResponseError for FormError {} /// `InternalServerError` for `TimerError` impl ResponseError for TimerError {} -/// `InternalServerError` for `UrlParseError` -impl ResponseError for UrlParseError {} - /// Return `BAD_REQUEST` for `de::value::Error` impl ResponseError for DeError { fn error_response(&self) -> Response { @@ -552,29 +548,6 @@ impl From for ReadlinesError { } } -/// Errors which can occur when attempting to generate resource uri. -#[derive(Fail, Debug, PartialEq)] -pub enum UrlGenerationError { - /// Resource not found - #[fail(display = "Resource not found")] - ResourceNotFound, - /// Not all path pattern covered - #[fail(display = "Not all path pattern covered")] - NotEnoughElements, - /// URL parse error - #[fail(display = "{}", _0)] - ParseError(#[cause] UrlParseError), -} - -/// `InternalServerError` for `UrlGeneratorError` -impl ResponseError for UrlGenerationError {} - -impl From for UrlGenerationError { - fn from(err: UrlParseError) -> Self { - UrlGenerationError::ParseError(err) - } -} - /// Helper type that can wrap any error and generate custom response. /// /// In following example any `io::Error` will be converted into "BAD REQUEST" diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index f20013020..a39967a22 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -48,12 +48,17 @@ where state: State, payload: Option, - messages: VecDeque, + messages: VecDeque, ka_expire: Instant, ka_timer: Option, } +enum Message { + Item(Request), + Error(Response), +} + enum State { None, Response(S::Future), @@ -131,32 +136,11 @@ where } // if checked is set to true, delay disconnect until all tasks have finished. - fn client_disconnected(&mut self, _checked: bool) { + fn client_disconnected(&mut self) { self.flags.insert(Flags::READ_DISCONNECTED); if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete); } - - // if !checked || self.tasks.is_empty() { - // self.flags - // .insert(Flags::WRITE_DISCONNECTED | Flags::FLUSHED); - - // // notify tasks - // for mut task in self.tasks.drain(..) { - // task.disconnected(); - // match task.poll_completed() { - // Ok(Async::NotReady) => { - // // spawn not completed task, it does not require access to io - // // at this point - // spawn(HttpHandlerTaskFut::new(task.into_task())); - // } - // Ok(Async::Ready(_)) => (), - // Err(err) => { - // error!("Unhandled application error: {}", err); - // } - // } - // } - // } } /// Flush stream @@ -166,7 +150,7 @@ where Ok(Async::NotReady) => Ok(Async::NotReady), Err(err) => { debug!("Error sending data: {}", err); - self.client_disconnected(false); + self.client_disconnected(); Err(err.into()) } Ok(Async::Ready(_)) => { @@ -192,19 +176,28 @@ where let state = match self.state { State::None => loop { break if let Some(msg) = self.messages.pop_front() { - let mut task = self.service.call(msg); - match task.poll() { - Ok(Async::Ready(res)) => { - if res.body().is_streaming() { - unimplemented!() - } else { - Some(Ok(State::SendResponse(Some( - OutMessage::Response(res), - )))) + match msg { + Message::Item(msg) => { + let mut task = self.service.call(msg); + match task.poll() { + Ok(Async::Ready(res)) => { + if res.body().is_streaming() { + unimplemented!() + } else { + Some(Ok(State::SendResponse(Some( + OutMessage::Response(res), + )))) + } + } + Ok(Async::NotReady) => { + Some(Ok(State::Response(task))) + } + Err(err) => Some(Err(DispatchError::Service(err))), } } - Ok(Async::NotReady) => Some(Ok(State::Response(task))), - Err(err) => Some(Err(DispatchError::Service(err))), + Message::Error(res) => Some(Ok(State::SendResponse(Some( + OutMessage::Response(res), + )))), } } else { None @@ -249,7 +242,8 @@ where } } State::SendResponseWithPayload(ref mut item) => { - let (msg, body) = item.take().expect("SendResponse is empty"); + let (msg, body) = + item.take().expect("SendResponseWithPayload is empty"); match self.framed.start_send(msg) { Ok(AsyncSink::Ready) => { self.flags.set( @@ -271,8 +265,7 @@ where match state { Some(Ok(state)) => self.state = state, Some(Err(err)) => { - // error!("Unhandled error1: {}", err); - self.client_disconnected(false); + self.client_disconnected(); return Err(err); } None => { @@ -310,12 +303,12 @@ where Ok(Async::NotReady) => self.state = State::Response(task), Err(err) => { error!("Unhandled application error: {}", err); - self.client_disconnected(false); + self.client_disconnected(); return Err(DispatchError::Service(err)); } } } else { - self.messages.push_back(msg); + self.messages.push_back(Message::Item(msg)); } } InMessage::MessageWithPayload(msg) => { @@ -324,7 +317,7 @@ where *msg.inner.payload.borrow_mut() = Some(pl); self.payload = Some(ps); - self.messages.push_back(msg); + self.messages.push_back(Message::Item(msg)); } InMessage::Chunk(chunk) => { if let Some(ref mut payload) = self.payload { @@ -332,7 +325,9 @@ where } else { error!("Internal server error: unexpected payload chunk"); self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - // self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); + self.messages.push_back(Message::Error( + Response::InternalServerError().finish(), + )); self.error = Some(DispatchError::InternalError); } } @@ -342,7 +337,9 @@ where } else { error!("Internal server error: unexpected eof"); self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - // self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); + self.messages.push_back(Message::Error( + Response::InternalServerError().finish(), + )); self.error = Some(DispatchError::InternalError); } } @@ -363,7 +360,7 @@ where } Ok(Async::Ready(None)) => { if self.flags.contains(Flags::READ_DISCONNECTED) { - self.client_disconnected(true); + self.client_disconnected(); } break; } @@ -378,7 +375,8 @@ where } // Malformed requests should be responded with 400 - // self.push_response_entry(StatusCode::BAD_REQUEST); + self.messages + .push_back(Message::Error(Response::BadRequest().finish())); self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); self.error = Some(DispatchError::MalformedRequest); break; diff --git a/src/lib.rs b/src/lib.rs index 32f78517b..4ec097266 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,12 +112,10 @@ extern crate tokio; extern crate tokio_codec; extern crate tokio_current_thread; extern crate tokio_io; -extern crate tokio_reactor; extern crate tokio_tcp; extern crate tokio_timer; #[cfg(all(unix, feature = "uds"))] extern crate tokio_uds; -extern crate url; #[cfg(test)] #[macro_use] diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs deleted file mode 100644 index 1866f29b4..000000000 --- a/tests/test_h1v2.rs +++ /dev/null @@ -1,46 +0,0 @@ -extern crate actix; -extern crate actix_http; -extern crate actix_net; -extern crate actix_web; -extern crate futures; - -use std::thread; - -use actix::System; -use actix_net::server::Server; -use actix_web::{client, test}; -use futures::future; - -use actix_http::{h1, Error, KeepAlive, Response, ServiceConfig}; - -#[test] -fn test_h1_v2() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - let settings = ServiceConfig::build() - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_disconnect(1000) - .server_hostname("localhost") - .server_address(addr) - .finish(); - - h1::H1Service::new(settings, |req| { - println!("REQ: {:?}", req); - future::ok::<_, Error>(Response::Ok().finish()) - }) - }).unwrap() - .run(); - }); - - let mut sys = System::new("test"); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } -} diff --git a/tests/test_server.rs b/tests/test_server.rs new file mode 100644 index 000000000..cc21416c0 --- /dev/null +++ b/tests/test_server.rs @@ -0,0 +1,90 @@ +extern crate actix; +extern crate actix_http; +extern crate actix_net; +extern crate actix_web; +extern crate futures; + +use std::{io::Read, io::Write, net, thread, time}; + +use actix::System; +use actix_net::server::Server; +use actix_web::{client, test}; +use futures::future; + +use actix_http::{h1, Error, KeepAlive, Response, ServiceConfig}; + +#[test] +fn test_h1_v2() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + let settings = ServiceConfig::build() + .keep_alive(KeepAlive::Disabled) + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .server_address(addr) + .finish(); + + h1::H1Service::new(settings, |_| { + future::ok::<_, Error>(Response::Ok().finish()) + }) + }).unwrap() + .run(); + }); + + let mut sys = System::new("test"); + { + let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + } +} + +#[test] +fn test_slow_request() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + let settings = ServiceConfig::build().client_timeout(100).finish(); + + h1::H1Service::new(settings, |_| { + future::ok::<_, Error>(Response::Ok().finish()) + }) + }).unwrap() + .run(); + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut stream = net::TcpStream::connect(addr).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); +} + +#[test] +fn test_malformed_request() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + let settings = ServiceConfig::build().client_timeout(100).finish(); + h1::H1Service::new(settings, |_| { + future::ok::<_, Error>(Response::Ok().finish()) + }) + }).unwrap() + .run(); + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut stream = net::TcpStream::connect(addr).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP1.1\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 400 Bad Request")); +} From 25af82c45a01d97ae65011665fa819c0edaa31df Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Oct 2018 21:14:02 -0700 Subject: [PATCH 023/208] cleanup dependencies --- .travis.yml | 17 +++-------------- Cargo.toml | 35 +---------------------------------- src/lib.rs | 14 -------------- src/response.rs | 4 +--- 4 files changed, 5 insertions(+), 65 deletions(-) diff --git a/.travis.yml b/.travis.yml index aa8a44f25..feae30596 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,17 +14,6 @@ matrix: allow_failures: - rust: nightly -env: - global: - # - RUSTFLAGS="-C link-dead-code" - - OPENSSL_VERSION=openssl-1.0.2 - -before_install: - - sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl - - sudo apt-get update -qq - - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev - -# Add clippy before_script: - export PATH=$PATH:~/.cargo/bin @@ -32,12 +21,12 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then cargo clean - cargo test --features="ssl,tls,rust-tls" -- --nocapture + cargo test fi - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin - RUST_BACKTRACE=1 cargo tarpaulin --features="ssl,tls,rust-tls" --out Xml + RUST_BACKTRACE=1 cargo tarpaulin --out Xml bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi @@ -46,7 +35,7 @@ script: #after_success: # - | # if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then -# cargo doc --features "ssl,tls,rust-tls,session" --no-deps && +# cargo doc --features "session" --no-deps && # echo "" > target/doc/index.html && # git clone https://github.com/davisp/ghp-import.git && # ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && diff --git a/Cargo.toml b/Cargo.toml index 48f199ed8..4e66ff0ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,13 +10,12 @@ repository = "https://github.com/actix/actix-web.git" documentation = "https://actix.rs/api/actix-web/stable/actix_web/" categories = ["network-programming", "asynchronous", "web-programming::http-server", - "web-programming::http-client", "web-programming::websocket"] license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] [package.metadata.docs.rs] -features = ["tls", "alpn", "rust-tls", "session", "brotli", "flate2-c"] +features = ["session"] [badges] travis-ci = { repository = "actix/actix-web", branch = "master" } @@ -30,18 +29,6 @@ path = "src/lib.rs" [features] default = ["session", "cell"] -# native-tls -tls = ["native-tls", "tokio-tls", "actix-net/tls"] - -# openssl -ssl = ["openssl", "tokio-openssl", "actix-net/ssl"] - -# rustls -rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots", "actix-net/rust-tls"] - -# unix sockets -uds = ["tokio-uds"] - # sessions feature, session require "ring" crate and c compiler session = ["cookie/secure"] @@ -82,31 +69,11 @@ tokio-tcp = "0.1" tokio-timer = "0.2" tokio-current-thread = "0.1" -# native-tls -native-tls = { version="0.2", optional = true } -tokio-tls = { version="0.2", optional = true } - -# openssl -openssl = { version="0.10", optional = true } -tokio-openssl = { version="0.2", optional = true } - -#rustls -rustls = { version = "0.14", optional = true } -tokio-rustls = { version = "0.8", optional = true } -webpki = { version = "0.18", optional = true } -webpki-roots = { version = "0.15", optional = true } - -# unix sockets -tokio-uds = { version="0.2", optional = true } - [dev-dependencies] actix-web = "0.7" env_logger = "0.5" serde_derive = "1.0" -[build-dependencies] -version_check = "0.1" - [profile.release] lto = true opt-level = 3 diff --git a/src/lib.rs b/src/lib.rs index 4ec097266..8a7bcfa43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,21 +63,9 @@ //! //! ## Package feature //! -//! * `tls` - enables ssl support via `native-tls` crate -//! * `ssl` - enables ssl support via `openssl` crate, supports `http/2` -//! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2` -//! * `uds` - enables support for making client requests via Unix Domain Sockets. -//! Unix only. Not necessary for *serving* requests. //! * `session` - enables session support, includes `ring` crate as //! dependency -//! * `brotli` - enables `brotli` compression support, requires `c` -//! compiler -//! * `flate2-c` - enables `gzip`, `deflate` compression support, requires -//! `c` compiler -//! * `flate2-rust` - experimental rust based implementation for -//! `gzip`, `deflate` compression. //! -#![cfg_attr(actix_nightly, feature(tool_lints))] // #![warn(missing_docs)] #![allow(dead_code)] @@ -114,8 +102,6 @@ extern crate tokio_current_thread; extern crate tokio_io; extern crate tokio_tcp; extern crate tokio_timer; -#[cfg(all(unix, feature = "uds"))] -extern crate tokio_uds; #[cfg(test)] #[macro_use] diff --git a/src/response.rs b/src/response.rs index d0136b408..e834748ef 100644 --- a/src/response.rs +++ b/src/response.rs @@ -942,7 +942,7 @@ mod tests { use http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; use header::ContentEncoding; - use test::TestRequest; + // use test::TestRequest; #[test] fn test_debug() { @@ -1118,8 +1118,6 @@ mod tests { #[test] fn test_into_response() { - let req = TestRequest::default().finish(); - let resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( From dda5b399ca56e966c34908756879f9489ac70481 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Oct 2018 21:32:01 -0700 Subject: [PATCH 024/208] add content-length test --- tests/test_server.rs | 58 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/tests/test_server.rs b/tests/test_server.rs index cc21416c0..d0f364b68 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -8,10 +8,10 @@ use std::{io::Read, io::Write, net, thread, time}; use actix::System; use actix_net::server::Server; -use actix_web::{client, test}; +use actix_web::{client, test, HttpMessage}; use futures::future; -use actix_http::{h1, Error, KeepAlive, Response, ServiceConfig}; +use actix_http::{h1, Error, KeepAlive, Request, Response, ServiceConfig}; #[test] fn test_h1_v2() { @@ -88,3 +88,57 @@ fn test_malformed_request() { let _ = stream.read_to_string(&mut data); assert!(data.starts_with("HTTP/1.1 400 Bad Request")); } + +#[test] +fn test_content_length() { + use actix_http::http::{ + header::{HeaderName, HeaderValue}, + StatusCode, + }; + + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + let settings = ServiceConfig::build().client_timeout(100).finish(); + h1::H1Service::new(settings, |req: Request| { + let indx: usize = req.uri().path()[1..].parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + future::ok::<_, Error>(Response::new(statuses[indx])) + }) + }).unwrap() + .run(); + }); + thread::sleep(time::Duration::from_millis(100)); + + let header = HeaderName::from_static("content-length"); + let value = HeaderValue::from_static("0"); + + let mut sys = System::new("test"); + { + for i in 0..4 { + let req = + client::ClientRequest::get(format!("http://{}/{}", addr, i).as_str()) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert_eq!(response.headers().get(&header), None); + } + + for i in 4..6 { + let req = + client::ClientRequest::get(format!("http://{}/{}", addr, i).as_str()) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert_eq!(response.headers().get(&header), Some(&value)); + } + } +} From b0ca6220f07fafd9862542cb703cefc1753c16e9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Oct 2018 22:36:57 -0700 Subject: [PATCH 025/208] refactor te encoding --- src/error.rs | 6 +- src/h1/codec.rs | 32 +++-------- src/h1/dispatcher.rs | 132 +++++++++++++++++++++---------------------- src/h1/service.rs | 13 +++-- 4 files changed, 83 insertions(+), 100 deletions(-) diff --git a/src/error.rs b/src/error.rs index 26b3ca56d..277814d2d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -374,7 +374,7 @@ impl ResponseError for cookie::ParseError { #[derive(Debug)] /// A set of errors that can occur during dispatching http requests -pub enum DispatchError { +pub enum DispatchError { /// Service error // #[fail(display = "Application specific error: {}", _0)] Service(E), @@ -413,13 +413,13 @@ pub enum DispatchError { Unknown, } -impl From for DispatchError { +impl From for DispatchError { fn from(err: ParseError) -> Self { DispatchError::Parse(err) } } -impl From for DispatchError { +impl From for DispatchError { fn from(err: io::Error) -> Self { DispatchError::Io(err) } diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 8ab8f2528..247b0f01c 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -54,7 +54,6 @@ pub struct Codec { // encoder part flags: Flags, - written: u64, headers_size: u32, te: ResponseEncoder, } @@ -82,31 +81,30 @@ impl Codec { version: Version::HTTP_11, flags, - written: 0, headers_size: 0, te: ResponseEncoder::default(), } } - fn written(&self) -> u64 { - self.written - } - + /// Check if request is upgrade pub fn upgrade(&self) -> bool { self.flags.contains(Flags::UPGRADE) } + /// Check if last response is keep-alive pub fn keepalive(&self) -> bool { self.flags.contains(Flags::KEEPALIVE) } + /// prepare transfer encoding + pub fn prepare_te(&mut self, res: &mut Response) { + self.te + .update(res, self.flags.contains(Flags::HEAD), self.version); + } + fn encode_response( &mut self, mut msg: Response, buffer: &mut BytesMut, ) -> io::Result<()> { - // prepare transfer encoding - self.te - .update(&mut msg, self.flags.contains(Flags::HEAD), self.version); - let ka = self.flags.contains(Flags::KEEPALIVE_ENABLED) && msg .keep_alive() .unwrap_or_else(|| self.flags.contains(Flags::KEEPALIVE)); @@ -131,12 +129,11 @@ impl Codec { msg.headers_mut() .insert(CONNECTION, HeaderValue::from_static("close")); } - let body = msg.replace_body(Body::Empty); // render message { let reason = msg.reason().as_bytes(); - if let Body::Binary(ref bytes) = body { + if let Body::Binary(ref bytes) = msg.body() { buffer.reserve( 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len() @@ -229,16 +226,6 @@ impl Codec { self.headers_size = buffer.len() as u32; } - if let Body::Binary(bytes) = body { - self.written = bytes.len() as u64; - // buffer.write(bytes.as_ref())?; - buffer.extend_from_slice(bytes.as_ref()); - } else { - // capacity, makes sense only for streaming or actor - // self.buffer_capacity = msg.write_buffer_capacity(); - - msg.replace_body(body); - } Ok(()) } } @@ -282,7 +269,6 @@ impl Encoder for Codec { ) -> Result<(), Self::Error> { match item { OutMessage::Response(res) => { - self.written = 0; self.encode_response(res, dst)?; } OutMessage::Payload(bytes) => { diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index a39967a22..7b6d31fe6 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -1,15 +1,15 @@ use std::collections::VecDeque; -use std::fmt::{Debug, Display}; use std::time::Instant; use actix_net::codec::Framed; use actix_net::service::Service; use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; +use log::Level::Debug; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; -use error::{ParseError, PayloadError}; +use error::{Error, ParseError, PayloadError}; use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; use body::Body; @@ -38,7 +38,7 @@ bitflags! { /// Dispatcher for HTTP/1.1 protocol pub struct Dispatcher where - S::Error: Debug + Display, + S::Error: Into, { service: S, flags: Flags, @@ -81,7 +81,7 @@ impl Dispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug + Display, + S::Error: Into, { /// Create http/1 dispatcher. pub fn new(stream: T, config: ServiceConfig, service: S) -> Self { @@ -177,52 +177,34 @@ where State::None => loop { break if let Some(msg) = self.messages.pop_front() { match msg { - Message::Item(msg) => { - let mut task = self.service.call(msg); - match task.poll() { - Ok(Async::Ready(res)) => { - if res.body().is_streaming() { - unimplemented!() - } else { - Some(Ok(State::SendResponse(Some( - OutMessage::Response(res), - )))) - } - } - Ok(Async::NotReady) => { - Some(Ok(State::Response(task))) - } - Err(err) => Some(Err(DispatchError::Service(err))), - } - } - Message::Error(res) => Some(Ok(State::SendResponse(Some( + Message::Item(req) => Some(self.handle_request(req)), + Message::Error(res) => Some(State::SendResponse(Some( OutMessage::Response(res), - )))), + ))), } } else { None }; }, State::Payload(ref mut _body) => unimplemented!(), - State::Response(ref mut fut) => { - match fut.poll() { - Ok(Async::Ready(res)) => { - if res.body().is_streaming() { - unimplemented!() - } else { - Some(Ok(State::SendResponse(Some( - OutMessage::Response(res), - )))) - } - } - Ok(Async::NotReady) => None, - Err(err) => { - // it is not possible to recover from error - // during pipe handling, so just drop connection - Some(Err(DispatchError::Service(err))) + State::Response(ref mut fut) => match fut.poll() { + Ok(Async::Ready(mut res)) => { + self.framed.get_codec_mut().prepare_te(&mut res); + if res.body().is_streaming() { + unimplemented!() + } else { + Some(State::SendResponse(Some(OutMessage::Response(res)))) } } - } + Ok(Async::NotReady) => None, + Err(err) => { + let err = err.into(); + if log_enabled!(Debug) { + debug!("{:?}", err); + } + Some(State::SendResponse(Some(OutMessage::Response(err.into())))) + } + }, State::SendResponse(ref mut item) => { let msg = item.take().expect("SendResponse is empty"); match self.framed.start_send(msg) { @@ -232,13 +214,19 @@ where self.framed.get_codec().keepalive(), ); self.flags.remove(Flags::FLUSHED); - Some(Ok(State::None)) + Some(State::None) } Ok(AsyncSink::NotReady(msg)) => { *item = Some(msg); return Ok(()); } - Err(err) => Some(Err(DispatchError::Io(err))), + Err(err) => { + self.flags.insert(Flags::READ_DISCONNECTED); + if let Some(mut payload) = self.payload.take() { + payload.set_error(PayloadError::Incomplete); + } + return Err(DispatchError::Io(err)); + } } } State::SendResponseWithPayload(ref mut item) => { @@ -251,23 +239,25 @@ where self.framed.get_codec().keepalive(), ); self.flags.remove(Flags::FLUSHED); - Some(Ok(State::Payload(body))) + Some(State::Payload(body)) } Ok(AsyncSink::NotReady(msg)) => { *item = Some((msg, body)); return Ok(()); } - Err(err) => Some(Err(DispatchError::Io(err))), + Err(err) => { + self.flags.insert(Flags::READ_DISCONNECTED); + if let Some(mut payload) = self.payload.take() { + payload.set_error(PayloadError::Incomplete); + } + return Err(DispatchError::Io(err)); + } } } }; match state { - Some(Ok(state)) => self.state = state, - Some(Err(err)) => { - self.client_disconnected(); - return Err(err); - } + Some(state) => self.state = state, None => { // if read-backpressure is enabled and we consumed some data. // we may read more dataand retry @@ -283,6 +273,28 @@ where Ok(()) } + fn handle_request(&mut self, req: Request) -> State { + let mut task = self.service.call(req); + match task.poll() { + Ok(Async::Ready(mut res)) => { + self.framed.get_codec_mut().prepare_te(&mut res); + if res.body().is_streaming() { + unimplemented!() + } else { + State::SendResponse(Some(OutMessage::Response(res))) + } + } + Ok(Async::NotReady) => State::Response(task), + Err(err) => { + let err = err.into(); + if log_enabled!(Debug) { + debug!("{:?}", err); + } + State::SendResponse(Some(OutMessage::Response(err.into()))) + } + } + } + fn one_message(&mut self, msg: InMessage) -> Result<(), DispatchError> { self.flags.insert(Flags::STARTED); @@ -290,23 +302,7 @@ where InMessage::Message(msg) => { // handle request early if self.state.is_empty() { - let mut task = self.service.call(msg); - match task.poll() { - Ok(Async::Ready(res)) => { - if res.body().is_streaming() { - unimplemented!() - } else { - self.state = - State::SendResponse(Some(OutMessage::Response(res))); - } - } - Ok(Async::NotReady) => self.state = State::Response(task), - Err(err) => { - error!("Unhandled application error: {}", err); - self.client_disconnected(); - return Err(DispatchError::Service(err)); - } - } + self.state = self.handle_request(msg); } else { self.messages.push_back(Message::Item(msg)); } @@ -449,7 +445,7 @@ impl Future for Dispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug + Display, + S::Error: Into, { type Item = (); type Error = DispatchError; diff --git a/src/h1/service.rs b/src/h1/service.rs index 02535fc8c..3ac073ad5 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -1,4 +1,3 @@ -use std::fmt::{Debug, Display}; use std::marker::PhantomData; use actix_net::codec::Framed; @@ -7,7 +6,7 @@ use futures::{future, Async, Future, Poll, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; use config::ServiceConfig; -use error::{DispatchError, ParseError}; +use error::{DispatchError, Error, ParseError}; use request::Request; use response::Response; @@ -24,6 +23,8 @@ pub struct H1Service { impl H1Service where S: NewService, + S::Service: Clone, + S::Error: Into, { /// Create new `HttpService` instance. pub fn new>(cfg: ServiceConfig, service: F) -> Self { @@ -40,7 +41,7 @@ where T: AsyncRead + AsyncWrite, S: NewService + Clone, S::Service: Clone, - S::Error: Debug + Display, + S::Error: Into, { type Request = T; type Response = (); @@ -69,7 +70,7 @@ where T: AsyncRead + AsyncWrite, S: NewService, S::Service: Clone, - S::Error: Debug + Display, + S::Error: Into, { type Item = H1ServiceHandler; type Error = S::InitError; @@ -93,7 +94,7 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where S: Service + Clone, - S::Error: Debug + Display, + S::Error: Into, { fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { H1ServiceHandler { @@ -108,7 +109,7 @@ impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, S: Service + Clone, - S::Error: Debug + Display, + S::Error: Into, { type Request = T; type Response = (); From 8d85c45c1d1369f7e357e9f3f19c4beb75284740 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Oct 2018 00:04:38 -0700 Subject: [PATCH 026/208] simplify error handling --- src/body.rs | 6 ++-- src/error.rs | 6 ++-- src/h1/dispatcher.rs | 69 ++++++++++++++++++++++---------------------- src/h1/service.rs | 13 +++++---- 4 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/body.rs b/src/body.rs index c10b067a2..c78ea8172 100644 --- a/src/body.rs +++ b/src/body.rs @@ -69,10 +69,10 @@ impl Body { /// Is this binary body. #[inline] - pub(crate) fn binary(self) -> Binary { + pub(crate) fn into_binary(self) -> Option { match self { - Body::Binary(b) => b, - _ => panic!(), + Body::Binary(b) => Some(b), + _ => None, } } } diff --git a/src/error.rs b/src/error.rs index 277814d2d..1e60c3486 100644 --- a/src/error.rs +++ b/src/error.rs @@ -374,7 +374,7 @@ impl ResponseError for cookie::ParseError { #[derive(Debug)] /// A set of errors that can occur during dispatching http requests -pub enum DispatchError { +pub enum DispatchError { /// Service error // #[fail(display = "Application specific error: {}", _0)] Service(E), @@ -413,13 +413,13 @@ pub enum DispatchError { Unknown, } -impl From for DispatchError { +impl From for DispatchError { fn from(err: ParseError) -> Self { DispatchError::Parse(err) } } -impl From for DispatchError { +impl From for DispatchError { fn from(err: io::Error) -> Self { DispatchError::Io(err) } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 7b6d31fe6..3bf17a8b3 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -1,15 +1,15 @@ use std::collections::VecDeque; +use std::fmt::{Debug, Display}; use std::time::Instant; use actix_net::codec::Framed; use actix_net::service::Service; use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; -use log::Level::Debug; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; -use error::{Error, ParseError, PayloadError}; +use error::{ParseError, PayloadError}; use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; use body::Body; @@ -38,7 +38,7 @@ bitflags! { /// Dispatcher for HTTP/1.1 protocol pub struct Dispatcher where - S::Error: Into, + S::Error: Debug + Display, { service: S, flags: Flags, @@ -61,7 +61,7 @@ enum Message { enum State { None, - Response(S::Future), + ServiceCall(S::Future), SendResponse(Option), SendResponseWithPayload(Option<(OutMessage, Body)>), Payload(Body), @@ -81,7 +81,7 @@ impl Dispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Into, + S::Error: Debug + Display, { /// Create http/1 dispatcher. pub fn new(stream: T, config: ServiceConfig, service: S) -> Self { @@ -177,7 +177,7 @@ where State::None => loop { break if let Some(msg) = self.messages.pop_front() { match msg { - Message::Item(req) => Some(self.handle_request(req)), + Message::Item(req) => Some(self.handle_request(req)?), Message::Error(res) => Some(State::SendResponse(Some( OutMessage::Response(res), ))), @@ -187,23 +187,23 @@ where }; }, State::Payload(ref mut _body) => unimplemented!(), - State::Response(ref mut fut) => match fut.poll() { - Ok(Async::Ready(mut res)) => { + State::ServiceCall(ref mut fut) => match fut + .poll() + .map_err(DispatchError::Service)? + { + Async::Ready(mut res) => { self.framed.get_codec_mut().prepare_te(&mut res); - if res.body().is_streaming() { - unimplemented!() - } else { + let body = res.replace_body(Body::Empty); + if body.is_empty() { Some(State::SendResponse(Some(OutMessage::Response(res)))) + } else { + Some(State::SendResponseWithPayload(Some(( + OutMessage::Response(res), + body, + )))) } } - Ok(Async::NotReady) => None, - Err(err) => { - let err = err.into(); - if log_enabled!(Debug) { - debug!("{:?}", err); - } - Some(State::SendResponse(Some(OutMessage::Response(err.into())))) - } + Async::NotReady => None, }, State::SendResponse(ref mut item) => { let msg = item.take().expect("SendResponse is empty"); @@ -273,25 +273,24 @@ where Ok(()) } - fn handle_request(&mut self, req: Request) -> State { + fn handle_request( + &mut self, req: Request, + ) -> Result, DispatchError> { let mut task = self.service.call(req); - match task.poll() { - Ok(Async::Ready(mut res)) => { + match task.poll().map_err(DispatchError::Service)? { + Async::Ready(mut res) => { self.framed.get_codec_mut().prepare_te(&mut res); - if res.body().is_streaming() { - unimplemented!() + let body = res.replace_body(Body::Empty); + if body.is_empty() { + Ok(State::SendResponse(Some(OutMessage::Response(res)))) } else { - State::SendResponse(Some(OutMessage::Response(res))) + Ok(State::SendResponseWithPayload(Some(( + OutMessage::Response(res), + body, + )))) } } - Ok(Async::NotReady) => State::Response(task), - Err(err) => { - let err = err.into(); - if log_enabled!(Debug) { - debug!("{:?}", err); - } - State::SendResponse(Some(OutMessage::Response(err.into()))) - } + Async::NotReady => Ok(State::ServiceCall(task)), } } @@ -302,7 +301,7 @@ where InMessage::Message(msg) => { // handle request early if self.state.is_empty() { - self.state = self.handle_request(msg); + self.state = self.handle_request(msg)?; } else { self.messages.push_back(Message::Item(msg)); } @@ -445,7 +444,7 @@ impl Future for Dispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Into, + S::Error: Debug + Display, { type Item = (); type Error = DispatchError; diff --git a/src/h1/service.rs b/src/h1/service.rs index 3ac073ad5..aa59614d3 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -1,3 +1,4 @@ +use std::fmt::{Debug, Display}; use std::marker::PhantomData; use actix_net::codec::Framed; @@ -6,7 +7,7 @@ use futures::{future, Async, Future, Poll, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; use config::ServiceConfig; -use error::{DispatchError, Error, ParseError}; +use error::{DispatchError, ParseError}; use request::Request; use response::Response; @@ -24,7 +25,7 @@ impl H1Service where S: NewService, S::Service: Clone, - S::Error: Into, + S::Error: Debug + Display, { /// Create new `HttpService` instance. pub fn new>(cfg: ServiceConfig, service: F) -> Self { @@ -41,7 +42,7 @@ where T: AsyncRead + AsyncWrite, S: NewService + Clone, S::Service: Clone, - S::Error: Into, + S::Error: Debug + Display, { type Request = T; type Response = (); @@ -70,7 +71,7 @@ where T: AsyncRead + AsyncWrite, S: NewService, S::Service: Clone, - S::Error: Into, + S::Error: Debug + Display, { type Item = H1ServiceHandler; type Error = S::InitError; @@ -94,7 +95,7 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where S: Service + Clone, - S::Error: Into, + S::Error: Debug + Display, { fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { H1ServiceHandler { @@ -109,7 +110,7 @@ impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, S: Service + Clone, - S::Error: Into, + S::Error: Debug + Display, { type Request = T; type Response = (); From 9c4a55c95c106abf8edebad1a5db8188d0bea57a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Oct 2018 08:28:38 -0700 Subject: [PATCH 027/208] simplify H1Service configuration --- src/h1/service.rs | 136 ++++++++++++++++++++++++++++++++++++++++++- tests/test_server.rs | 28 ++++----- 2 files changed, 144 insertions(+), 20 deletions(-) diff --git a/src/h1/service.rs b/src/h1/service.rs index aa59614d3..eea0e6d9f 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -1,12 +1,13 @@ use std::fmt::{Debug, Display}; use std::marker::PhantomData; +use std::net; use actix_net::codec::Framed; use actix_net::service::{IntoNewService, NewService, Service}; use futures::{future, Async, Future, Poll, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; -use config::ServiceConfig; +use config::{KeepAlive, ServiceConfig}; use error::{DispatchError, ParseError}; use request::Request; use response::Response; @@ -28,13 +29,20 @@ where S::Error: Debug + Display, { /// Create new `HttpService` instance. - pub fn new>(cfg: ServiceConfig, service: F) -> Self { + pub fn new>(service: F) -> Self { + let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); + H1Service { cfg, srv: service.into_new_service(), _t: PhantomData, } } + + /// Create builder for `HttpService` instance. + pub fn build() -> H1ServiceBuilder { + H1ServiceBuilder::new() + } } impl NewService for H1Service @@ -60,6 +68,130 @@ where } } +/// A http/1 new service builder +/// +/// This type can be used to construct an instance of `ServiceConfig` through a +/// builder-like pattern. +pub struct H1ServiceBuilder { + keep_alive: KeepAlive, + client_timeout: u64, + client_disconnect: u64, + host: String, + addr: net::SocketAddr, + secure: bool, + _t: PhantomData<(T, S)>, +} + +impl H1ServiceBuilder +where + S: NewService, + S::Service: Clone, + S::Error: Debug + Display, +{ + /// Create instance of `ServiceConfigBuilder` + pub fn new() -> H1ServiceBuilder { + H1ServiceBuilder { + keep_alive: KeepAlive::Timeout(5), + client_timeout: 5000, + client_disconnect: 0, + secure: false, + host: "localhost".to_owned(), + addr: "127.0.0.1:8080".parse().unwrap(), + _t: PhantomData, + } + } + + /// Enable secure flag for current server. + /// This flags also enables `client disconnect timeout`. + /// + /// By default this flag is set to false. + pub fn secure(mut self) -> Self { + self.secure = true; + if self.client_disconnect == 0 { + self.client_disconnect = 3000; + } + self + } + + /// Set server keep-alive setting. + /// + /// By default keep alive is set to a 5 seconds. + pub fn keep_alive>(mut self, val: U) -> Self { + self.keep_alive = val.into(); + self + } + + /// Set server client timeout in milliseconds for first request. + /// + /// Defines a timeout for reading client request header. If a client does not transmit + /// the entire set headers within this time, the request is terminated with + /// the 408 (Request Time-out) error. + /// + /// To disable timeout set value to 0. + /// + /// By default client timeout is set to 5000 milliseconds. + pub fn client_timeout(mut self, val: u64) -> Self { + self.client_timeout = val; + self + } + + /// Set server connection disconnect timeout in milliseconds. + /// + /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete + /// within this time, the request get dropped. This timeout affects secure connections. + /// + /// To disable timeout set value to 0. + /// + /// By default disconnect timeout is set to 3000 milliseconds. + pub fn client_disconnect(mut self, val: u64) -> Self { + self.client_disconnect = val; + self + } + + /// Set server host name. + /// + /// Host name is used by application router aa a hostname for url + /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. + /// html#method.host) documentation for more information. + /// + /// By default host name is set to a "localhost" value. + pub fn server_hostname(mut self, val: &str) -> Self { + self.host = val.to_owned(); + self + } + + /// Set server ip address. + /// + /// Host name is used by application router aa a hostname for url + /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. + /// html#method.host) documentation for more information. + /// + /// By default server address is set to a "127.0.0.1:8080" + pub fn server_address(mut self, addr: U) -> Self { + match addr.to_socket_addrs() { + Err(err) => error!("Can not convert to SocketAddr: {}", err), + Ok(mut addrs) => if let Some(addr) = addrs.next() { + self.addr = addr; + }, + } + self + } + + /// Finish service configuration and create `H1Service` instance. + pub fn finish>(self, service: F) -> H1Service { + let cfg = ServiceConfig::new( + self.keep_alive, + self.client_timeout, + self.client_disconnect, + ); + H1Service { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } +} + pub struct H1ServiceResponse { fut: S::Future, cfg: Option, diff --git a/tests/test_server.rs b/tests/test_server.rs index d0f364b68..8d682e120 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -11,7 +11,7 @@ use actix_net::server::Server; use actix_web::{client, test, HttpMessage}; use futures::future; -use actix_http::{h1, Error, KeepAlive, Request, Response, ServiceConfig}; +use actix_http::{h1, Error, KeepAlive, Request, Response}; #[test] fn test_h1_v2() { @@ -19,17 +19,13 @@ fn test_h1_v2() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - let settings = ServiceConfig::build() + h1::H1Service::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) .client_disconnect(1000) .server_hostname("localhost") .server_address(addr) - .finish(); - - h1::H1Service::new(settings, |_| { - future::ok::<_, Error>(Response::Ok().finish()) - }) + .finish(|_| future::ok::<_, Error>(Response::Ok().finish())) }).unwrap() .run(); }); @@ -50,11 +46,9 @@ fn test_slow_request() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - let settings = ServiceConfig::build().client_timeout(100).finish(); - - h1::H1Service::new(settings, |_| { - future::ok::<_, Error>(Response::Ok().finish()) - }) + h1::H1Service::build() + .client_timeout(100) + .finish(|_| future::ok::<_, Error>(Response::Ok().finish())) }).unwrap() .run(); }); @@ -73,10 +67,9 @@ fn test_malformed_request() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - let settings = ServiceConfig::build().client_timeout(100).finish(); - h1::H1Service::new(settings, |_| { - future::ok::<_, Error>(Response::Ok().finish()) - }) + h1::H1Service::build() + .client_timeout(100) + .finish(|_| future::ok::<_, Error>(Response::Ok().finish())) }).unwrap() .run(); }); @@ -100,8 +93,7 @@ fn test_content_length() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - let settings = ServiceConfig::build().client_timeout(100).finish(); - h1::H1Service::new(settings, |req: Request| { + h1::H1Service::new(|req: Request| { let indx: usize = req.uri().path()[1..].parse().unwrap(); let statuses = [ StatusCode::NO_CONTENT, From 13193a0721110f0ad0dd046af76480322ae46430 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Oct 2018 09:48:53 -0700 Subject: [PATCH 028/208] refactor http/1 dispatcher --- src/error.rs | 15 ++---- src/h1/decoder.rs | 16 +++---- src/h1/dispatcher.rs | 111 ++++++++++++++++++------------------------- src/h1/service.rs | 14 +++--- src/payload.rs | 7 --- tests/test_server.rs | 11 +++-- 6 files changed, 68 insertions(+), 106 deletions(-) diff --git a/src/error.rs b/src/error.rs index 1e60c3486..465b8ae0a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -341,15 +341,6 @@ pub enum PayloadError { /// A payload length is unknown. #[fail(display = "A payload length is unknown.")] UnknownLength, - /// Io error - #[fail(display = "{}", _0)] - Io(#[cause] IoError), -} - -impl From for PayloadError { - fn from(err: IoError) -> PayloadError { - PayloadError::Io(err) - } } /// `PayloadError` returns two possible results: @@ -374,7 +365,7 @@ impl ResponseError for cookie::ParseError { #[derive(Debug)] /// A set of errors that can occur during dispatching http requests -pub enum DispatchError { +pub enum DispatchError { /// Service error // #[fail(display = "Application specific error: {}", _0)] Service(E), @@ -413,13 +404,13 @@ pub enum DispatchError { Unknown, } -impl From for DispatchError { +impl From for DispatchError { fn from(err: ParseError) -> Self { DispatchError::Parse(err) } } -impl From for DispatchError { +impl From for DispatchError { fn from(err: io::Error) -> Self { DispatchError::Io(err) } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index fb29f033c..d0c3fa048 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -219,9 +219,7 @@ impl PayloadDecoder { } pub fn eof() -> PayloadDecoder { - PayloadDecoder { - kind: Kind::Eof(false), - } + PayloadDecoder { kind: Kind::Eof } } } @@ -246,7 +244,7 @@ enum Kind { /// > the final encoding, the message body length cannot be determined /// > reliably; the server MUST respond with the 400 (Bad Request) /// > status code and then close the connection. - Eof(bool), + Eof, } #[derive(Debug, PartialEq, Clone)] @@ -309,13 +307,11 @@ impl Decoder for PayloadDecoder { } } } - Kind::Eof(ref mut is_eof) => { - if *is_eof { - Ok(Some(PayloadItem::Eof)) - } else if !src.is_empty() { - Ok(Some(PayloadItem::Chunk(src.take().freeze()))) - } else { + Kind::Eof => { + if src.is_empty() { Ok(None) + } else { + Ok(Some(PayloadItem::Chunk(src.take().freeze()))) } } } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 3bf17a8b3..92bec3544 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -1,5 +1,5 @@ use std::collections::VecDeque; -use std::fmt::{Debug, Display}; +use std::fmt::Debug; use std::time::Instant; use actix_net::codec::Framed; @@ -27,18 +27,17 @@ bitflags! { const STARTED = 0b0000_0001; const KEEPALIVE_ENABLED = 0b0000_0010; const KEEPALIVE = 0b0000_0100; - const SHUTDOWN = 0b0000_1000; - const READ_DISCONNECTED = 0b0001_0000; - const WRITE_DISCONNECTED = 0b0010_0000; - const POLLED = 0b0100_0000; - const FLUSHED = 0b1000_0000; + const POLLED = 0b0000_1000; + const FLUSHED = 0b0001_0000; + const SHUTDOWN = 0b0010_0000; + const DISCONNECTED = 0b0100_0000; } } /// Dispatcher for HTTP/1.1 protocol pub struct Dispatcher where - S::Error: Debug + Display, + S::Error: Debug, { service: S, flags: Flags, @@ -81,7 +80,7 @@ impl Dispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug + Display, + S::Error: Debug, { /// Create http/1 dispatcher. pub fn new(stream: T, config: ServiceConfig, service: S) -> Self { @@ -122,9 +121,8 @@ where } } - #[inline] fn can_read(&self) -> bool { - if self.flags.contains(Flags::READ_DISCONNECTED) { + if self.flags.contains(Flags::DISCONNECTED) { return false; } @@ -137,7 +135,7 @@ where // if checked is set to true, delay disconnect until all tasks have finished. fn client_disconnected(&mut self) { - self.flags.insert(Flags::READ_DISCONNECTED); + self.flags.insert(Flags::DISCONNECTED); if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete); } @@ -145,12 +143,11 @@ where /// Flush stream fn poll_flush(&mut self) -> Poll<(), DispatchError> { - if self.flags.contains(Flags::STARTED) && !self.flags.contains(Flags::FLUSHED) { + if !self.flags.contains(Flags::FLUSHED) { match self.framed.poll_complete() { Ok(Async::NotReady) => Ok(Async::NotReady), Err(err) => { debug!("Error sending data: {}", err); - self.client_disconnected(); Err(err.into()) } Ok(Async::Ready(_)) => { @@ -167,8 +164,7 @@ where } } - pub(self) fn poll_handler(&mut self) -> Result<(), DispatchError> { - self.poll_io()?; + fn poll_response(&mut self) -> Result<(), DispatchError> { let mut retry = self.can_read(); // process @@ -221,7 +217,6 @@ where return Ok(()); } Err(err) => { - self.flags.insert(Flags::READ_DISCONNECTED); if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete); } @@ -246,7 +241,6 @@ where return Ok(()); } Err(err) => { - self.flags.insert(Flags::READ_DISCONNECTED); if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete); } @@ -261,7 +255,7 @@ where None => { // if read-backpressure is enabled and we consumed some data. // we may read more dataand retry - if !retry && self.can_read() && self.poll_io()? { + if !retry && self.can_read() && self.poll_request()? { retry = self.can_read(); continue; } @@ -319,7 +313,7 @@ where payload.feed_data(chunk); } else { error!("Internal server error: unexpected payload chunk"); - self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); + self.flags.insert(Flags::DISCONNECTED); self.messages.push_back(Message::Error( Response::InternalServerError().finish(), )); @@ -331,7 +325,7 @@ where payload.feed_eof(); } else { error!("Internal server error: unexpected eof"); - self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); + self.flags.insert(Flags::DISCONNECTED); self.messages.push_back(Message::Error( Response::InternalServerError().finish(), )); @@ -343,7 +337,7 @@ where Ok(()) } - pub(self) fn poll_io(&mut self) -> Result> { + pub(self) fn poll_request(&mut self) -> Result> { let mut updated = false; if self.messages.len() < MAX_PIPELINED_MESSAGES { @@ -354,26 +348,25 @@ where self.one_message(msg)?; } Ok(Async::Ready(None)) => { - if self.flags.contains(Flags::READ_DISCONNECTED) { - self.client_disconnected(); - } + self.client_disconnected(); break; } Ok(Async::NotReady) => break, + Err(ParseError::Io(e)) => { + self.client_disconnected(); + self.error = Some(DispatchError::Io(e)); + break; + } Err(e) => { if let Some(mut payload) = self.payload.take() { - let e = match e { - ParseError::Io(e) => PayloadError::Io(e), - _ => PayloadError::EncodingCorrupted, - }; - payload.set_error(e); + payload.set_error(PayloadError::EncodingCorrupted); } // Malformed requests should be responded with 400 self.messages .push_back(Message::Error(Response::BadRequest().finish())); - self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - self.error = Some(DispatchError::MalformedRequest); + self.flags.insert(Flags::DISCONNECTED); + self.error = Some(e.into()); break; } } @@ -402,8 +395,7 @@ where } else if !self.flags.contains(Flags::STARTED) { // timeout on first request (slow request) return 408 trace!("Slow request timeout"); - self.flags - .insert(Flags::STARTED | Flags::READ_DISCONNECTED); + self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); self.state = State::SendResponse(Some(OutMessage::Response( Response::RequestTimeout().finish(), @@ -444,54 +436,43 @@ impl Future for Dispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug + Display, + S::Error: Debug, { type Item = (); type Error = DispatchError; #[inline] fn poll(&mut self) -> Poll<(), Self::Error> { - self.poll_keepalive()?; - - // shutdown if self.flags.contains(Flags::SHUTDOWN) { - if self.flags.contains(Flags::WRITE_DISCONNECTED) { - return Ok(Async::Ready(())); - } + self.poll_keepalive()?; try_ready!(self.poll_flush()); - return Ok(AsyncWrite::shutdown(self.framed.get_mut())?); - } - - // process incoming requests - if !self.flags.contains(Flags::WRITE_DISCONNECTED) { - self.poll_handler()?; - - // flush stream + Ok(AsyncWrite::shutdown(self.framed.get_mut())?) + } else { + self.poll_keepalive()?; + self.poll_request()?; + self.poll_response()?; self.poll_flush()?; - // deal with keep-alive and stream eof (client-side write shutdown) + // keep-alive and stream errors if self.state.is_empty() && self.flags.contains(Flags::FLUSHED) { - // handle stream eof - if self - .flags - .intersects(Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED) - { - return Ok(Async::Ready(())); + if let Some(err) = self.error.take() { + Err(err) + } else if self.flags.contains(Flags::DISCONNECTED) { + Ok(Async::Ready(())) } - // no keep-alive - if self.flags.contains(Flags::STARTED) - && (!self.flags.contains(Flags::KEEPALIVE_ENABLED) - || !self.flags.contains(Flags::KEEPALIVE)) + // disconnect if keep-alive is not enabled + else if self.flags.contains(Flags::STARTED) && !self + .flags + .intersects(Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED) { self.flags.insert(Flags::SHUTDOWN); - return self.poll(); + self.poll() + } else { + Ok(Async::NotReady) } + } else { + Ok(Async::NotReady) } - Ok(Async::NotReady) - } else if let Some(err) = self.error.take() { - Err(err) - } else { - Ok(Async::Ready(())) } } } diff --git a/src/h1/service.rs b/src/h1/service.rs index eea0e6d9f..de24f52c1 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -1,4 +1,4 @@ -use std::fmt::{Debug, Display}; +use std::fmt::Debug; use std::marker::PhantomData; use std::net; @@ -26,7 +26,7 @@ impl H1Service where S: NewService, S::Service: Clone, - S::Error: Debug + Display, + S::Error: Debug, { /// Create new `HttpService` instance. pub fn new>(service: F) -> Self { @@ -50,7 +50,7 @@ where T: AsyncRead + AsyncWrite, S: NewService + Clone, S::Service: Clone, - S::Error: Debug + Display, + S::Error: Debug, { type Request = T; type Response = (); @@ -86,7 +86,7 @@ impl H1ServiceBuilder where S: NewService, S::Service: Clone, - S::Error: Debug + Display, + S::Error: Debug, { /// Create instance of `ServiceConfigBuilder` pub fn new() -> H1ServiceBuilder { @@ -203,7 +203,7 @@ where T: AsyncRead + AsyncWrite, S: NewService, S::Service: Clone, - S::Error: Debug + Display, + S::Error: Debug, { type Item = H1ServiceHandler; type Error = S::InitError; @@ -227,7 +227,7 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where S: Service + Clone, - S::Error: Debug + Display, + S::Error: Debug, { fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { H1ServiceHandler { @@ -242,7 +242,7 @@ impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, S: Service + Clone, - S::Error: Debug + Display, + S::Error: Debug, { type Request = T; type Response = (); diff --git a/src/payload.rs b/src/payload.rs index 3f51f6ec0..54539c408 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -522,18 +522,11 @@ where #[cfg(test)] mod tests { use super::*; - use failure::Fail; use futures::future::{lazy, result}; - use std::io; use tokio::runtime::current_thread::Runtime; #[test] fn test_error() { - let err: PayloadError = - io::Error::new(io::ErrorKind::Other, "ParseError").into(); - assert_eq!(format!("{}", err), "ParseError"); - assert_eq!(format!("{}", err.cause().unwrap()), "ParseError"); - let err = PayloadError::Incomplete; assert_eq!( format!("{}", err), diff --git a/tests/test_server.rs b/tests/test_server.rs index 8d682e120..43e3966df 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -11,7 +11,7 @@ use actix_net::server::Server; use actix_web::{client, test, HttpMessage}; use futures::future; -use actix_http::{h1, Error, KeepAlive, Request, Response}; +use actix_http::{h1, KeepAlive, Request, Response}; #[test] fn test_h1_v2() { @@ -25,10 +25,11 @@ fn test_h1_v2() { .client_disconnect(1000) .server_hostname("localhost") .server_address(addr) - .finish(|_| future::ok::<_, Error>(Response::Ok().finish())) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) }).unwrap() .run(); }); + thread::sleep(time::Duration::from_millis(100)); let mut sys = System::new("test"); { @@ -48,7 +49,7 @@ fn test_slow_request() { .bind("test", addr, move || { h1::H1Service::build() .client_timeout(100) - .finish(|_| future::ok::<_, Error>(Response::Ok().finish())) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) }).unwrap() .run(); }); @@ -69,7 +70,7 @@ fn test_malformed_request() { .bind("test", addr, move || { h1::H1Service::build() .client_timeout(100) - .finish(|_| future::ok::<_, Error>(Response::Ok().finish())) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) }).unwrap() .run(); }); @@ -103,7 +104,7 @@ fn test_content_length() { StatusCode::OK, StatusCode::NOT_FOUND, ]; - future::ok::<_, Error>(Response::new(statuses[indx])) + future::ok::<_, ()>(Response::new(statuses[indx])) }) }).unwrap() .run(); From 8acf9eb98a505eb5f16d72af5424763989599c51 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Oct 2018 10:09:48 -0700 Subject: [PATCH 029/208] better keep-alive handling --- src/h1/dispatcher.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 92bec3544..d44e687d1 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -386,21 +386,13 @@ where if let Some(ref mut timer) = self.ka_timer { match timer.poll() { Ok(Async::Ready(_)) => { - if timer.deadline() >= self.ka_expire { - // check for any outstanding request handling - if self.state.is_empty() && self.messages.is_empty() { - // if we get timer during shutdown, just drop connection - if self.flags.contains(Flags::SHUTDOWN) { - return Err(DispatchError::DisconnectTimeout); - } else if !self.flags.contains(Flags::STARTED) { - // timeout on first request (slow request) return 408 - trace!("Slow request timeout"); - self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); - self.state = - State::SendResponse(Some(OutMessage::Response( - Response::RequestTimeout().finish(), - ))); - } else { + // if we get timer during shutdown, just drop connection + if self.flags.contains(Flags::SHUTDOWN) { + return Err(DispatchError::DisconnectTimeout); + } else if timer.deadline() >= self.ka_expire { + // check for any outstanding response processing + if self.state.is_empty() { + if self.flags.contains(Flags::STARTED) { trace!("Keep-alive timeout, close connection"); self.flags.insert(Flags::SHUTDOWN); @@ -412,6 +404,14 @@ where } else { return Ok(()); } + } else { + // timeout on first request (slow request) return 408 + trace!("Slow request timeout"); + self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); + self.state = + State::SendResponse(Some(OutMessage::Response( + Response::RequestTimeout().finish(), + ))); } } else if let Some(deadline) = self.config.keep_alive_expire() { timer.reset(deadline) From 30db78c19cf8c3e65a4cfefdeb6f8ccf15cec921 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 07:55:01 -0700 Subject: [PATCH 030/208] use TakeItem instead of TakeRequest --- src/h1/mod.rs | 3 +- src/h1/service.rs | 81 ++--------------------------------------------- tests/test_ws.rs | 3 +- 3 files changed, 6 insertions(+), 81 deletions(-) diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 266ebf39c..634136a47 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -6,5 +6,6 @@ mod encoder; mod service; pub use self::codec::{Codec, InMessage, OutMessage}; +pub use self::decoder::{PayloadDecoder, RequestDecoder}; pub use self::dispatcher::Dispatcher; -pub use self::service::{H1Service, H1ServiceHandler, TakeRequest}; +pub use self::service::{H1Service, H1ServiceHandler}; diff --git a/src/h1/service.rs b/src/h1/service.rs index de24f52c1..a7261df17 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -2,17 +2,15 @@ use std::fmt::Debug; use std::marker::PhantomData; use std::net; -use actix_net::codec::Framed; use actix_net::service::{IntoNewService, NewService, Service}; -use futures::{future, Async, Future, Poll, Stream}; +use futures::{Async, Future, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; use config::{KeepAlive, ServiceConfig}; -use error::{DispatchError, ParseError}; +use error::DispatchError; use request::Request; use response::Response; -use super::codec::{Codec, InMessage}; use super::dispatcher::Dispatcher; /// `NewService` implementation for HTTP1 transport @@ -257,78 +255,3 @@ where Dispatcher::new(req, self.cfg.clone(), self.srv.clone()) } } - -/// `NewService` that implements, read one request from framed object feature. -pub struct TakeRequest { - _t: PhantomData, -} - -impl TakeRequest { - /// Create new `TakeRequest` instance. - pub fn new() -> Self { - TakeRequest { _t: PhantomData } - } -} - -impl NewService for TakeRequest -where - T: AsyncRead + AsyncWrite, -{ - type Request = Framed; - type Response = (Option, Framed); - type Error = ParseError; - type InitError = (); - type Service = TakeRequestService; - type Future = future::FutureResult; - - fn new_service(&self) -> Self::Future { - future::ok(TakeRequestService { _t: PhantomData }) - } -} - -/// `NewService` that implements, read one request from framed object feature. -pub struct TakeRequestService { - _t: PhantomData, -} - -impl Service for TakeRequestService -where - T: AsyncRead + AsyncWrite, -{ - type Request = Framed; - type Response = (Option, Framed); - type Error = ParseError; - type Future = TakeRequestServiceResponse; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, framed: Self::Request) -> Self::Future { - TakeRequestServiceResponse { - framed: Some(framed), - } - } -} - -pub struct TakeRequestServiceResponse -where - T: AsyncRead + AsyncWrite, -{ - framed: Option>, -} - -impl Future for TakeRequestServiceResponse -where - T: AsyncRead + AsyncWrite, -{ - type Item = (Option, Framed); - type Error = ParseError; - - fn poll(&mut self) -> Poll { - match self.framed.as_mut().unwrap().poll()? { - Async::Ready(item) => Ok(Async::Ready((item, self.framed.take().unwrap()))), - Async::NotReady => Ok(Async::NotReady), - } - } -} diff --git a/tests/test_ws.rs b/tests/test_ws.rs index a2a18ff28..8a1098747 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -12,6 +12,7 @@ use actix_net::codec::Framed; use actix_net::framed::IntoFramed; use actix_net::server::Server; use actix_net::service::NewServiceExt; +use actix_net::stream::TakeItem; use actix_web::{test, ws as web_ws}; use bytes::Bytes; use futures::future::{ok, Either}; @@ -36,7 +37,7 @@ fn test_simple() { Server::new() .bind("test", addr, move || { IntoFramed::new(|| h1::Codec::new(false)) - .and_then(h1::TakeRequest::new().map_err(|_| ())) + .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(req, framed): (_, Framed<_, _>)| { // validate request if let Some(h1::InMessage::MessageWithPayload(req)) = req { From 431e33acb2a73245dd7d99e9876d5ee37028c951 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 10:14:29 -0700 Subject: [PATCH 031/208] add Date header to response --- src/config.rs | 140 +++++++++++++++++++++++++------------------ src/h1/codec.rs | 15 ++--- src/h1/dispatcher.rs | 2 +- src/lib.rs | 9 +-- 4 files changed, 91 insertions(+), 75 deletions(-) diff --git a/src/config.rs b/src/config.rs index 4e85044f1..2e14a33e6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -48,7 +48,7 @@ struct Inner { client_timeout: u64, client_disconnect: u64, ka_enabled: bool, - date: UnsafeCell<(bool, Date)>, + timer: DateService, } impl Clone for ServiceConfig { @@ -78,7 +78,7 @@ impl ServiceConfig { ka_enabled, client_timeout, client_disconnect, - date: UnsafeCell::new((false, Date::new())), + timer: DateService::with(Duration::from_millis(500)), })) } @@ -99,17 +99,14 @@ impl ServiceConfig { self.0.ka_enabled } - fn update_date(&self) { - // Unsafe: WorkerSetting is !Sync and !Send - unsafe { (*self.0.date.get()).0 = false }; - } - #[inline] /// Client timeout for first request. pub fn client_timer(&self) -> Option { let delay = self.0.client_timeout; if delay != 0 { - Some(Delay::new(self.now() + Duration::from_millis(delay))) + Some(Delay::new( + self.0.timer.now() + Duration::from_millis(delay), + )) } else { None } @@ -119,7 +116,7 @@ impl ServiceConfig { pub fn client_timer_expire(&self) -> Option { let delay = self.0.client_timeout; if delay != 0 { - Some(self.now() + Duration::from_millis(delay)) + Some(self.0.timer.now() + Duration::from_millis(delay)) } else { None } @@ -129,7 +126,7 @@ impl ServiceConfig { pub fn client_disconnect_timer(&self) -> Option { let delay = self.0.client_disconnect; if delay != 0 { - Some(self.now() + Duration::from_millis(delay)) + Some(self.0.timer.now() + Duration::from_millis(delay)) } else { None } @@ -139,7 +136,7 @@ impl ServiceConfig { /// Return keep-alive timer delay is configured. pub fn keep_alive_timer(&self) -> Option { if let Some(ka) = self.0.keep_alive { - Some(Delay::new(self.now() + ka)) + Some(Delay::new(self.0.timer.now() + ka)) } else { None } @@ -148,57 +145,23 @@ impl ServiceConfig { /// Keep-alive expire time pub fn keep_alive_expire(&self) -> Option { if let Some(ka) = self.0.keep_alive { - Some(self.now() + ka) + Some(self.0.timer.now() + ka) } else { None } } - pub(crate) fn set_date(&self, dst: &mut BytesMut, full: bool) { - // Unsafe: WorkerSetting is !Sync and !Send - let date_bytes = unsafe { - let date = &mut (*self.0.date.get()); - if !date.0 { - date.1.update(); - date.0 = true; - - // periodic date update - let s = self.clone(); - spawn(sleep(Duration::from_millis(500)).then(move |_| { - s.update_date(); - future::ok(()) - })); - } - &date.1.bytes - }; - if full { - let mut buf: [u8; 39] = [0; 39]; - buf[..6].copy_from_slice(b"date: "); - buf[6..35].copy_from_slice(date_bytes); - buf[35..].copy_from_slice(b"\r\n\r\n"); - dst.extend_from_slice(&buf); - } else { - dst.extend_from_slice(date_bytes); - } - } - #[inline] pub(crate) fn now(&self) -> Instant { - unsafe { - let date = &mut (*self.0.date.get()); - if !date.0 { - date.1.update(); - date.0 = true; + self.0.timer.now() + } - // periodic date update - let s = self.clone(); - spawn(sleep(Duration::from_millis(500)).then(move |_| { - s.update_date(); - future::ok(()) - })); - } - date.1.current - } + pub(crate) fn set_date(&self, dst: &mut BytesMut) { + let mut buf: [u8; 39] = [0; 39]; + buf[..6].copy_from_slice(b"date: "); + buf[6..35].copy_from_slice(&self.0.timer.date().bytes); + buf[35..].copy_from_slice(b"\r\n\r\n"); + dst.extend_from_slice(&buf); } } @@ -311,7 +274,6 @@ impl ServiceConfigBuilder { } struct Date { - current: Instant, bytes: [u8; DATE_VALUE_LENGTH], pos: usize, } @@ -319,7 +281,6 @@ struct Date { impl Date { fn new() -> Date { let mut date = Date { - current: Instant::now(), bytes: [0; DATE_VALUE_LENGTH], pos: 0, }; @@ -328,7 +289,6 @@ impl Date { } fn update(&mut self) { self.pos = 0; - self.current = Instant::now(); write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap(); } } @@ -342,6 +302,68 @@ impl fmt::Write for Date { } } +#[derive(Clone)] +struct DateService(Rc); + +struct DateServiceInner { + interval: Duration, + current: UnsafeCell>, +} + +impl DateServiceInner { + fn new(interval: Duration) -> Self { + DateServiceInner { + interval, + current: UnsafeCell::new(None), + } + } + + fn get_ref(&self) -> &Option<(Date, Instant)> { + unsafe { &*self.current.get() } + } + + fn reset(&self) { + unsafe { (&mut *self.current.get()).take() }; + } + + fn update(&self) { + let now = Instant::now(); + let date = Date::new(); + *(unsafe { &mut *self.current.get() }) = Some((date, now)); + } +} + +impl DateService { + fn with(resolution: Duration) -> Self { + DateService(Rc::new(DateServiceInner::new(resolution))) + } + + fn check_date(&self) { + if self.0.get_ref().is_none() { + self.0.update(); + + // periodic date update + let s = self.clone(); + spawn(sleep(Duration::from_millis(500)).then(move |_| { + s.0.reset(); + future::ok(()) + })); + } + } + + fn now(&self) -> Instant { + self.check_date(); + self.0.get_ref().as_ref().unwrap().1 + } + + fn date(&self) -> &Date { + self.check_date(); + + let item = self.0.get_ref().as_ref().unwrap(); + &item.0 + } +} + #[cfg(test)] mod tests { use super::*; @@ -360,9 +382,9 @@ mod tests { let _ = rt.block_on(future::lazy(|| { let settings = ServiceConfig::new(KeepAlive::Os, 0, 0); let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf1, true); + settings.set_date(&mut buf1); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf2, true); + settings.set_date(&mut buf2); assert_eq!(buf1, buf2); future::ok::<_, ()>(()) })); diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 247b0f01c..d0faad43f 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -7,6 +7,7 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::{PayloadDecoder, PayloadItem, RequestDecoder}; use super::encoder::{ResponseEncoder, ResponseLength}; use body::Body; +use config::ServiceConfig; use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; @@ -48,6 +49,7 @@ pub enum InMessage { /// HTTP/1 Codec pub struct Codec { + config: ServiceConfig, decoder: RequestDecoder, payload: Option, version: Version, @@ -62,20 +64,19 @@ impl Codec { /// Create HTTP/1 codec. /// /// `keepalive_enabled` how response `connection` header get generated. - pub fn new(keepalive_enabled: bool) -> Self { - Codec::with_pool(RequestPool::pool(), keepalive_enabled) + pub fn new(config: ServiceConfig) -> Self { + Codec::with_pool(RequestPool::pool(), config) } /// Create HTTP/1 codec with request's pool - pub(crate) fn with_pool( - pool: &'static RequestPool, keepalive_enabled: bool, - ) -> Self { - let flags = if keepalive_enabled { + pub(crate) fn with_pool(pool: &'static RequestPool, config: ServiceConfig) -> Self { + let flags = if config.keep_alive_enabled() { Flags::KEEPALIVE_ENABLED } else { Flags::empty() }; Codec { + config, decoder: RequestDecoder::with_pool(pool), payload: None, version: Version::HTTP_11, @@ -217,7 +218,7 @@ impl Codec { // optimized date header, set_date writes \r\n if !has_date { - // self.settings.set_date(&mut buffer, true); + self.config.set_date(buffer); buffer.extend_from_slice(b"\r\n"); } else { // msg eof diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index d44e687d1..c8ce7d65e 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -97,7 +97,7 @@ where } else { Flags::FLUSHED }; - let framed = Framed::new(stream, Codec::new(keepalive)); + let framed = Framed::new(stream, Codec::new(config.clone())); let (ka_expire, ka_timer) = if let Some(delay) = timeout { (delay.deadline(), Some(delay)) diff --git a/src/lib.rs b/src/lib.rs index 8a7bcfa43..85bf9c2ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,17 +48,10 @@ //! //! ## Features //! -//! * Supported *HTTP/1.x* and *HTTP/2.0* protocols +//! * Supported *HTTP/1.x* protocol //! * Streaming and pipelining //! * Keep-alive and slow requests handling //! * `WebSockets` server/client -//! * Transparent content compression/decompression (br, gzip, deflate) -//! * Configurable request routing -//! * Graceful server shutdown -//! * Multipart streams -//! * SSL support with OpenSSL or `native-tls` -//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`) -//! * Built on top of [Actix actor framework](https://github.com/actix/actix) //! * Supported Rust version: 1.26 or later //! //! ## Package feature From 805e7a4cd042f305175a8436ed7c0b6a8802ff99 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 15:24:51 -0700 Subject: [PATCH 032/208] impl response body support --- src/h1/codec.rs | 24 +++- src/h1/dispatcher.rs | 134 ++++++++++-------- src/h1/encoder.rs | 46 ++++--- tests/test_server.rs | 314 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 427 insertions(+), 91 deletions(-) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index d0faad43f..16965768f 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -6,7 +6,7 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::{PayloadDecoder, PayloadItem, RequestDecoder}; use super::encoder::{ResponseEncoder, ResponseLength}; -use body::Body; +use body::{Binary, Body}; use config::ServiceConfig; use error::ParseError; use helpers; @@ -26,12 +26,13 @@ bitflags! { const AVERAGE_HEADER_SIZE: usize = 30; +#[derive(Debug)] /// Http response pub enum OutMessage { /// Http response message Response(Response), /// Payload chunk - Payload(Bytes), + Payload(Option), } /// Incoming http/1 request @@ -151,6 +152,7 @@ impl Codec { buffer.extend_from_slice(reason); // content length + let mut len_is_set = true; match self.te.length { ResponseLength::Chunked => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") @@ -167,6 +169,10 @@ impl Codec { buffer.extend_from_slice(b"\r\n"); } ResponseLength::None => buffer.extend_from_slice(b"\r\n"), + ResponseLength::HeaderOrZero => { + len_is_set = false; + buffer.extend_from_slice(b"\r\n") + } } // write headers @@ -179,6 +185,9 @@ impl Codec { TRANSFER_ENCODING => continue, CONTENT_LENGTH => match self.te.length { ResponseLength::None => (), + ResponseLength::HeaderOrZero => { + len_is_set = true; + } _ => continue, }, DATE => { @@ -215,11 +224,13 @@ impl Codec { unsafe { buffer.advance_mut(pos); } + if !len_is_set { + buffer.extend_from_slice(b"content-length: 0\r\n") + } // optimized date header, set_date writes \r\n if !has_date { self.config.set_date(buffer); - buffer.extend_from_slice(b"\r\n"); } else { // msg eof buffer.extend_from_slice(b"\r\n"); @@ -272,8 +283,11 @@ impl Encoder for Codec { OutMessage::Response(res) => { self.encode_response(res, dst)?; } - OutMessage::Payload(bytes) => { - dst.extend_from_slice(&bytes); + OutMessage::Payload(Some(bytes)) => { + self.te.encode(bytes.as_ref(), dst)?; + } + OutMessage::Payload(None) => { + self.te.encode_eof(dst)?; } } Ok(()) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index c8ce7d65e..c2ce12037 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -12,7 +12,7 @@ use tokio_timer::Delay; use error::{ParseError, PayloadError}; use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; -use body::Body; +use body::{Body, BodyStream}; use config::ServiceConfig; use error::DispatchError; use request::Request; @@ -61,9 +61,8 @@ enum Message { enum State { None, ServiceCall(S::Future), - SendResponse(Option), - SendResponseWithPayload(Option<(OutMessage, Body)>), - Payload(Body), + SendResponse(Option<(OutMessage, Body)>), + SendPayload(Option, Option), } impl State { @@ -99,6 +98,7 @@ where }; let framed = Framed::new(stream, Codec::new(config.clone())); + // keep-alive timer let (ka_expire, ka_timer) = if let Some(delay) = timeout { (delay.deadline(), Some(delay)) } else if let Some(delay) = config.keep_alive_timer() { @@ -174,59 +174,32 @@ where break if let Some(msg) = self.messages.pop_front() { match msg { Message::Item(req) => Some(self.handle_request(req)?), - Message::Error(res) => Some(State::SendResponse(Some( + Message::Error(res) => Some(State::SendResponse(Some(( OutMessage::Response(res), - ))), + Body::Empty, + )))), } } else { None }; }, - State::Payload(ref mut _body) => unimplemented!(), - State::ServiceCall(ref mut fut) => match fut - .poll() - .map_err(DispatchError::Service)? - { - Async::Ready(mut res) => { - self.framed.get_codec_mut().prepare_te(&mut res); - let body = res.replace_body(Body::Empty); - if body.is_empty() { - Some(State::SendResponse(Some(OutMessage::Response(res)))) - } else { - Some(State::SendResponseWithPayload(Some(( + // call inner service + State::ServiceCall(ref mut fut) => { + match fut.poll().map_err(DispatchError::Service)? { + Async::Ready(mut res) => { + self.framed.get_codec_mut().prepare_te(&mut res); + let body = res.replace_body(Body::Empty); + Some(State::SendResponse(Some(( OutMessage::Response(res), body, )))) } - } - Async::NotReady => None, - }, - State::SendResponse(ref mut item) => { - let msg = item.take().expect("SendResponse is empty"); - match self.framed.start_send(msg) { - Ok(AsyncSink::Ready) => { - self.flags.set( - Flags::KEEPALIVE, - self.framed.get_codec().keepalive(), - ); - self.flags.remove(Flags::FLUSHED); - Some(State::None) - } - Ok(AsyncSink::NotReady(msg)) => { - *item = Some(msg); - return Ok(()); - } - Err(err) => { - if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete); - } - return Err(DispatchError::Io(err)); - } + Async::NotReady => None, } } - State::SendResponseWithPayload(ref mut item) => { - let (msg, body) = - item.take().expect("SendResponseWithPayload is empty"); + // send respons + State::SendResponse(ref mut item) => { + let (msg, body) = item.take().expect("SendResponse is empty"); match self.framed.start_send(msg) { Ok(AsyncSink::Ready) => { self.flags.set( @@ -234,7 +207,16 @@ where self.framed.get_codec().keepalive(), ); self.flags.remove(Flags::FLUSHED); - Some(State::Payload(body)) + match body { + Body::Empty => Some(State::None), + Body::Binary(bin) => Some(State::SendPayload( + None, + Some(OutMessage::Payload(bin.into())), + )), + Body::Streaming(stream) => { + Some(State::SendPayload(Some(stream), None)) + } + } } Ok(AsyncSink::NotReady(msg)) => { *item = Some((msg, body)); @@ -248,6 +230,48 @@ where } } } + // Send payload + State::SendPayload(ref mut stream, ref mut bin) => { + if let Some(item) = bin.take() { + match self.framed.start_send(item) { + Ok(AsyncSink::Ready) => { + self.flags.remove(Flags::FLUSHED); + } + Ok(AsyncSink::NotReady(item)) => { + *bin = Some(item); + return Ok(()); + } + Err(err) => return Err(DispatchError::Io(err)), + } + } + if let Some(ref mut stream) = stream { + match stream.poll() { + Ok(Async::Ready(Some(item))) => match self + .framed + .start_send(OutMessage::Payload(Some(item.into()))) + { + Ok(AsyncSink::Ready) => { + self.flags.remove(Flags::FLUSHED); + continue; + } + Ok(AsyncSink::NotReady(msg)) => { + *bin = Some(msg); + return Ok(()); + } + Err(err) => return Err(DispatchError::Io(err)), + }, + Ok(Async::Ready(None)) => Some(State::SendPayload( + None, + Some(OutMessage::Payload(None)), + )), + Ok(Async::NotReady) => return Ok(()), + // Err(err) => return Err(DispatchError::Io(err)), + Err(_) => return Err(DispatchError::Unknown), + } + } else { + Some(State::None) + } + } }; match state { @@ -275,19 +299,13 @@ where Async::Ready(mut res) => { self.framed.get_codec_mut().prepare_te(&mut res); let body = res.replace_body(Body::Empty); - if body.is_empty() { - Ok(State::SendResponse(Some(OutMessage::Response(res)))) - } else { - Ok(State::SendResponseWithPayload(Some(( - OutMessage::Response(res), - body, - )))) - } + Ok(State::SendResponse(Some((OutMessage::Response(res), body)))) } Async::NotReady => Ok(State::ServiceCall(task)), } } + /// Process one incoming message fn one_message(&mut self, msg: InMessage) -> Result<(), DispatchError> { self.flags.insert(Flags::STARTED); @@ -408,10 +426,12 @@ where // timeout on first request (slow request) return 408 trace!("Slow request timeout"); self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); - self.state = - State::SendResponse(Some(OutMessage::Response( + self.state = State::SendResponse(Some(( + OutMessage::Response( Response::RequestTimeout().finish(), - ))); + ), + Body::Empty, + ))); } } else if let Some(deadline) = self.config.keep_alive_expire() { timer.reset(deadline) diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 1544b2404..6e8d44cee 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -17,9 +17,13 @@ use response::Response; #[derive(Debug)] pub(crate) enum ResponseLength { Chunked, + /// Content length is 0 Zero, + /// Check if headers contains length or write 0 + HeaderOrZero, Length(usize), Length64(u64), + /// Do no set content-length None, } @@ -41,6 +45,16 @@ impl Default for ResponseEncoder { } impl ResponseEncoder { + /// Encode message + pub fn encode(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result { + self.te.encode(msg, buf) + } + + /// Encode eof + pub fn encode_eof(&mut self, buf: &mut BytesMut) -> io::Result<()> { + self.te.encode_eof(buf) + } + pub fn update(&mut self, resp: &mut Response, head: bool, version: Version) { self.head = head; @@ -63,17 +77,13 @@ impl ResponseEncoder { let transfer = match resp.body() { Body::Empty => { - if !self.head { - self.length = match resp.status() { - StatusCode::NO_CONTENT - | StatusCode::CONTINUE - | StatusCode::SWITCHING_PROTOCOLS - | StatusCode::PROCESSING => ResponseLength::None, - _ => ResponseLength::Zero, - }; - } else { - self.length = ResponseLength::Zero; - } + self.length = match resp.status() { + StatusCode::NO_CONTENT + | StatusCode::CONTINUE + | StatusCode::SWITCHING_PROTOCOLS + | StatusCode::PROCESSING => ResponseLength::None, + _ => ResponseLength::HeaderOrZero, + }; TransferEncoding::empty() } Body::Binary(_) => { @@ -253,16 +263,22 @@ impl TransferEncoding { /// Encode eof. Return `EOF` state of encoder #[inline] - pub fn encode_eof(&mut self, buf: &mut BytesMut) -> bool { + pub fn encode_eof(&mut self, buf: &mut BytesMut) -> io::Result<()> { match self.kind { - TransferEncodingKind::Eof => true, - TransferEncodingKind::Length(rem) => rem == 0, + TransferEncodingKind::Eof => Ok(()), + TransferEncodingKind::Length(rem) => { + if rem != 0 { + Err(io::Error::new(io::ErrorKind::UnexpectedEof, "")) + } else { + Ok(()) + } + } TransferEncodingKind::Chunked(ref mut eof) => { if !*eof { *eof = true; buf.extend_from_slice(b"0\r\n\r\n"); } - true + Ok(()) } } } diff --git a/tests/test_server.rs b/tests/test_server.rs index 43e3966df..e86176097 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -2,6 +2,7 @@ extern crate actix; extern crate actix_http; extern crate actix_net; extern crate actix_web; +extern crate bytes; extern crate futures; use std::{io::Read, io::Write, net, thread, time}; @@ -9,9 +10,11 @@ use std::{io::Read, io::Write, net, thread, time}; use actix::System; use actix_net::server::Server; use actix_web::{client, test, HttpMessage}; -use futures::future; +use bytes::Bytes; +use futures::future::{self, ok}; +use futures::stream::once; -use actix_http::{h1, KeepAlive, Request, Response}; +use actix_http::{h1, Body, KeepAlive, Request, Response}; #[test] fn test_h1_v2() { @@ -33,7 +36,7 @@ fn test_h1_v2() { let mut sys = System::new("test"); { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + let req = client::ClientRequest::get(format!("http://{}/", addr)) .finish() .unwrap(); let response = sys.block_on(req.send()).unwrap(); @@ -68,9 +71,7 @@ fn test_malformed_request() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - h1::H1Service::build() - .client_timeout(100) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().finish())) }).unwrap() .run(); }); @@ -117,21 +118,306 @@ fn test_content_length() { let mut sys = System::new("test"); { for i in 0..4 { - let req = - client::ClientRequest::get(format!("http://{}/{}", addr, i).as_str()) - .finish() - .unwrap(); + let req = client::ClientRequest::get(format!("http://{}/{}", addr, i)) + .finish() + .unwrap(); let response = sys.block_on(req.send()).unwrap(); assert_eq!(response.headers().get(&header), None); } for i in 4..6 { - let req = - client::ClientRequest::get(format!("http://{}/{}", addr, i).as_str()) - .finish() - .unwrap(); + let req = client::ClientRequest::get(format!("http://{}/{}", addr, i)) + .finish() + .unwrap(); let response = sys.block_on(req.send()).unwrap(); assert_eq!(response.headers().get(&header), Some(&value)); } } } + +#[test] +fn test_headers() { + let data = STR.repeat(10); + let data2 = data.clone(); + + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + let data = data.clone(); + h1::H1Service::new(move |_| { + let mut builder = Response::Ok(); + for idx in 0..90 { + builder.header( + format!("X-TEST-{}", idx).as_str(), + "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", + ); + } + future::ok::<_, ()>(builder.body(data.clone())) + }) + }) + .unwrap() + .run() + }); + thread::sleep(time::Duration::from_millis(400)); + + let mut sys = System::new("test"); + let req = client::ClientRequest::get(format!("http://{}/", addr)) + .finish() + .unwrap(); + + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = sys.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from(data2)); +} + +const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World"; + +#[test] +fn test_body() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + }).unwrap() + .run(); + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let req = client::ClientRequest::get(format!("http://{}/", addr)) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = sys.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_head_empty() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::new(|_| { + ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).finish()) + }) + }).unwrap() + .run() + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let req = client::ClientRequest::head(format!("http://{}/", addr)) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + { + println!("RESP: {:?}", response); + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = sys.block_on(response.body()).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_head_binary() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::new(|_| { + ok::<_, ()>( + Response::Ok().content_length(STR.len() as u64).body(STR), + ) + }) + }).unwrap() + .run() + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let req = client::ClientRequest::head(format!("http://{}/", addr)) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = sys.block_on(response.body()).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_head_binary2() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))) + }).unwrap() + .run() + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let req = client::ClientRequest::head(format!("http://{}/", addr)) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } +} + +#[test] +fn test_body_length() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::new(|_| { + let body = once(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .content_length(STR.len() as u64) + .body(Body::Streaming(Box::new(body))), + ) + }) + }).unwrap() + .run() + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let req = client::ClientRequest::get(format!("http://{}/", addr)) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = sys.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_body_chunked_explicit() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::new(|_| { + let body = once(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .chunked() + .body(Body::Streaming(Box::new(body))), + ) + }) + }).unwrap() + .run() + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let req = client::ClientRequest::get(format!("http://{}/", addr)) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = sys.block_on(response.body()).unwrap(); + + // decode + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_body_chunked_implicit() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::new(|_| { + let body = once(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>(Response::Ok().body(Body::Streaming(Box::new(body)))) + }) + }).unwrap() + .run() + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let req = client::ClientRequest::get(format!("http://{}/", addr)) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = sys.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} From 3984ad45dfb5f13a9e645b0f48265c5ec5b6834f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 15:33:38 -0700 Subject: [PATCH 033/208] separate ResponseLength::Zero is not needed --- src/h1/codec.rs | 9 +++------ src/h1/encoder.rs | 6 ++---- tests/test_server.rs | 7 ++++++- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 16965768f..8f97d6779 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -158,7 +158,8 @@ impl Codec { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } ResponseLength::Zero => { - buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n") + len_is_set = false; + buffer.extend_from_slice(b"\r\n") } ResponseLength::Length(len) => { helpers::write_content_length(len, buffer) @@ -169,10 +170,6 @@ impl Codec { buffer.extend_from_slice(b"\r\n"); } ResponseLength::None => buffer.extend_from_slice(b"\r\n"), - ResponseLength::HeaderOrZero => { - len_is_set = false; - buffer.extend_from_slice(b"\r\n") - } } // write headers @@ -185,7 +182,7 @@ impl Codec { TRANSFER_ENCODING => continue, CONTENT_LENGTH => match self.te.length { ResponseLength::None => (), - ResponseLength::HeaderOrZero => { + ResponseLength::Zero => { len_is_set = true; } _ => continue, diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 6e8d44cee..ea11f11fd 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -17,10 +17,8 @@ use response::Response; #[derive(Debug)] pub(crate) enum ResponseLength { Chunked, - /// Content length is 0 - Zero, /// Check if headers contains length or write 0 - HeaderOrZero, + Zero, Length(usize), Length64(u64), /// Do no set content-length @@ -82,7 +80,7 @@ impl ResponseEncoder { | StatusCode::CONTINUE | StatusCode::SWITCHING_PROTOCOLS | StatusCode::PROCESSING => ResponseLength::None, - _ => ResponseLength::HeaderOrZero, + _ => ResponseLength::Zero, }; TransferEncoding::empty() } diff --git a/tests/test_server.rs b/tests/test_server.rs index e86176097..3b13b98ed 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -123,6 +123,12 @@ fn test_content_length() { .unwrap(); let response = sys.block_on(req.send()).unwrap(); assert_eq!(response.headers().get(&header), None); + + let req = client::ClientRequest::head(format!("http://{}/{}", addr, i)) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert_eq!(response.headers().get(&header), None); } for i in 4..6 { @@ -254,7 +260,6 @@ fn test_head_empty() { assert!(response.status().is_success()); { - println!("RESP: {:?}", response); let len = response .headers() .get(http::header::CONTENT_LENGTH) From f99a723643d3e0618068acb2631331e3c25f6dba Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 15:52:12 -0700 Subject: [PATCH 034/208] add Default impl for ServiceConfig --- src/config.rs | 6 ++++++ tests/test_ws.rs | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 2e14a33e6..ff22ea486 100644 --- a/src/config.rs +++ b/src/config.rs @@ -57,6 +57,12 @@ impl Clone for ServiceConfig { } } +impl Default for ServiceConfig { + fn default() -> Self { + Self::new(KeepAlive::Timeout(5), 0, 0) + } +} + impl ServiceConfig { /// Create instance of `ServiceConfig` pub(crate) fn new( diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 8a1098747..00ccd1558 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -18,7 +18,7 @@ use bytes::Bytes; use futures::future::{ok, Either}; use futures::{Future, Sink, Stream}; -use actix_http::{h1, ws, ResponseError}; +use actix_http::{h1, ws, ResponseError, ServiceConfig}; fn ws_service(req: ws::Message) -> impl Future { match req { @@ -36,7 +36,7 @@ fn test_simple() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - IntoFramed::new(|| h1::Codec::new(false)) + IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(req, framed): (_, Framed<_, _>)| { // validate request From 2b4870e65b70bfd1600eecc3a61f82d437599482 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 16:10:07 -0700 Subject: [PATCH 035/208] fix tests on stable --- tests/test_server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_server.rs b/tests/test_server.rs index 3b13b98ed..f382eafbd 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -14,7 +14,7 @@ use bytes::Bytes; use futures::future::{self, ok}; use futures::stream::once; -use actix_http::{h1, Body, KeepAlive, Request, Response}; +use actix_http::{h1, http, Body, KeepAlive, Request, Response}; #[test] fn test_h1_v2() { From fd5da5945efe16ef6e7f08027312286cc4c2829b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 21:23:52 -0700 Subject: [PATCH 036/208] update appveyor config --- .appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 7addc8c08..4af6cdbf8 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -37,4 +37,5 @@ build: false # Equivalent to Travis' `script` phase test_script: - - cargo test --no-default-features --features="flate2-rust" + - cargo clean + - cargo test From cb78d9d41a4063b81b18d1218819740916693821 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 22:04:53 -0700 Subject: [PATCH 037/208] use actix-net release --- .appveyor.yml | 2 +- Cargo.toml | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 4af6cdbf8..780fdd6b5 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,6 +1,6 @@ environment: global: - PROJECT_NAME: actix + PROJECT_NAME: actix-http matrix: # Stable channel - TARGET: i686-pc-windows-msvc diff --git a/Cargo.toml b/Cargo.toml index 4e66ff0ee..7bfb82fb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,13 +36,14 @@ cell = ["actix-net/cell"] [dependencies] actix = "0.7.0" -actix-net = { git="https://github.com/actix/actix-net.git" } -#actix-net = { path = "../actix-net" } +actix-net = "0.1.0" +#actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" http = "^0.1.8" httparse = "1.3" +failure = "^0.1.2" log = "0.4" mime = "0.3" rand = "0.5" @@ -55,8 +56,6 @@ lazy_static = "1.0" serde_urlencoded = "^0.5.3" cookie = { version="0.11", features=["percent-encode"] } -failure = "^0.1.2" - # io net2 = "0.2" bytes = "0.4" From 1407bf4f7f2a0e0ec6302b792eef45acbc4656bf Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 9 Oct 2018 10:36:40 -0700 Subject: [PATCH 038/208] simplify h1 codec messages --- README.md | 2 +- src/h1/codec.rs | 27 ++++---- src/h1/decoder.rs | 5 +- src/h1/dispatcher.rs | 154 +++++++++++++++++++++---------------------- tests/test_ws.rs | 2 +- 5 files changed, 90 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index b273ea8c5..59e6fc375 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Actix http [![Build Status](https://travis-ci.org/fafhrd91/actix-http.svg?branch=master)](https://travis-ci.org/fafhrd91/actix-http) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/fafhrd91/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/fafhrd91/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/fafhrd91/actix-http.svg?branch=master)](https://travis-ci.org/fafhrd91/actix-http) [![Build status](https://ci.appveyor.com/api/projects/status/bwq6923pblqg55gk/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-http/branch/master) [![codecov](https://codecov.io/gh/fafhrd91/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/fafhrd91/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix http diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 8f97d6779..04cf395b6 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -32,20 +32,16 @@ pub enum OutMessage { /// Http response message Response(Response), /// Payload chunk - Payload(Option), + Chunk(Option), } /// Incoming http/1 request #[derive(Debug)] pub enum InMessage { /// Request - Message(Request), - /// Request with payload - MessageWithPayload(Request), + Message { req: Request, payload: bool }, /// Payload chunk - Chunk(Bytes), - /// End of payload - Eof, + Chunk(Option), } /// HTTP/1 Codec @@ -246,8 +242,8 @@ impl Decoder for Codec { fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { if self.payload.is_some() { Ok(match self.payload.as_mut().unwrap().decode(src)? { - Some(PayloadItem::Chunk(chunk)) => Some(InMessage::Chunk(chunk)), - Some(PayloadItem::Eof) => Some(InMessage::Eof), + Some(PayloadItem::Chunk(chunk)) => Some(InMessage::Chunk(Some(chunk))), + Some(PayloadItem::Eof) => Some(InMessage::Chunk(None)), None => None, }) } else if let Some((req, payload)) = self.decoder.decode(src)? { @@ -258,11 +254,10 @@ impl Decoder for Codec { self.flags.set(Flags::KEEPALIVE, req.keep_alive()); } self.payload = payload; - if self.payload.is_some() { - Ok(Some(InMessage::MessageWithPayload(req))) - } else { - Ok(Some(InMessage::Message(req))) - } + Ok(Some(InMessage::Message { + req, + payload: self.payload.is_some(), + })) } else { Ok(None) } @@ -280,10 +275,10 @@ impl Encoder for Codec { OutMessage::Response(res) => { self.encode_response(res, dst)?; } - OutMessage::Payload(Some(bytes)) => { + OutMessage::Chunk(Some(bytes)) => { self.te.encode(bytes.as_ref(), dst)?; } - OutMessage::Payload(None) => { + OutMessage::Chunk(None) => { self.te.encode_eof(dst)?; } } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index d0c3fa048..5fe8b19c8 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -488,14 +488,13 @@ mod tests { impl InMessage { fn message(self) -> Request { match self { - InMessage::Message(msg) => msg, - InMessage::MessageWithPayload(msg) => msg, + InMessage::Message { req, payload: _ } => req, _ => panic!("error"), } } fn is_payload(&self) -> bool { match *self { - InMessage::MessageWithPayload(_) => true, + InMessage::Message { req: _, payload } => payload, _ => panic!("error"), } } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index c2ce12037..8b7c29331 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -211,7 +211,7 @@ where Body::Empty => Some(State::None), Body::Binary(bin) => Some(State::SendPayload( None, - Some(OutMessage::Payload(bin.into())), + Some(OutMessage::Chunk(bin.into())), )), Body::Streaming(stream) => { Some(State::SendPayload(Some(stream), None)) @@ -248,7 +248,7 @@ where match stream.poll() { Ok(Async::Ready(Some(item))) => match self .framed - .start_send(OutMessage::Payload(Some(item.into()))) + .start_send(OutMessage::Chunk(Some(item.into()))) { Ok(AsyncSink::Ready) => { self.flags.remove(Flags::FLUSHED); @@ -262,7 +262,7 @@ where }, Ok(Async::Ready(None)) => Some(State::SendPayload( None, - Some(OutMessage::Payload(None)), + Some(OutMessage::Chunk(None)), )), Ok(Async::NotReady) => return Ok(()), // Err(err) => return Err(DispatchError::Io(err)), @@ -305,89 +305,85 @@ where } } - /// Process one incoming message - fn one_message(&mut self, msg: InMessage) -> Result<(), DispatchError> { - self.flags.insert(Flags::STARTED); - - match msg { - InMessage::Message(msg) => { - // handle request early - if self.state.is_empty() { - self.state = self.handle_request(msg)?; - } else { - self.messages.push_back(Message::Item(msg)); - } - } - InMessage::MessageWithPayload(msg) => { - // payload - let (ps, pl) = Payload::new(false); - *msg.inner.payload.borrow_mut() = Some(pl); - self.payload = Some(ps); - - self.messages.push_back(Message::Item(msg)); - } - InMessage::Chunk(chunk) => { - if let Some(ref mut payload) = self.payload { - payload.feed_data(chunk); - } else { - error!("Internal server error: unexpected payload chunk"); - self.flags.insert(Flags::DISCONNECTED); - self.messages.push_back(Message::Error( - Response::InternalServerError().finish(), - )); - self.error = Some(DispatchError::InternalError); - } - } - InMessage::Eof => { - if let Some(mut payload) = self.payload.take() { - payload.feed_eof(); - } else { - error!("Internal server error: unexpected eof"); - self.flags.insert(Flags::DISCONNECTED); - self.messages.push_back(Message::Error( - Response::InternalServerError().finish(), - )); - self.error = Some(DispatchError::InternalError); - } - } + /// Process one incoming requests + pub(self) fn poll_request(&mut self) -> Result> { + // limit a mount of non processed requests + if self.messages.len() >= MAX_PIPELINED_MESSAGES { + return Ok(false); } - Ok(()) - } - - pub(self) fn poll_request(&mut self) -> Result> { let mut updated = false; + 'outer: loop { + match self.framed.poll() { + Ok(Async::Ready(Some(msg))) => { + updated = true; + self.flags.insert(Flags::STARTED); - if self.messages.len() < MAX_PIPELINED_MESSAGES { - 'outer: loop { - match self.framed.poll() { - Ok(Async::Ready(Some(msg))) => { - updated = true; - self.one_message(msg)?; - } - Ok(Async::Ready(None)) => { - self.client_disconnected(); - break; - } - Ok(Async::NotReady) => break, - Err(ParseError::Io(e)) => { - self.client_disconnected(); - self.error = Some(DispatchError::Io(e)); - break; - } - Err(e) => { - if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::EncodingCorrupted); + match msg { + InMessage::Message { req, payload } => { + if payload { + let (ps, pl) = Payload::new(false); + *req.inner.payload.borrow_mut() = Some(pl); + self.payload = Some(ps); + } + + // handle request early + if self.state.is_empty() { + self.state = self.handle_request(req)?; + } else { + self.messages.push_back(Message::Item(req)); + } + } + InMessage::Chunk(Some(chunk)) => { + if let Some(ref mut payload) = self.payload { + payload.feed_data(chunk); + } else { + error!( + "Internal server error: unexpected payload chunk" + ); + self.flags.insert(Flags::DISCONNECTED); + self.messages.push_back(Message::Error( + Response::InternalServerError().finish(), + )); + self.error = Some(DispatchError::InternalError); + } + } + InMessage::Chunk(None) => { + if let Some(mut payload) = self.payload.take() { + payload.feed_eof(); + } else { + error!("Internal server error: unexpected eof"); + self.flags.insert(Flags::DISCONNECTED); + self.messages.push_back(Message::Error( + Response::InternalServerError().finish(), + )); + self.error = Some(DispatchError::InternalError); + } } - - // Malformed requests should be responded with 400 - self.messages - .push_back(Message::Error(Response::BadRequest().finish())); - self.flags.insert(Flags::DISCONNECTED); - self.error = Some(e.into()); - break; } } + Ok(Async::Ready(None)) => { + self.client_disconnected(); + break; + } + Ok(Async::NotReady) => break, + Err(ParseError::Io(e)) => { + self.client_disconnected(); + self.error = Some(DispatchError::Io(e)); + break; + } + Err(e) => { + if let Some(mut payload) = self.payload.take() { + payload.set_error(PayloadError::EncodingCorrupted); + } + + // Malformed requests should be responded with 400 + self.messages + .push_back(Message::Error(Response::BadRequest().finish())); + self.flags.insert(Flags::DISCONNECTED); + self.error = Some(e.into()); + break; + } } } diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 00ccd1558..73590990c 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -40,7 +40,7 @@ fn test_simple() { .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(req, framed): (_, Framed<_, _>)| { // validate request - if let Some(h1::InMessage::MessageWithPayload(req)) = req { + if let Some(h1::InMessage::Message { req, payload: _ }) = req { match ws::handshake(&req) { Err(e) => { // validation failed From 4a167dc89e7ce9b0c9fcd239bf7c4f5e17e0e7bc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 9 Oct 2018 10:46:00 -0700 Subject: [PATCH 039/208] update readme example --- README.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 59e6fc375..3cb6f2308 100644 --- a/README.md +++ b/README.md @@ -5,27 +5,28 @@ Actix http ## Documentation & community resources * [User Guide](https://actix.rs/docs/) -* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) -* [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/) +* [API Documentation (Development)](https://actix.rs/actix-http/actix_http/) +* [API Documentation (Releases)](https://actix.rs/api/actix-http/stable/actix_http/) * [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-web](https://crates.io/crates/actix-web) +* Cargo package: [actix-http](https://crates.io/crates/actix-web) * Minimum supported Rust version: 1.26 or later ## Example ```rust -extern crate actix_web; -use actix_web::{http, server, App, Path, Responder}; - -fn index(info: Path<(u32, String)>) -> impl Responder { - format!("Hello {}! id:{}", info.1, info.0) -} +extern crate actix_http; +use actix_http::{h1, Response, ServiceConfig}; fn main() { - server::new( - || App::new() - .route("/{id}/{name}/index.html", http::Method::GET, index)) - .bind("127.0.0.1:8080").unwrap() + Server::new() + .bind("app", addr, move || { + IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) // <- create h1 codec + .and_then(TakeItem::new().map_err(|_| ())) // <- read one request + .and_then(|(req, framed): (_, Framed<_, _>)| { // <- send response and close conn + framed + .send(h1::OutMessage::Response(Response::Ok().finish())) + }) + }) .run(); } ``` @@ -41,6 +42,6 @@ at your option. ## Code of Conduct -Contribution to the actix-web crate is organized under the terms of the -Contributor Covenant, the maintainer of actix-net, @fafhrd91, promises to +Contribution to the actix-http crate is organized under the terms of the +Contributor Covenant, the maintainer of actix-http, @fafhrd91, promises to intervene to uphold that code of conduct. From 47b47af01a6baf8258c1286d3c82f6f28524e2ba Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Oct 2018 13:20:00 -0700 Subject: [PATCH 040/208] refactor ws codec --- src/ws/codec.rs | 74 ++++++++++++++++++++++++++++++-------------- src/ws/frame.rs | 75 +++++++++++++++++++++++++-------------------- src/ws/mod.rs | 4 +-- src/ws/transport.rs | 6 ++-- tests/test_ws.rs | 21 ++++++++++--- 5 files changed, 113 insertions(+), 67 deletions(-) diff --git a/src/ws/codec.rs b/src/ws/codec.rs index 6e2b12090..7ba10672c 100644 --- a/src/ws/codec.rs +++ b/src/ws/codec.rs @@ -1,7 +1,7 @@ use bytes::BytesMut; use tokio_codec::{Decoder, Encoder}; -use super::frame::Frame; +use super::frame::Parser; use super::proto::{CloseReason, OpCode}; use super::ProtocolError; use body::Binary; @@ -21,6 +21,21 @@ pub enum Message { Close(Option), } +/// `WebSocket` frame +#[derive(Debug, PartialEq)] +pub enum Frame { + /// Text frame, codec does not verify utf8 encoding + Text(Option), + /// Binary frame + Binary(Option), + /// Ping message + Ping(String), + /// Pong message + Pong(String), + /// Close message with optional reason + Close(Option), +} + /// WebSockets protocol codec pub struct Codec { max_size: usize, @@ -60,29 +75,29 @@ impl Encoder for Codec { fn encode(&mut self, item: Message, dst: &mut BytesMut) -> Result<(), Self::Error> { match item { Message::Text(txt) => { - Frame::write_message(dst, txt, OpCode::Text, true, !self.server) + Parser::write_message(dst, txt, OpCode::Text, true, !self.server) } Message::Binary(bin) => { - Frame::write_message(dst, bin, OpCode::Binary, true, !self.server) + Parser::write_message(dst, bin, OpCode::Binary, true, !self.server) } Message::Ping(txt) => { - Frame::write_message(dst, txt, OpCode::Ping, true, !self.server) + Parser::write_message(dst, txt, OpCode::Ping, true, !self.server) } Message::Pong(txt) => { - Frame::write_message(dst, txt, OpCode::Pong, true, !self.server) + Parser::write_message(dst, txt, OpCode::Pong, true, !self.server) } - Message::Close(reason) => Frame::write_close(dst, reason, !self.server), + Message::Close(reason) => Parser::write_close(dst, reason, !self.server), } Ok(()) } } impl Decoder for Codec { - type Item = Message; + type Item = Frame; type Error = ProtocolError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - match Frame::parse(src, self.server, self.max_size) { + match Parser::parse(src, self.server, self.max_size) { Ok(Some((finished, opcode, payload))) => { // continuation is not supported if !finished { @@ -93,23 +108,36 @@ impl Decoder for Codec { OpCode::Continue => Err(ProtocolError::NoContinuation), OpCode::Bad => Err(ProtocolError::BadOpCode), OpCode::Close => { - let close_reason = Frame::parse_close_payload(&payload); - Ok(Some(Message::Close(close_reason))) - } - OpCode::Ping => Ok(Some(Message::Ping( - String::from_utf8_lossy(payload.as_ref()).into(), - ))), - OpCode::Pong => Ok(Some(Message::Pong( - String::from_utf8_lossy(payload.as_ref()).into(), - ))), - OpCode::Binary => Ok(Some(Message::Binary(payload))), - OpCode::Text => { - let tmp = Vec::from(payload.as_ref()); - match String::from_utf8(tmp) { - Ok(s) => Ok(Some(Message::Text(s))), - Err(_) => Err(ProtocolError::BadEncoding), + if let Some(ref pl) = payload { + let close_reason = Parser::parse_close_payload(pl); + Ok(Some(Frame::Close(close_reason))) + } else { + Ok(Some(Frame::Close(None))) } } + OpCode::Ping => { + if let Some(ref pl) = payload { + Ok(Some(Frame::Ping(String::from_utf8_lossy(pl).into()))) + } else { + Ok(Some(Frame::Ping(String::new()))) + } + } + OpCode::Pong => { + if let Some(ref pl) = payload { + Ok(Some(Frame::Pong(String::from_utf8_lossy(pl).into()))) + } else { + Ok(Some(Frame::Pong(String::new()))) + } + } + OpCode::Binary => Ok(Some(Frame::Binary(payload))), + OpCode::Text => { + Ok(Some(Frame::Text(payload))) + //let tmp = Vec::from(payload.as_ref()); + //match String::from_utf8(tmp) { + // Ok(s) => Ok(Some(Message::Text(s))), + // Err(_) => Err(ProtocolError::BadEncoding), + //} + } } } Ok(None) => Ok(None), diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 38bebc283..de1b92394 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -9,9 +9,9 @@ use ws::ProtocolError; /// A struct representing a `WebSocket` frame. #[derive(Debug)] -pub struct Frame; +pub struct Parser; -impl Frame { +impl Parser { fn parse_metadata( src: &[u8], server: bool, max_size: usize, ) -> Result)>, ProtocolError> { @@ -87,10 +87,10 @@ impl Frame { /// Parse the input stream into a frame. pub fn parse( src: &mut BytesMut, server: bool, max_size: usize, - ) -> Result, ProtocolError> { + ) -> Result)>, ProtocolError> { // try to parse ws frame metadata let (idx, finished, opcode, length, mask) = - match Frame::parse_metadata(src, server, max_size)? { + match Parser::parse_metadata(src, server, max_size)? { None => return Ok(None), Some(res) => res, }; @@ -105,7 +105,7 @@ impl Frame { // no need for body if length == 0 { - return Ok(Some((finished, opcode, Binary::from("")))); + return Ok(Some((finished, opcode, None))); } let mut data = src.split_to(length); @@ -117,7 +117,7 @@ impl Frame { } OpCode::Close if length > 125 => { debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); - return Ok(Some((true, OpCode::Close, Binary::from("")))); + return Ok(Some((true, OpCode::Close, None))); } _ => (), } @@ -127,16 +127,16 @@ impl Frame { apply_mask(&mut data, mask); } - Ok(Some((finished, opcode, data.into()))) + Ok(Some((finished, opcode, Some(data)))) } /// Parse the payload of a close frame. - pub fn parse_close_payload(payload: &Binary) -> Option { + pub fn parse_close_payload(payload: &[u8]) -> Option { if payload.len() >= 2 { - let raw_code = NetworkEndian::read_u16(payload.as_ref()); + let raw_code = NetworkEndian::read_u16(payload); let code = CloseCode::from(raw_code); let description = if payload.len() > 2 { - Some(String::from_utf8_lossy(&payload.as_ref()[2..]).into()) + Some(String::from_utf8_lossy(&payload[2..]).into()) } else { None }; @@ -203,33 +203,40 @@ impl Frame { } }; - Frame::write_message(dst, payload, OpCode::Close, true, mask) + Parser::write_message(dst, payload, OpCode::Close, true, mask) } } #[cfg(test)] mod tests { use super::*; + use bytes::Bytes; struct F { finished: bool, opcode: OpCode, - payload: Binary, + payload: Bytes, } - fn is_none(frm: &Result, ProtocolError>) -> bool { + fn is_none( + frm: &Result)>, ProtocolError>, + ) -> bool { match *frm { Ok(None) => true, _ => false, } } - fn extract(frm: Result, ProtocolError>) -> F { + fn extract( + frm: Result)>, ProtocolError>, + ) -> F { match frm { Ok(Some((finished, opcode, payload))) => F { finished, opcode, - payload, + payload: payload + .map(|b| b.freeze()) + .unwrap_or_else(|| Bytes::from("")), }, _ => unreachable!("error"), } @@ -238,12 +245,12 @@ mod tests { #[test] fn test_parse() { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); - assert!(is_none(&Frame::parse(&mut buf, false, 1024))); + assert!(is_none(&Parser::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); buf.extend(b"1"); - let frame = extract(Frame::parse(&mut buf, false, 1024)); + let frame = extract(Parser::parse(&mut buf, false, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.payload.as_ref(), &b"1"[..]); @@ -252,7 +259,7 @@ mod tests { #[test] fn test_parse_length0() { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0000u8][..]); - let frame = extract(Frame::parse(&mut buf, false, 1024)); + let frame = extract(Parser::parse(&mut buf, false, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert!(frame.payload.is_empty()); @@ -261,13 +268,13 @@ mod tests { #[test] fn test_parse_length2() { let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); - assert!(is_none(&Frame::parse(&mut buf, false, 1024))); + assert!(is_none(&Parser::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); buf.extend(&[0u8, 4u8][..]); buf.extend(b"1234"); - let frame = extract(Frame::parse(&mut buf, false, 1024)); + let frame = extract(Parser::parse(&mut buf, false, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.payload.as_ref(), &b"1234"[..]); @@ -276,13 +283,13 @@ mod tests { #[test] fn test_parse_length4() { let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); - assert!(is_none(&Frame::parse(&mut buf, false, 1024))); + assert!(is_none(&Parser::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]); buf.extend(b"1234"); - let frame = extract(Frame::parse(&mut buf, false, 1024)); + let frame = extract(Parser::parse(&mut buf, false, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.payload.as_ref(), &b"1234"[..]); @@ -294,12 +301,12 @@ mod tests { buf.extend(b"0001"); buf.extend(b"1"); - assert!(Frame::parse(&mut buf, false, 1024).is_err()); + assert!(Parser::parse(&mut buf, false, 1024).is_err()); - let frame = extract(Frame::parse(&mut buf, true, 1024)); + let frame = extract(Parser::parse(&mut buf, true, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); - assert_eq!(frame.payload, vec![1u8].into()); + assert_eq!(frame.payload, Bytes::from(vec![1u8])); } #[test] @@ -307,12 +314,12 @@ mod tests { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); buf.extend(&[1u8]); - assert!(Frame::parse(&mut buf, true, 1024).is_err()); + assert!(Parser::parse(&mut buf, true, 1024).is_err()); - let frame = extract(Frame::parse(&mut buf, false, 1024)); + let frame = extract(Parser::parse(&mut buf, false, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); - assert_eq!(frame.payload, vec![1u8].into()); + assert_eq!(frame.payload, Bytes::from(vec![1u8])); } #[test] @@ -320,9 +327,9 @@ mod tests { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0010u8][..]); buf.extend(&[1u8, 1u8]); - assert!(Frame::parse(&mut buf, true, 1).is_err()); + assert!(Parser::parse(&mut buf, true, 1).is_err()); - if let Err(ProtocolError::Overflow) = Frame::parse(&mut buf, false, 0) { + if let Err(ProtocolError::Overflow) = Parser::parse(&mut buf, false, 0) { } else { unreachable!("error"); } @@ -331,7 +338,7 @@ mod tests { #[test] fn test_ping_frame() { let mut buf = BytesMut::new(); - Frame::write_message(&mut buf, Vec::from("data"), OpCode::Ping, true, false); + Parser::write_message(&mut buf, Vec::from("data"), OpCode::Ping, true, false); let mut v = vec![137u8, 4u8]; v.extend(b"data"); @@ -341,7 +348,7 @@ mod tests { #[test] fn test_pong_frame() { let mut buf = BytesMut::new(); - Frame::write_message(&mut buf, Vec::from("data"), OpCode::Pong, true, false); + Parser::write_message(&mut buf, Vec::from("data"), OpCode::Pong, true, false); let mut v = vec![138u8, 4u8]; v.extend(b"data"); @@ -352,7 +359,7 @@ mod tests { fn test_close_frame() { let mut buf = BytesMut::new(); let reason = (CloseCode::Normal, "data"); - Frame::write_close(&mut buf, Some(reason.into()), false); + Parser::write_close(&mut buf, Some(reason.into()), false); let mut v = vec![136u8, 6u8, 3u8, 232u8]; v.extend(b"data"); @@ -362,7 +369,7 @@ mod tests { #[test] fn test_empty_close_frame() { let mut buf = BytesMut::new(); - Frame::write_close(&mut buf, None, false); + Parser::write_close(&mut buf, None, false); assert_eq!(&buf[..], &vec![0x88, 0x00][..]); } } diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 5ebb502bb..7df1f4b4d 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -16,8 +16,8 @@ mod mask; mod proto; mod transport; -pub use self::codec::{Codec, Message}; -pub use self::frame::Frame; +pub use self::codec::{Codec, Frame, Message}; +pub use self::frame::Parser; pub use self::proto::{CloseCode, CloseReason, OpCode}; pub use self::transport::Transport; diff --git a/src/ws/transport.rs b/src/ws/transport.rs index aabeb5d5a..102d02b43 100644 --- a/src/ws/transport.rs +++ b/src/ws/transport.rs @@ -4,7 +4,7 @@ use actix_net::service::{IntoService, Service}; use futures::{Future, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; -use super::{Codec, Message}; +use super::{Codec, Frame, Message}; pub struct Transport where @@ -17,7 +17,7 @@ where impl Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { @@ -37,7 +37,7 @@ where impl Future for Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 73590990c..91e212efd 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -20,12 +20,23 @@ use futures::{Future, Sink, Stream}; use actix_http::{h1, ws, ResponseError, ServiceConfig}; -fn ws_service(req: ws::Message) -> impl Future { +fn ws_service(req: ws::Frame) -> impl Future { match req { - ws::Message::Ping(msg) => ok(ws::Message::Pong(msg)), - ws::Message::Text(text) => ok(ws::Message::Text(text)), - ws::Message::Binary(bin) => ok(ws::Message::Binary(bin)), - ws::Message::Close(reason) => ok(ws::Message::Close(reason)), + ws::Frame::Ping(msg) => ok(ws::Message::Pong(msg)), + ws::Frame::Text(text) => { + let text = if let Some(pl) = text { + String::from_utf8(Vec::from(pl.as_ref())).unwrap() + } else { + String::new() + }; + ok(ws::Message::Text(text)) + } + ws::Frame::Binary(bin) => ok(ws::Message::Binary( + bin.map(|e| e.freeze()) + .unwrap_or_else(|| Bytes::from("")) + .into(), + )), + ws::Frame::Close(reason) => ok(ws::Message::Close(reason)), _ => ok(ws::Message::Close(None)), } } From 06addd55232a866dd195149c3622c9b37f5b9ae5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Oct 2018 13:23:25 -0700 Subject: [PATCH 041/208] update deps --- Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7bfb82fb3..9193aeeaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,15 +35,15 @@ session = ["cookie/secure"] cell = ["actix-net/cell"] [dependencies] -actix = "0.7.0" -actix-net = "0.1.0" +actix = "0.7.5" +actix-net = "0.1.1" #actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" -http = "^0.1.8" +http = "0.1.8" httparse = "1.3" -failure = "^0.1.2" +failure = "0.1.2" log = "0.4" mime = "0.3" rand = "0.5" @@ -53,7 +53,7 @@ sha1 = "0.6" time = "0.1" encoding = "0.2" lazy_static = "1.0" -serde_urlencoded = "^0.5.3" +serde_urlencoded = "0.5.3" cookie = { version="0.11", features=["percent-encode"] } # io From b960b5827c844d32bb85ea560d0d9e0783c2aaf7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 11 Oct 2018 20:15:10 -0700 Subject: [PATCH 042/208] export Uri --- src/h1/service.rs | 2 +- src/lib.rs | 5 +++-- src/request.rs | 13 ++++++++----- src/uri.rs | 6 +++--- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/h1/service.rs b/src/h1/service.rs index a7261df17..aa7a51733 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -22,7 +22,7 @@ pub struct H1Service { impl H1Service where - S: NewService, + S: NewService + Clone, S::Service: Clone, S::Error: Debug, { diff --git a/src/lib.rs b/src/lib.rs index 85bf9c2ff..5ce3ec39f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,7 +110,7 @@ mod json; mod payload; mod request; mod response; -mod uri; +pub mod uri; pub mod error; pub mod h1; @@ -148,10 +148,11 @@ pub mod http { //! Various HTTP related types // re-exports + pub use modhttp::header::{HeaderName, HeaderValue}; pub use modhttp::{Method, StatusCode, Version}; #[doc(hidden)] - pub use modhttp::{uri, Error, Extensions, HeaderMap, HttpTryFrom, Uri}; + pub use modhttp::{uri, Error, HeaderMap, HttpTryFrom, Uri}; pub use cookie::{Cookie, CookieBuilder}; diff --git a/src/request.rs b/src/request.rs index 82d8c22fa..ef28e3694 100644 --- a/src/request.rs +++ b/src/request.rs @@ -8,7 +8,7 @@ use http::{header, HeaderMap, Method, Uri, Version}; use extensions::Extensions; use httpmessage::HttpMessage; use payload::Payload; -use uri::Url as InnerUrl; +use uri::Url; bitflags! { pub(crate) struct MessageFlags: u8 { @@ -25,7 +25,7 @@ pub struct Request { pub(crate) struct InnerRequest { pub(crate) version: Version, pub(crate) method: Method, - pub(crate) url: InnerUrl, + pub(crate) url: Url, pub(crate) flags: Cell, pub(crate) headers: HeaderMap, pub(crate) extensions: RefCell, @@ -73,7 +73,7 @@ impl Request { inner: Rc::new(InnerRequest { pool, method: Method::GET, - url: InnerUrl::default(), + url: Url::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), flags: Cell::new(MessageFlags::empty()), @@ -94,7 +94,7 @@ impl Request { } #[inline] - pub(crate) fn url(&self) -> &InnerUrl { + pub fn url(&self) -> &Url { &self.inner().url } @@ -162,7 +162,10 @@ impl Request { self.inner().method == Method::CONNECT } - pub(crate) fn clone(&self) -> Self { + #[doc(hidden)] + /// Note: this method should be called only as part of clone operation + /// of wrapper type. + pub fn clone_request(&self) -> Self { Request { inner: self.inner.clone(), } diff --git a/src/uri.rs b/src/uri.rs index 881cf20a8..6edd220ce 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -32,11 +32,11 @@ fn set_bit(array: &mut [u8], ch: u8) { } lazy_static! { - static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") }; + pub static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") }; } #[derive(Default, Clone, Debug)] -pub(crate) struct Url { +pub struct Url { uri: Uri, path: Option>, } @@ -61,7 +61,7 @@ impl Url { } } -pub(crate) struct Quoter { +pub struct Quoter { safe_table: [u8; 16], protected_table: [u8; 16], } From d39c018c9384963030d0adda0e29a0735d7d5179 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 13 Oct 2018 23:57:31 -0700 Subject: [PATCH 043/208] do not handle upgrade and connect requests --- src/h1/codec.rs | 43 ++++++++++++++++++++------- src/h1/decoder.rs | 45 ++++++++++++++++++++--------- src/h1/dispatcher.rs | 69 ++++++++++++++++++++++++++++++-------------- src/h1/mod.rs | 13 ++++++++- src/h1/service.rs | 5 ++-- tests/test_server.rs | 21 +++++++++----- tests/test_ws.rs | 2 +- 7 files changed, 141 insertions(+), 57 deletions(-) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 04cf395b6..a91f5cb34 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -4,7 +4,7 @@ use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; -use super::decoder::{PayloadDecoder, PayloadItem, RequestDecoder}; +use super::decoder::{PayloadDecoder, PayloadItem, RequestDecoder, RequestPayloadType}; use super::encoder::{ResponseEncoder, ResponseLength}; use body::{Binary, Body}; use config::ServiceConfig; @@ -17,10 +17,11 @@ use response::Response; bitflags! { struct Flags: u8 { - const HEAD = 0b0000_0001; - const UPGRADE = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; - const KEEPALIVE_ENABLED = 0b0001_0000; + const HEAD = 0b0000_0001; + const UPGRADE = 0b0000_0010; + const KEEPALIVE = 0b0000_0100; + const KEEPALIVE_ENABLED = 0b0000_1000; + const UNHANDLED = 0b0001_0000; } } @@ -39,11 +40,19 @@ pub enum OutMessage { #[derive(Debug)] pub enum InMessage { /// Request - Message { req: Request, payload: bool }, + Message(Request, InMessageType), /// Payload chunk Chunk(Option), } +/// Incoming request type +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InMessageType { + None, + Payload, + Unhandled, +} + /// HTTP/1 Codec pub struct Codec { config: ServiceConfig, @@ -246,6 +255,8 @@ impl Decoder for Codec { Some(PayloadItem::Eof) => Some(InMessage::Chunk(None)), None => None, }) + } else if self.flags.contains(Flags::UNHANDLED) { + Ok(None) } else if let Some((req, payload)) = self.decoder.decode(src)? { self.flags .set(Flags::HEAD, req.inner.method == Method::HEAD); @@ -253,11 +264,21 @@ impl Decoder for Codec { if self.flags.contains(Flags::KEEPALIVE_ENABLED) { self.flags.set(Flags::KEEPALIVE, req.keep_alive()); } - self.payload = payload; - Ok(Some(InMessage::Message { - req, - payload: self.payload.is_some(), - })) + let payload = match payload { + RequestPayloadType::None => { + self.payload = None; + InMessageType::None + } + RequestPayloadType::Payload(pl) => { + self.payload = Some(pl); + InMessageType::Payload + } + RequestPayloadType::Unhandled => { + self.payload = None; + InMessageType::Unhandled + } + }; + Ok(Some(InMessage::Message(req, payload))) } else { Ok(None) } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 5fe8b19c8..c2a8d0e99 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -16,6 +16,13 @@ const MAX_HEADERS: usize = 96; pub struct RequestDecoder(&'static RequestPool); +/// Incoming request type +pub enum RequestPayloadType { + None, + Payload(PayloadDecoder), + Unhandled, +} + impl RequestDecoder { pub(crate) fn with_pool(pool: &'static RequestPool) -> RequestDecoder { RequestDecoder(pool) @@ -29,7 +36,7 @@ impl Default for RequestDecoder { } impl Decoder for RequestDecoder { - type Item = (Request, Option); + type Item = (Request, RequestPayloadType); type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { @@ -149,18 +156,18 @@ impl Decoder for RequestDecoder { // https://tools.ietf.org/html/rfc7230#section-3.3.3 let decoder = if chunked { // Chunked encoding - Some(PayloadDecoder::chunked()) + RequestPayloadType::Payload(PayloadDecoder::chunked()) } else if let Some(len) = content_length { // Content-Length - Some(PayloadDecoder::length(len)) + RequestPayloadType::Payload(PayloadDecoder::length(len)) } else if has_upgrade || msg.inner.method == Method::CONNECT { // upgrade(websocket) or connect - Some(PayloadDecoder::eof()) + RequestPayloadType::Unhandled } else if src.len() >= MAX_BUFFER_SIZE { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); return Err(ParseError::TooLarge); } else { - None + RequestPayloadType::None }; Ok(Some((msg, decoder))) @@ -481,20 +488,36 @@ mod tests { use super::*; use error::ParseError; - use h1::InMessage; + use h1::{InMessage, InMessageType}; use httpmessage::HttpMessage; use request::Request; + impl RequestPayloadType { + fn unwrap(self) -> PayloadDecoder { + match self { + RequestPayloadType::Payload(pl) => pl, + _ => panic!(), + } + } + + fn is_unhandled(&self) -> bool { + match self { + RequestPayloadType::Unhandled => true, + _ => false, + } + } + } + impl InMessage { fn message(self) -> Request { match self { - InMessage::Message { req, payload: _ } => req, + InMessage::Message(req, _) => req, _ => panic!("error"), } } fn is_payload(&self) -> bool { match *self { - InMessage::Message { req: _, payload } => payload, + InMessage::Message(_, payload) => payload == InMessageType::Payload, _ => panic!("error"), } } @@ -919,13 +942,9 @@ mod tests { ); let mut reader = RequestDecoder::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); - let mut pl = pl.unwrap(); assert!(!req.keep_alive()); assert!(req.upgrade()); - assert_eq!( - pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"some raw data" - ); + assert!(pl.is_unhandled()); } #[test] diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 8b7c29331..8ae2ae8ce 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -18,7 +18,8 @@ use error::DispatchError; use request::Request; use response::Response; -use super::codec::{Codec, InMessage, OutMessage}; +use super::codec::{Codec, InMessage, InMessageType, OutMessage}; +use super::H1ServiceResult; const MAX_PIPELINED_MESSAGES: usize = 16; @@ -41,13 +42,14 @@ where { service: S, flags: Flags, - framed: Framed, + framed: Option>, error: Option>, config: ServiceConfig, state: State, payload: Option, messages: VecDeque, + unhandled: Option, ka_expire: Instant, ka_timer: Option, @@ -112,9 +114,10 @@ where state: State::None, error: None, messages: VecDeque::new(), + framed: Some(framed), + unhandled: None, service, flags, - framed, config, ka_expire, ka_timer, @@ -144,7 +147,7 @@ where /// Flush stream fn poll_flush(&mut self) -> Poll<(), DispatchError> { if !self.flags.contains(Flags::FLUSHED) { - match self.framed.poll_complete() { + match self.framed.as_mut().unwrap().poll_complete() { Ok(Async::NotReady) => Ok(Async::NotReady), Err(err) => { debug!("Error sending data: {}", err); @@ -187,7 +190,11 @@ where State::ServiceCall(ref mut fut) => { match fut.poll().map_err(DispatchError::Service)? { Async::Ready(mut res) => { - self.framed.get_codec_mut().prepare_te(&mut res); + self.framed + .as_mut() + .unwrap() + .get_codec_mut() + .prepare_te(&mut res); let body = res.replace_body(Body::Empty); Some(State::SendResponse(Some(( OutMessage::Response(res), @@ -200,11 +207,11 @@ where // send respons State::SendResponse(ref mut item) => { let (msg, body) = item.take().expect("SendResponse is empty"); - match self.framed.start_send(msg) { + match self.framed.as_mut().unwrap().start_send(msg) { Ok(AsyncSink::Ready) => { self.flags.set( Flags::KEEPALIVE, - self.framed.get_codec().keepalive(), + self.framed.as_mut().unwrap().get_codec().keepalive(), ); self.flags.remove(Flags::FLUSHED); match body { @@ -233,7 +240,7 @@ where // Send payload State::SendPayload(ref mut stream, ref mut bin) => { if let Some(item) = bin.take() { - match self.framed.start_send(item) { + match self.framed.as_mut().unwrap().start_send(item) { Ok(AsyncSink::Ready) => { self.flags.remove(Flags::FLUSHED); } @@ -248,6 +255,8 @@ where match stream.poll() { Ok(Async::Ready(Some(item))) => match self .framed + .as_mut() + .unwrap() .start_send(OutMessage::Chunk(Some(item.into()))) { Ok(AsyncSink::Ready) => { @@ -297,7 +306,11 @@ where let mut task = self.service.call(req); match task.poll().map_err(DispatchError::Service)? { Async::Ready(mut res) => { - self.framed.get_codec_mut().prepare_te(&mut res); + self.framed + .as_mut() + .unwrap() + .get_codec_mut() + .prepare_te(&mut res); let body = res.replace_body(Body::Empty); Ok(State::SendResponse(Some((OutMessage::Response(res), body)))) } @@ -314,17 +327,24 @@ where let mut updated = false; 'outer: loop { - match self.framed.poll() { + match self.framed.as_mut().unwrap().poll() { Ok(Async::Ready(Some(msg))) => { updated = true; self.flags.insert(Flags::STARTED); match msg { - InMessage::Message { req, payload } => { - if payload { - let (ps, pl) = Payload::new(false); - *req.inner.payload.borrow_mut() = Some(pl); - self.payload = Some(ps); + InMessage::Message(req, payload) => { + match payload { + InMessageType::Payload => { + let (ps, pl) = Payload::new(false); + *req.inner.payload.borrow_mut() = Some(pl); + self.payload = Some(ps); + } + InMessageType::Unhandled => { + self.unhandled = Some(req); + return Ok(updated); + } + _ => (), } // handle request early @@ -454,15 +474,16 @@ where S: Service, S::Error: Debug, { - type Item = (); + type Item = H1ServiceResult; type Error = DispatchError; #[inline] - fn poll(&mut self) -> Poll<(), Self::Error> { + fn poll(&mut self) -> Poll { if self.flags.contains(Flags::SHUTDOWN) { self.poll_keepalive()?; try_ready!(self.poll_flush()); - Ok(AsyncWrite::shutdown(self.framed.get_mut())?) + let io = self.framed.take().unwrap().into_inner(); + Ok(Async::Ready(H1ServiceResult::Shutdown(io))) } else { self.poll_keepalive()?; self.poll_request()?; @@ -474,15 +495,21 @@ where if let Some(err) = self.error.take() { Err(err) } else if self.flags.contains(Flags::DISCONNECTED) { - Ok(Async::Ready(())) + Ok(Async::Ready(H1ServiceResult::Disconnected)) + } + // unhandled request (upgrade or connect) + else if self.unhandled.is_some() { + let req = self.unhandled.take().unwrap(); + let framed = self.framed.take().unwrap(); + Ok(Async::Ready(H1ServiceResult::Unhandled(req, framed))) } // disconnect if keep-alive is not enabled else if self.flags.contains(Flags::STARTED) && !self .flags .intersects(Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED) { - self.flags.insert(Flags::SHUTDOWN); - self.poll() + let io = self.framed.take().unwrap().into_inner(); + Ok(Async::Ready(H1ServiceResult::Shutdown(io))) } else { Ok(Async::NotReady) } diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 634136a47..4e196ad54 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -1,11 +1,22 @@ //! HTTP/1 implementation +use actix_net::codec::Framed; + mod codec; mod decoder; mod dispatcher; mod encoder; mod service; -pub use self::codec::{Codec, InMessage, OutMessage}; +pub use self::codec::{Codec, InMessage, InMessageType, OutMessage}; pub use self::decoder::{PayloadDecoder, RequestDecoder}; pub use self::dispatcher::Dispatcher; pub use self::service::{H1Service, H1ServiceHandler}; + +use request::Request; + +/// H1 service response type +pub enum H1ServiceResult { + Disconnected, + Shutdown(T), + Unhandled(Request, Framed), +} diff --git a/src/h1/service.rs b/src/h1/service.rs index aa7a51733..6e9d7d651 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -12,6 +12,7 @@ use request::Request; use response::Response; use super::dispatcher::Dispatcher; +use super::H1ServiceResult; /// `NewService` implementation for HTTP1 transport pub struct H1Service { @@ -51,7 +52,7 @@ where S::Error: Debug, { type Request = T; - type Response = (); + type Response = H1ServiceResult; type Error = DispatchError; type InitError = S::InitError; type Service = H1ServiceHandler; @@ -243,7 +244,7 @@ where S::Error: Debug, { type Request = T; - type Response = (); + type Response = H1ServiceResult; type Error = DispatchError; type Future = Dispatcher; diff --git a/tests/test_server.rs b/tests/test_server.rs index f382eafbd..c8de0290d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -9,6 +9,7 @@ use std::{io::Read, io::Write, net, thread, time}; use actix::System; use actix_net::server::Server; +use actix_net::service::NewServiceExt; use actix_web::{client, test, HttpMessage}; use bytes::Bytes; use futures::future::{self, ok}; @@ -29,6 +30,7 @@ fn test_h1_v2() { .server_hostname("localhost") .server_address(addr) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) }).unwrap() .run(); }); @@ -53,6 +55,7 @@ fn test_slow_request() { h1::H1Service::build() .client_timeout(100) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) }).unwrap() .run(); }); @@ -72,6 +75,7 @@ fn test_malformed_request() { Server::new() .bind("test", addr, move || { h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) }).unwrap() .run(); }); @@ -106,7 +110,7 @@ fn test_content_length() { StatusCode::NOT_FOUND, ]; future::ok::<_, ()>(Response::new(statuses[indx])) - }) + }).map(|_| ()) }).unwrap() .run(); }); @@ -172,7 +176,7 @@ fn test_headers() { ); } future::ok::<_, ()>(builder.body(data.clone())) - }) + }).map(|_| ()) }) .unwrap() .run() @@ -221,6 +225,7 @@ fn test_body() { Server::new() .bind("test", addr, move || { h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .map(|_| ()) }).unwrap() .run(); }); @@ -246,7 +251,7 @@ fn test_head_empty() { .bind("test", addr, move || { h1::H1Service::new(|_| { ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).finish()) - }) + }).map(|_| ()) }).unwrap() .run() }); @@ -282,7 +287,7 @@ fn test_head_binary() { ok::<_, ()>( Response::Ok().content_length(STR.len() as u64).body(STR), ) - }) + }).map(|_| ()) }).unwrap() .run() }); @@ -314,7 +319,7 @@ fn test_head_binary2() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))) + h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }).unwrap() .run() }); @@ -349,7 +354,7 @@ fn test_body_length() { .content_length(STR.len() as u64) .body(Body::Streaming(Box::new(body))), ) - }) + }).map(|_| ()) }).unwrap() .run() }); @@ -380,7 +385,7 @@ fn test_body_chunked_explicit() { .chunked() .body(Body::Streaming(Box::new(body))), ) - }) + }).map(|_| ()) }).unwrap() .run() }); @@ -409,7 +414,7 @@ fn test_body_chunked_implicit() { h1::H1Service::new(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().body(Body::Streaming(Box::new(body)))) - }) + }).map(|_| ()) }).unwrap() .run() }); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 91e212efd..f475cd22d 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -51,7 +51,7 @@ fn test_simple() { .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(req, framed): (_, Framed<_, _>)| { // validate request - if let Some(h1::InMessage::Message { req, payload: _ }) = req { + if let Some(h1::InMessage::Message(req, _)) = req { match ws::handshake(&req) { Err(e) => { // validation failed From 3c402a55da704ac6605d26500877bedab8565bf7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 15 Oct 2018 15:56:47 -0700 Subject: [PATCH 044/208] added H1SimpleService --- src/h1/codec.rs | 12 ++++++ src/h1/mod.rs | 2 +- src/h1/service.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 110 insertions(+), 3 deletions(-) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index a91f5cb34..020466482 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -103,6 +103,17 @@ impl Codec { self.flags.contains(Flags::KEEPALIVE) } + /// Check last request's message type + pub fn message_type(&self) -> InMessageType { + if self.flags.contains(Flags::UNHANDLED) { + InMessageType::Unhandled + } else if self.payload.is_none() { + InMessageType::None + } else { + InMessageType::Payload + } + } + /// prepare transfer encoding pub fn prepare_te(&mut self, res: &mut Response) { self.te @@ -275,6 +286,7 @@ impl Decoder for Codec { } RequestPayloadType::Unhandled => { self.payload = None; + self.flags.insert(Flags::UNHANDLED); InMessageType::Unhandled } }; diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 4e196ad54..2a276b7a0 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -10,7 +10,7 @@ mod service; pub use self::codec::{Codec, InMessage, InMessageType, OutMessage}; pub use self::decoder::{PayloadDecoder, RequestDecoder}; pub use self::dispatcher::Dispatcher; -pub use self::service::{H1Service, H1ServiceHandler}; +pub use self::service::{H1Service, H1ServiceHandler, H1SimpleService}; use request::Request; diff --git a/src/h1/service.rs b/src/h1/service.rs index 6e9d7d651..bf92e8d2f 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -2,15 +2,18 @@ use std::fmt::Debug; use std::marker::PhantomData; use std::net; +use actix_net::codec::Framed; use actix_net::service::{IntoNewService, NewService, Service}; -use futures::{Async, Future, Poll}; +use futures::future::{ok, FutureResult}; +use futures::{Async, Future, Poll, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; use config::{KeepAlive, ServiceConfig}; -use error::DispatchError; +use error::{DispatchError, ParseError}; use request::Request; use response::Response; +use super::codec::{Codec, InMessage}; use super::dispatcher::Dispatcher; use super::H1ServiceResult; @@ -191,6 +194,7 @@ where } } +#[doc(hidden)] pub struct H1ServiceResponse { fut: S::Future, cfg: Option, @@ -256,3 +260,94 @@ where Dispatcher::new(req, self.cfg.clone(), self.srv.clone()) } } + +/// `NewService` implementation for `H1SimpleServiceHandler` service +pub struct H1SimpleService { + config: ServiceConfig, + _t: PhantomData, +} + +impl H1SimpleService { + /// Create new `H1SimpleService` instance. + pub fn new() -> Self { + H1SimpleService { + config: ServiceConfig::default(), + _t: PhantomData, + } + } +} + +impl NewService for H1SimpleService +where + T: AsyncRead + AsyncWrite, +{ + type Request = T; + type Response = (Request, Framed); + type Error = ParseError; + type InitError = (); + type Service = H1SimpleServiceHandler; + type Future = FutureResult; + + fn new_service(&self) -> Self::Future { + ok(H1SimpleServiceHandler { + config: self.config.clone(), + _t: PhantomData, + }) + } +} + +/// `Service` implementation for HTTP1 transport. Reads one request and returns +/// request and framed object. +pub struct H1SimpleServiceHandler { + config: ServiceConfig, + _t: PhantomData, +} + +impl Service for H1SimpleServiceHandler +where + T: AsyncRead + AsyncWrite, +{ + type Request = T; + type Response = (Request, Framed); + type Error = ParseError; + type Future = H1SimpleServiceHandlerResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + H1SimpleServiceHandlerResponse { + framed: Some(Framed::new(req, Codec::new(self.config.clone()))), + } + } +} + +#[doc(hidden)] +pub struct H1SimpleServiceHandlerResponse +where + T: AsyncRead + AsyncWrite, +{ + framed: Option>, +} + +impl Future for H1SimpleServiceHandlerResponse +where + T: AsyncRead + AsyncWrite, +{ + type Item = (Request, Framed); + type Error = ParseError; + + fn poll(&mut self) -> Poll { + match self.framed.as_mut().unwrap().poll()? { + Async::Ready(Some(req)) => match req { + InMessage::Message(req, _) => { + Ok(Async::Ready((req, self.framed.take().unwrap()))) + } + InMessage::Chunk(_) => unreachable!("Something is wrong"), + }, + Async::Ready(None) => Err(ParseError::Incomplete), + Async::NotReady => Ok(Async::NotReady), + } + } +} From 20c693b39c0e80436cc6c1d0e3641a226f8c8fe1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 15 Oct 2018 16:46:13 -0700 Subject: [PATCH 045/208] rename service --- src/h1/mod.rs | 2 +- src/h1/service.rs | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 2a276b7a0..1ac65912e 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -10,7 +10,7 @@ mod service; pub use self::codec::{Codec, InMessage, InMessageType, OutMessage}; pub use self::decoder::{PayloadDecoder, RequestDecoder}; pub use self::dispatcher::Dispatcher; -pub use self::service::{H1Service, H1ServiceHandler, H1SimpleService}; +pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; use request::Request; diff --git a/src/h1/service.rs b/src/h1/service.rs index bf92e8d2f..404ded6b0 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -261,23 +261,23 @@ where } } -/// `NewService` implementation for `H1SimpleServiceHandler` service -pub struct H1SimpleService { +/// `NewService` implementation for `OneRequestService` service +pub struct OneRequest { config: ServiceConfig, _t: PhantomData, } -impl H1SimpleService { +impl OneRequest { /// Create new `H1SimpleService` instance. pub fn new() -> Self { - H1SimpleService { + OneRequest { config: ServiceConfig::default(), _t: PhantomData, } } } -impl NewService for H1SimpleService +impl NewService for OneRequest where T: AsyncRead + AsyncWrite, { @@ -285,11 +285,11 @@ where type Response = (Request, Framed); type Error = ParseError; type InitError = (); - type Service = H1SimpleServiceHandler; + type Service = OneRequestService; type Future = FutureResult; fn new_service(&self) -> Self::Future { - ok(H1SimpleServiceHandler { + ok(OneRequestService { config: self.config.clone(), _t: PhantomData, }) @@ -298,40 +298,40 @@ where /// `Service` implementation for HTTP1 transport. Reads one request and returns /// request and framed object. -pub struct H1SimpleServiceHandler { +pub struct OneRequestService { config: ServiceConfig, _t: PhantomData, } -impl Service for H1SimpleServiceHandler +impl Service for OneRequestService where T: AsyncRead + AsyncWrite, { type Request = T; type Response = (Request, Framed); type Error = ParseError; - type Future = H1SimpleServiceHandlerResponse; + type Future = OneRequestServiceResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { Ok(Async::Ready(())) } fn call(&mut self, req: Self::Request) -> Self::Future { - H1SimpleServiceHandlerResponse { + OneRequestServiceResponse { framed: Some(Framed::new(req, Codec::new(self.config.clone()))), } } } #[doc(hidden)] -pub struct H1SimpleServiceHandlerResponse +pub struct OneRequestServiceResponse where T: AsyncRead + AsyncWrite, { framed: Option>, } -impl Future for H1SimpleServiceHandlerResponse +impl Future for OneRequestServiceResponse where T: AsyncRead + AsyncWrite, { From 9b94eaa6a8017cf203fef999c75366b56554216f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Oct 2018 09:59:20 -0700 Subject: [PATCH 046/208] ws services --- src/error.rs | 7 ++ src/h1/service.rs | 5 +- src/lib.rs | 2 + src/service.rs | 185 ++++++++++++++++++++++++++++++++++++++++++++++ src/ws/mod.rs | 44 +++++++---- src/ws/service.rs | 52 +++++++++++++ tests/test_ws.rs | 9 ++- 7 files changed, 284 insertions(+), 20 deletions(-) create mode 100644 src/service.rs create mode 100644 src/ws/service.rs diff --git a/src/error.rs b/src/error.rs index 465b8ae0a..3c0902030 100644 --- a/src/error.rs +++ b/src/error.rs @@ -632,6 +632,13 @@ where } } +/// Convert Response to a Error +impl From for Error { + fn from(res: Response) -> Error { + InternalError::from_response("", res).into() + } +} + /// Helper function that creates wrapper of any error and generate *BAD /// REQUEST* response. #[allow(non_snake_case)] diff --git a/src/h1/service.rs b/src/h1/service.rs index 404ded6b0..096cb3016 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -267,7 +267,10 @@ pub struct OneRequest { _t: PhantomData, } -impl OneRequest { +impl OneRequest +where + T: AsyncRead + AsyncWrite, +{ /// Create new `H1SimpleService` instance. pub fn new() -> Self { OneRequest { diff --git a/src/lib.rs b/src/lib.rs index 5ce3ec39f..c9cebb47d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,6 +110,7 @@ mod json; mod payload; mod request; mod response; +mod service; pub mod uri; pub mod error; @@ -123,6 +124,7 @@ pub use extensions::Extensions; pub use httpmessage::HttpMessage; pub use request::Request; pub use response::Response; +pub use service::{SendError, SendResponse}; pub use self::config::{KeepAlive, ServiceConfig, ServiceConfigBuilder}; diff --git a/src/service.rs b/src/service.rs new file mode 100644 index 000000000..4467087bc --- /dev/null +++ b/src/service.rs @@ -0,0 +1,185 @@ +use std::io; +use std::marker::PhantomData; + +use actix_net::codec::Framed; +use actix_net::service::{NewService, Service}; +use futures::future::{ok, Either, FutureResult}; +use futures::{Async, AsyncSink, Future, Poll, Sink}; +use tokio_io::AsyncWrite; + +use error::ResponseError; +use h1::{Codec, OutMessage}; +use response::Response; + +pub struct SendError(PhantomData<(T, R, E)>); + +impl Default for SendError +where + T: AsyncWrite, + E: ResponseError, +{ + fn default() -> Self { + SendError(PhantomData) + } +} + +impl NewService for SendError +where + T: AsyncWrite, + E: ResponseError, +{ + type Request = Result)>; + type Response = R; + type Error = (E, Framed); + type InitError = (); + type Service = SendError; + type Future = FutureResult; + + fn new_service(&self) -> Self::Future { + ok(SendError(PhantomData)) + } +} + +impl Service for SendError +where + T: AsyncWrite, + E: ResponseError, +{ + type Request = Result)>; + type Response = R; + type Error = (E, Framed); + type Future = Either)>, SendErrorFut>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + match req { + Ok(r) => Either::A(ok(r)), + Err((e, framed)) => Either::B(SendErrorFut { + framed: Some(framed), + res: Some(OutMessage::Response(e.error_response())), + err: Some(e), + _t: PhantomData, + }), + } + } +} + +pub struct SendErrorFut { + res: Option, + framed: Option>, + err: Option, + _t: PhantomData, +} + +impl Future for SendErrorFut +where + E: ResponseError, + T: AsyncWrite, +{ + type Item = R; + type Error = (E, Framed); + + fn poll(&mut self) -> Poll { + if let Some(res) = self.res.take() { + match self.framed.as_mut().unwrap().start_send(res) { + Ok(AsyncSink::Ready) => (), + Ok(AsyncSink::NotReady(res)) => { + self.res = Some(res); + return Ok(Async::NotReady); + } + Err(_) => { + return Err((self.err.take().unwrap(), self.framed.take().unwrap())) + } + } + } + match self.framed.as_mut().unwrap().poll_complete() { + Ok(Async::Ready(_)) => { + return Err((self.err.take().unwrap(), self.framed.take().unwrap())) + } + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(_) => { + return Err((self.err.take().unwrap(), self.framed.take().unwrap())) + } + } + } +} + +pub struct SendResponse(PhantomData<(T,)>); + +impl Default for SendResponse +where + T: AsyncWrite, +{ + fn default() -> Self { + SendResponse(PhantomData) + } +} + +impl NewService for SendResponse +where + T: AsyncWrite, +{ + type Request = (Response, Framed); + type Response = Framed; + type Error = io::Error; + type InitError = (); + type Service = SendResponse; + type Future = FutureResult; + + fn new_service(&self) -> Self::Future { + ok(SendResponse(PhantomData)) + } +} + +impl Service for SendResponse +where + T: AsyncWrite, +{ + type Request = (Response, Framed); + type Response = Framed; + type Error = io::Error; + type Future = SendResponseFut; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, (res, framed): Self::Request) -> Self::Future { + SendResponseFut { + res: Some(OutMessage::Response(res)), + framed: Some(framed), + } + } +} + +pub struct SendResponseFut { + res: Option, + framed: Option>, +} + +impl Future for SendResponseFut +where + T: AsyncWrite, +{ + type Item = Framed; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + if let Some(res) = self.res.take() { + match self.framed.as_mut().unwrap().start_send(res)? { + AsyncSink::Ready => (), + AsyncSink::NotReady(res) => { + self.res = Some(res); + return Ok(Async::NotReady); + } + } + } + match self.framed.as_mut().unwrap().poll_complete()? { + Async::Ready(_) => Ok(Async::Ready(self.framed.take().unwrap())), + Async::NotReady => Ok(Async::NotReady), + } + } +} diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 7df1f4b4d..690c56feb 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -14,11 +14,13 @@ mod codec; mod frame; mod mask; mod proto; +mod service; mod transport; pub use self::codec::{Codec, Frame, Message}; pub use self::frame::Parser; pub use self::proto::{CloseCode, CloseReason, OpCode}; +pub use self::service::VerifyWebSockets; pub use self::transport::Transport; /// Websocket protocol errors @@ -109,15 +111,20 @@ impl ResponseError for HandshakeError { } } -/// Prepare `WebSocket` handshake response. -/// -/// This function returns handshake `Response`, ready to send to peer. -/// It does not perform any IO. -/// +/// Verify `WebSocket` handshake request and create handshake reponse. // /// `protocols` is a sequence of known protocols. On successful handshake, // /// the returned response headers contain the first protocol in this list // /// which the server also knows. pub fn handshake(req: &Request) -> Result { + verify_handshake(req)?; + Ok(handshake_response(req)) +} + +/// Verify `WebSocket` handshake request. +// /// `protocols` is a sequence of known protocols. On successful handshake, +// /// the returned response headers contain the first protocol in this list +// /// which the server also knows. +pub fn verify_handshake(req: &Request) -> Result<(), HandshakeError> { // WebSocket accepts only GET if *req.method() != Method::GET { return Err(HandshakeError::GetMethodRequired); @@ -161,17 +168,24 @@ pub fn handshake(req: &Request) -> Result { if !req.headers().contains_key(header::SEC_WEBSOCKET_KEY) { return Err(HandshakeError::BadWebsocketKey); } + Ok(()) +} + +/// Create websocket's handshake response +/// +/// This function returns handshake `Response`, ready to send to peer. +pub fn handshake_response(req: &Request) -> ResponseBuilder { let key = { let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap(); proto::hash_key(key.as_ref()) }; - Ok(Response::build(StatusCode::SWITCHING_PROTOCOLS) + Response::build(StatusCode::SWITCHING_PROTOCOLS) .connection_type(ConnectionType::Upgrade) .header(header::UPGRADE, "websocket") .header(header::TRANSFER_ENCODING, "chunked") .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) - .take()) + .take() } #[cfg(test)] @@ -185,13 +199,13 @@ mod tests { let req = TestRequest::default().method(Method::POST).finish(); assert_eq!( HandshakeError::GetMethodRequired, - handshake(&req).err().unwrap() + verify_handshake(&req).err().unwrap() ); let req = TestRequest::default().finish(); assert_eq!( HandshakeError::NoWebsocketUpgrade, - handshake(&req).err().unwrap() + verify_handshake(&req).err().unwrap() ); let req = TestRequest::default() @@ -199,7 +213,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::NoWebsocketUpgrade, - handshake(&req).err().unwrap() + verify_handshake(&req).err().unwrap() ); let req = TestRequest::default() @@ -209,7 +223,7 @@ mod tests { ).finish(); assert_eq!( HandshakeError::NoConnectionUpgrade, - handshake(&req).err().unwrap() + verify_handshake(&req).err().unwrap() ); let req = TestRequest::default() @@ -222,7 +236,7 @@ mod tests { ).finish(); assert_eq!( HandshakeError::NoVersionHeader, - handshake(&req).err().unwrap() + verify_handshake(&req).err().unwrap() ); let req = TestRequest::default() @@ -238,7 +252,7 @@ mod tests { ).finish(); assert_eq!( HandshakeError::UnsupportedVersion, - handshake(&req).err().unwrap() + verify_handshake(&req).err().unwrap() ); let req = TestRequest::default() @@ -254,7 +268,7 @@ mod tests { ).finish(); assert_eq!( HandshakeError::BadWebsocketKey, - handshake(&req).err().unwrap() + verify_handshake(&req).err().unwrap() ); let req = TestRequest::default() @@ -273,7 +287,7 @@ mod tests { ).finish(); assert_eq!( StatusCode::SWITCHING_PROTOCOLS, - handshake(&req).unwrap().finish().status() + handshake_response(&req).finish().status() ); } diff --git a/src/ws/service.rs b/src/ws/service.rs new file mode 100644 index 000000000..9cce4d639 --- /dev/null +++ b/src/ws/service.rs @@ -0,0 +1,52 @@ +use std::marker::PhantomData; + +use actix_net::codec::Framed; +use actix_net::service::{NewService, Service}; +use futures::future::{ok, FutureResult}; +use futures::{Async, IntoFuture, Poll}; + +use h1::Codec; +use request::Request; + +use super::{verify_handshake, HandshakeError}; + +pub struct VerifyWebSockets { + _t: PhantomData, +} + +impl Default for VerifyWebSockets { + fn default() -> Self { + VerifyWebSockets { _t: PhantomData } + } +} + +impl NewService for VerifyWebSockets { + type Request = (Request, Framed); + type Response = (Request, Framed); + type Error = (HandshakeError, Framed); + type InitError = (); + type Service = VerifyWebSockets; + type Future = FutureResult; + + fn new_service(&self) -> Self::Future { + ok(VerifyWebSockets { _t: PhantomData }) + } +} + +impl Service for VerifyWebSockets { + type Request = (Request, Framed); + type Response = (Request, Framed); + type Error = (HandshakeError, Framed); + type Future = FutureResult; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, (req, framed): Self::Request) -> Self::Future { + match verify_handshake(&req) { + Err(e) => Err((e, framed)).into_future(), + Ok(_) => Ok((req, framed)).into_future(), + } + } +} diff --git a/tests/test_ws.rs b/tests/test_ws.rs index f475cd22d..a5503c209 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -52,7 +52,7 @@ fn test_simple() { .and_then(|(req, framed): (_, Framed<_, _>)| { // validate request if let Some(h1::InMessage::Message(req, _)) = req { - match ws::handshake(&req) { + match ws::verify_handshake(&req) { Err(e) => { // validation failed let resp = e.error_response(); @@ -63,11 +63,12 @@ fn test_simple() { .map(|_| ()), ) } - Ok(mut resp) => Either::B( + Ok(_) => Either::B( // send response framed - .send(h1::OutMessage::Response(resp.finish())) - .map_err(|_| ()) + .send(h1::OutMessage::Response( + ws::handshake_response(&req).finish(), + )).map_err(|_| ()) .and_then(|framed| { // start websocket service let framed = From 09c94cb06be623d21aead3173707268b185ab78b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Oct 2018 18:18:05 -0700 Subject: [PATCH 047/208] add client http codec; websockets client --- Cargo.toml | 3 + src/client/mod.rs | 6 + src/client/request.rs | 564 +++++++++++++++++++++++++++++++++++++++ src/client/response.rs | 128 +++++++++ src/h1/client.rs | 217 +++++++++++++++ src/h1/codec.rs | 76 ++---- src/h1/decoder.rs | 194 ++++++++++++-- src/h1/dispatcher.rs | 71 ++--- src/h1/encoder.rs | 34 +++ src/h1/mod.rs | 28 +- src/h1/service.rs | 8 +- src/lib.rs | 3 + src/request.rs | 53 ++-- src/service.rs | 10 +- src/ws/client/connect.rs | 95 +++++++ src/ws/client/error.rs | 87 ++++++ src/ws/client/mod.rs | 48 ++++ src/ws/client/service.rs | 270 +++++++++++++++++++ src/ws/mod.rs | 2 + tests/test_ws.rs | 53 +++- 20 files changed, 1802 insertions(+), 148 deletions(-) create mode 100644 src/client/mod.rs create mode 100644 src/client/request.rs create mode 100644 src/client/response.rs create mode 100644 src/h1/client.rs create mode 100644 src/ws/client/connect.rs create mode 100644 src/ws/client/error.rs create mode 100644 src/ws/client/mod.rs create mode 100644 src/ws/client/service.rs diff --git a/Cargo.toml b/Cargo.toml index 9193aeeaa..87bd05b8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,10 @@ time = "0.1" encoding = "0.2" lazy_static = "1.0" serde_urlencoded = "0.5.3" + cookie = { version="0.11", features=["percent-encode"] } +percent-encoding = "1.0" +url = { version="1.7", features=["query_encoding"] } # io net2 = "0.2" diff --git a/src/client/mod.rs b/src/client/mod.rs new file mode 100644 index 000000000..a5582fea8 --- /dev/null +++ b/src/client/mod.rs @@ -0,0 +1,6 @@ +//! Http client api +mod request; +mod response; + +pub use self::request::{ClientRequest, ClientRequestBuilder}; +pub use self::response::ClientResponse; diff --git a/src/client/request.rs b/src/client/request.rs new file mode 100644 index 000000000..bf82927e9 --- /dev/null +++ b/src/client/request.rs @@ -0,0 +1,564 @@ +use std::fmt; +use std::fmt::Write as FmtWrite; +use std::io::Write; + +use bytes::{BufMut, BytesMut}; +use cookie::{Cookie, CookieJar}; +use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use urlcrate::Url; + +use header::{self, Header, IntoHeaderValue}; +use http::{ + uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, + Uri, Version, +}; + +/// An HTTP Client Request +/// +/// ```rust +/// # extern crate actix_web; +/// # extern crate futures; +/// # extern crate tokio; +/// # use futures::Future; +/// # use std::process; +/// use actix_web::{actix, client}; +/// +/// fn main() { +/// actix::run( +/// || client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder +/// .header("User-Agent", "Actix-web") +/// .finish().unwrap() +/// .send() // <- Send http request +/// .map_err(|_| ()) +/// .and_then(|response| { // <- server http response +/// println!("Response: {:?}", response); +/// # actix::System::current().stop(); +/// Ok(()) +/// }), +/// ); +/// } +/// ``` +pub struct ClientRequest { + uri: Uri, + method: Method, + version: Version, + headers: HeaderMap, + chunked: bool, + upgrade: bool, +} + +impl Default for ClientRequest { + fn default() -> ClientRequest { + ClientRequest { + uri: Uri::default(), + method: Method::default(), + version: Version::HTTP_11, + headers: HeaderMap::with_capacity(16), + chunked: false, + upgrade: false, + } + } +} + +impl ClientRequest { + /// Create request builder for `GET` request + pub fn get>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::GET).uri(uri); + builder + } + + /// Create request builder for `HEAD` request + pub fn head>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::HEAD).uri(uri); + builder + } + + /// Create request builder for `POST` request + pub fn post>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::POST).uri(uri); + builder + } + + /// Create request builder for `PUT` request + pub fn put>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::PUT).uri(uri); + builder + } + + /// Create request builder for `DELETE` request + pub fn delete>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::DELETE).uri(uri); + builder + } +} + +impl ClientRequest { + /// Create client request builder + pub fn build() -> ClientRequestBuilder { + ClientRequestBuilder { + request: Some(ClientRequest::default()), + err: None, + cookies: None, + default_headers: true, + } + } + + /// Get the request URI + #[inline] + pub fn uri(&self) -> &Uri { + &self.uri + } + + /// Set client request URI + #[inline] + pub fn set_uri(&mut self, uri: Uri) { + self.uri = uri + } + + /// Get the request method + #[inline] + pub fn method(&self) -> &Method { + &self.method + } + + /// Set HTTP `Method` for the request + #[inline] + pub fn set_method(&mut self, method: Method) { + self.method = method + } + + /// Get HTTP version for the request + #[inline] + pub fn version(&self) -> Version { + self.version + } + + /// Set http `Version` for the request + #[inline] + pub fn set_version(&mut self, version: Version) { + self.version = version + } + + /// Get the headers from the request + #[inline] + pub fn headers(&self) -> &HeaderMap { + &self.headers + } + + /// Get a mutable reference to the headers + #[inline] + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.headers + } + + /// is chunked encoding enabled + #[inline] + pub fn chunked(&self) -> bool { + self.chunked + } + + /// is upgrade request + #[inline] + pub fn upgrade(&self) -> bool { + self.upgrade + } +} + +impl fmt::Debug for ClientRequest { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!( + f, + "\nClientRequest {:?} {}:{}", + self.version, self.method, self.uri + )?; + writeln!(f, " headers:")?; + for (key, val) in self.headers.iter() { + writeln!(f, " {:?}: {:?}", key, val)?; + } + Ok(()) + } +} + +/// An HTTP Client request builder +/// +/// This type can be used to construct an instance of `ClientRequest` through a +/// builder-like pattern. +pub struct ClientRequestBuilder { + request: Option, + err: Option, + cookies: Option, + default_headers: bool, +} + +impl ClientRequestBuilder { + /// Set HTTP URI of request. + #[inline] + pub fn uri>(&mut self, uri: U) -> &mut Self { + match Url::parse(uri.as_ref()) { + Ok(url) => self._uri(url.as_str()), + Err(_) => self._uri(uri.as_ref()), + } + } + + fn _uri(&mut self, url: &str) -> &mut Self { + match Uri::try_from(url) { + Ok(uri) => { + if let Some(parts) = parts(&mut self.request, &self.err) { + parts.uri = uri; + } + } + Err(e) => self.err = Some(e.into()), + } + self + } + + /// Set HTTP method of this request. + #[inline] + pub fn method(&mut self, method: Method) -> &mut Self { + if let Some(parts) = parts(&mut self.request, &self.err) { + parts.method = method; + } + self + } + + /// Set HTTP method of this request. + #[inline] + pub fn get_method(&mut self) -> &Method { + let parts = self.request.as_ref().expect("cannot reuse request builder"); + &parts.method + } + + /// Set HTTP version of this request. + /// + /// By default requests's HTTP version depends on network stream + #[inline] + pub fn version(&mut self, version: Version) -> &mut Self { + if let Some(parts) = parts(&mut self.request, &self.err) { + parts.version = version; + } + self + } + + /// Set a header. + /// + /// ```rust + /// # extern crate mime; + /// # extern crate actix_web; + /// # use actix_web::client::*; + /// # + /// use actix_web::{client, http}; + /// + /// fn main() { + /// let req = client::ClientRequest::build() + /// .set(http::header::Date::now()) + /// .set(http::header::ContentType(mime::TEXT_HTML)) + /// .finish() + /// .unwrap(); + /// } + /// ``` + #[doc(hidden)] + pub fn set(&mut self, hdr: H) -> &mut Self { + if let Some(parts) = parts(&mut self.request, &self.err) { + match hdr.try_into() { + Ok(value) => { + parts.headers.insert(H::name(), value); + } + Err(e) => self.err = Some(e.into()), + } + } + self + } + + /// Append a header. + /// + /// Header gets appended to existing header. + /// To override header use `set_header()` method. + /// + /// ```rust + /// # extern crate http; + /// # extern crate actix_web; + /// # use actix_web::client::*; + /// # + /// use http::header; + /// + /// fn main() { + /// let req = ClientRequest::build() + /// .header("X-TEST", "value") + /// .header(header::CONTENT_TYPE, "application/json") + /// .finish() + /// .unwrap(); + /// } + /// ``` + pub fn header(&mut self, key: K, value: V) -> &mut Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + if let Some(parts) = parts(&mut self.request, &self.err) { + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + parts.headers.append(key, value); + } + Err(e) => self.err = Some(e.into()), + }, + Err(e) => self.err = Some(e.into()), + }; + } + self + } + + /// Set a header. + pub fn set_header(&mut self, key: K, value: V) -> &mut Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + if let Some(parts) = parts(&mut self.request, &self.err) { + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + parts.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + }, + Err(e) => self.err = Some(e.into()), + }; + } + self + } + + /// Set a header only if it is not yet set. + pub fn set_header_if_none(&mut self, key: K, value: V) -> &mut Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + if let Some(parts) = parts(&mut self.request, &self.err) { + match HeaderName::try_from(key) { + Ok(key) => if !parts.headers.contains_key(&key) { + match value.try_into() { + Ok(value) => { + parts.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + } + }, + Err(e) => self.err = Some(e.into()), + }; + } + self + } + + /// Enable connection upgrade + #[inline] + pub fn upgrade(&mut self) -> &mut Self { + if let Some(parts) = parts(&mut self.request, &self.err) { + parts.upgrade = true; + } + self + } + + /// Set request's content type + #[inline] + pub fn content_type(&mut self, value: V) -> &mut Self + where + HeaderValue: HttpTryFrom, + { + if let Some(parts) = parts(&mut self.request, &self.err) { + match HeaderValue::try_from(value) { + Ok(value) => { + parts.headers.insert(header::CONTENT_TYPE, value); + } + Err(e) => self.err = Some(e.into()), + }; + } + self + } + + /// Set content length + #[inline] + pub fn content_length(&mut self, len: u64) -> &mut Self { + let mut wrt = BytesMut::new().writer(); + let _ = write!(wrt, "{}", len); + self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) + } + + /// Set a cookie + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{client, http}; + /// + /// fn main() { + /// let req = client::ClientRequest::build() + /// .cookie( + /// http::Cookie::build("name", "value") + /// .domain("www.rust-lang.org") + /// .path("/") + /// .secure(true) + /// .http_only(true) + /// .finish(), + /// ) + /// .finish() + /// .unwrap(); + /// } + /// ``` + pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { + if self.cookies.is_none() { + let mut jar = CookieJar::new(); + jar.add(cookie.into_owned()); + self.cookies = Some(jar) + } else { + self.cookies.as_mut().unwrap().add(cookie.into_owned()); + } + self + } + + /// Do not add default request headers. + /// By default `Accept-Encoding` and `User-Agent` headers are set. + pub fn no_default_headers(&mut self) -> &mut Self { + self.default_headers = false; + self + } + + /// This method calls provided closure with builder reference if + /// value is `true`. + pub fn if_true(&mut self, value: bool, f: F) -> &mut Self + where + F: FnOnce(&mut ClientRequestBuilder), + { + if value { + f(self); + } + self + } + + /// This method calls provided closure with builder reference if + /// value is `Some`. + pub fn if_some(&mut self, value: Option, f: F) -> &mut Self + where + F: FnOnce(T, &mut ClientRequestBuilder), + { + if let Some(val) = value { + f(val, self); + } + self + } + + /// Set a body and generate `ClientRequest`. + /// + /// `ClientRequestBuilder` can not be used after this call. + pub fn finish(&mut self) -> Result { + if let Some(e) = self.err.take() { + return Err(e); + } + + if self.default_headers { + // enable br only for https + let https = if let Some(parts) = parts(&mut self.request, &self.err) { + parts + .uri + .scheme_part() + .map(|s| s == &uri::Scheme::HTTPS) + .unwrap_or(true) + } else { + true + }; + + if https { + self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate"); + } else { + self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate"); + } + + // set request host header + if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(host) = parts.uri.host() { + if !parts.headers.contains_key(header::HOST) { + let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); + + let _ = match parts.uri.port() { + None | Some(80) | Some(443) => write!(wrt, "{}", host), + Some(port) => write!(wrt, "{}:{}", host, port), + }; + + match wrt.get_mut().take().freeze().try_into() { + Ok(value) => { + parts.headers.insert(header::HOST, value); + } + Err(e) => self.err = Some(e.into()), + } + } + } + } + + // user agent + self.set_header_if_none( + header::USER_AGENT, + concat!("actix-http/", env!("CARGO_PKG_VERSION")), + ); + } + + let mut request = self.request.take().expect("cannot reuse request builder"); + + // set cookies + if let Some(ref mut jar) = self.cookies { + let mut cookie = String::new(); + for c in jar.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); + } + request.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); + } + Ok(request) + } + + /// This method construct new `ClientRequestBuilder` + pub fn take(&mut self) -> ClientRequestBuilder { + ClientRequestBuilder { + request: self.request.take(), + err: self.err.take(), + cookies: self.cookies.take(), + default_headers: self.default_headers, + } + } +} + +#[inline] +fn parts<'a>( + parts: &'a mut Option, err: &Option, +) -> Option<&'a mut ClientRequest> { + if err.is_some() { + return None; + } + parts.as_mut() +} + +impl fmt::Debug for ClientRequestBuilder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(ref parts) = self.request { + writeln!( + f, + "\nClientRequestBuilder {:?} {}:{}", + parts.version, parts.method, parts.uri + )?; + writeln!(f, " headers:")?; + for (key, val) in parts.headers.iter() { + writeln!(f, " {:?}: {:?}", key, val)?; + } + Ok(()) + } else { + write!(f, "ClientRequestBuilder(Consumed)") + } + } +} diff --git a/src/client/response.rs b/src/client/response.rs new file mode 100644 index 000000000..627a1c78e --- /dev/null +++ b/src/client/response.rs @@ -0,0 +1,128 @@ +use std::cell::{Cell, Ref, RefCell, RefMut}; +use std::fmt; +use std::rc::Rc; + +use http::{HeaderMap, Method, StatusCode, Version}; + +use extensions::Extensions; +use httpmessage::HttpMessage; +use payload::Payload; +use request::{Message, MessageFlags, MessagePool}; +use uri::Url; + +/// Client Response +pub struct ClientResponse { + pub(crate) inner: Rc, +} + +impl HttpMessage for ClientResponse { + type Stream = Payload; + + fn headers(&self) -> &HeaderMap { + &self.inner.headers + } + + #[inline] + fn payload(&self) -> Payload { + if let Some(payload) = self.inner.payload.borrow_mut().take() { + payload + } else { + Payload::empty() + } + } +} + +impl ClientResponse { + /// Create new Request instance + pub fn new() -> ClientResponse { + ClientResponse::with_pool(MessagePool::pool()) + } + + /// Create new Request instance with pool + pub(crate) fn with_pool(pool: &'static MessagePool) -> ClientResponse { + ClientResponse { + inner: Rc::new(Message { + pool, + method: Method::GET, + status: StatusCode::OK, + url: Url::default(), + version: Version::HTTP_11, + headers: HeaderMap::with_capacity(16), + flags: Cell::new(MessageFlags::empty()), + payload: RefCell::new(None), + extensions: RefCell::new(Extensions::new()), + }), + } + } + + #[inline] + pub(crate) fn inner(&self) -> &Message { + self.inner.as_ref() + } + + #[inline] + pub(crate) fn inner_mut(&mut self) -> &mut Message { + Rc::get_mut(&mut self.inner).expect("Multiple copies exist") + } + + /// Read the Request Version. + #[inline] + pub fn version(&self) -> Version { + self.inner().version + } + + /// Get the status from the server. + #[inline] + pub fn status(&self) -> StatusCode { + self.inner().status + } + + #[inline] + /// Returns Request's headers. + pub fn headers(&self) -> &HeaderMap { + &self.inner().headers + } + + #[inline] + /// Returns mutable Request's headers. + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.inner_mut().headers + } + + /// Checks if a connection should be kept alive. + #[inline] + pub fn keep_alive(&self) -> bool { + self.inner().flags.get().contains(MessageFlags::KEEPALIVE) + } + + /// Request extensions + #[inline] + pub fn extensions(&self) -> Ref { + self.inner().extensions.borrow() + } + + /// Mutable reference to a the request's extensions + #[inline] + pub fn extensions_mut(&self) -> RefMut { + self.inner().extensions.borrow_mut() + } +} + +impl Drop for ClientResponse { + fn drop(&mut self) { + if Rc::strong_count(&self.inner) == 1 { + self.inner.pool.release(self.inner.clone()); + } + } +} + +impl fmt::Debug for ClientResponse { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status(),)?; + writeln!(f, " headers:")?; + for (key, val) in self.headers().iter() { + writeln!(f, " {:?}: {:?}", key, val)?; + } + Ok(()) + } +} diff --git a/src/h1/client.rs b/src/h1/client.rs new file mode 100644 index 000000000..8ec1f0aea --- /dev/null +++ b/src/h1/client.rs @@ -0,0 +1,217 @@ +#![allow(unused_imports, unused_variables, dead_code)] +use std::io::{self, Write}; + +use bytes::{BufMut, Bytes, BytesMut}; +use tokio_codec::{Decoder, Encoder}; + +use super::decoder::{PayloadDecoder, PayloadItem, PayloadType, ResponseDecoder}; +use super::encoder::{RequestEncoder, ResponseLength}; +use super::{Message, MessageType}; +use body::{Binary, Body}; +use client::{ClientRequest, ClientResponse}; +use config::ServiceConfig; +use error::ParseError; +use helpers; +use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; +use http::{Method, Version}; +use request::MessagePool; + +bitflags! { + struct Flags: u8 { + const HEAD = 0b0000_0001; + const UPGRADE = 0b0000_0010; + const KEEPALIVE = 0b0000_0100; + const KEEPALIVE_ENABLED = 0b0000_1000; + const UNHANDLED = 0b0001_0000; + } +} + +const AVERAGE_HEADER_SIZE: usize = 30; + +/// HTTP/1 Codec +pub struct ClientCodec { + config: ServiceConfig, + decoder: ResponseDecoder, + payload: Option, + version: Version, + + // encoder part + flags: Flags, + headers_size: u32, + te: RequestEncoder, +} + +impl Default for ClientCodec { + fn default() -> Self { + ClientCodec::new(ServiceConfig::default()) + } +} + +impl ClientCodec { + /// Create HTTP/1 codec. + /// + /// `keepalive_enabled` how response `connection` header get generated. + pub fn new(config: ServiceConfig) -> Self { + ClientCodec::with_pool(MessagePool::pool(), config) + } + + /// Create HTTP/1 codec with request's pool + pub(crate) fn with_pool(pool: &'static MessagePool, config: ServiceConfig) -> Self { + let flags = if config.keep_alive_enabled() { + Flags::KEEPALIVE_ENABLED + } else { + Flags::empty() + }; + ClientCodec { + config, + decoder: ResponseDecoder::with_pool(pool), + payload: None, + version: Version::HTTP_11, + + flags, + headers_size: 0, + te: RequestEncoder::default(), + } + } + + /// Check if request is upgrade + pub fn upgrade(&self) -> bool { + self.flags.contains(Flags::UPGRADE) + } + + /// Check if last response is keep-alive + pub fn keepalive(&self) -> bool { + self.flags.contains(Flags::KEEPALIVE) + } + + /// Check last request's message type + pub fn message_type(&self) -> MessageType { + if self.flags.contains(Flags::UNHANDLED) { + MessageType::Unhandled + } else if self.payload.is_none() { + MessageType::None + } else { + MessageType::Payload + } + } + + /// prepare transfer encoding + pub fn prepare_te(&mut self, res: &mut ClientRequest) { + self.te + .update(res, self.flags.contains(Flags::HEAD), self.version); + } + + fn encode_response( + &mut self, msg: ClientRequest, buffer: &mut BytesMut, + ) -> io::Result<()> { + // Connection upgrade + if msg.upgrade() { + self.flags.insert(Flags::UPGRADE); + } + + // render message + { + // status line + writeln!( + Writer(buffer), + "{} {} {:?}\r", + msg.method(), + msg.uri() + .path_and_query() + .map(|u| u.as_str()) + .unwrap_or("/"), + msg.version() + ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + + // write headers + buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE); + for (key, value) in msg.headers() { + let v = value.as_ref(); + let k = key.as_str().as_bytes(); + buffer.reserve(k.len() + v.len() + 4); + buffer.put_slice(k); + buffer.put_slice(b": "); + buffer.put_slice(v); + buffer.put_slice(b"\r\n"); + } + + // set date header + if !msg.headers().contains_key(DATE) { + self.config.set_date(buffer); + } else { + buffer.extend_from_slice(b"\r\n"); + } + } + + Ok(()) + } +} + +impl Decoder for ClientCodec { + type Item = Message; + type Error = ParseError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + if self.payload.is_some() { + Ok(match self.payload.as_mut().unwrap().decode(src)? { + Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), + Some(PayloadItem::Eof) => Some(Message::Chunk(None)), + None => None, + }) + } else if self.flags.contains(Flags::UNHANDLED) { + Ok(None) + } else if let Some((req, payload)) = self.decoder.decode(src)? { + self.flags + .set(Flags::HEAD, req.inner.method == Method::HEAD); + self.version = req.inner.version; + if self.flags.contains(Flags::KEEPALIVE_ENABLED) { + self.flags.set(Flags::KEEPALIVE, req.keep_alive()); + } + match payload { + PayloadType::None => self.payload = None, + PayloadType::Payload(pl) => self.payload = Some(pl), + PayloadType::Unhandled => { + self.payload = None; + self.flags.insert(Flags::UNHANDLED); + } + }; + Ok(Some(Message::Item(req))) + } else { + Ok(None) + } + } +} + +impl Encoder for ClientCodec { + type Item = Message; + type Error = io::Error; + + fn encode( + &mut self, item: Self::Item, dst: &mut BytesMut, + ) -> Result<(), Self::Error> { + match item { + Message::Item(res) => { + self.encode_response(res, dst)?; + } + Message::Chunk(Some(bytes)) => { + self.te.encode(bytes.as_ref(), dst)?; + } + Message::Chunk(None) => { + self.te.encode_eof(dst)?; + } + } + Ok(()) + } +} + +pub struct Writer<'a>(pub &'a mut BytesMut); + +impl<'a> io::Write for Writer<'a> { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 020466482..8f7e77e05 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -4,15 +4,16 @@ use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; -use super::decoder::{PayloadDecoder, PayloadItem, RequestDecoder, RequestPayloadType}; +use super::decoder::{PayloadDecoder, PayloadItem, PayloadType, RequestDecoder}; use super::encoder::{ResponseEncoder, ResponseLength}; +use super::{Message, MessageType}; use body::{Binary, Body}; use config::ServiceConfig; use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{Method, Version}; -use request::{Request, RequestPool}; +use request::{MessagePool, Request}; use response::Response; bitflags! { @@ -27,32 +28,6 @@ bitflags! { const AVERAGE_HEADER_SIZE: usize = 30; -#[derive(Debug)] -/// Http response -pub enum OutMessage { - /// Http response message - Response(Response), - /// Payload chunk - Chunk(Option), -} - -/// Incoming http/1 request -#[derive(Debug)] -pub enum InMessage { - /// Request - Message(Request, InMessageType), - /// Payload chunk - Chunk(Option), -} - -/// Incoming request type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum InMessageType { - None, - Payload, - Unhandled, -} - /// HTTP/1 Codec pub struct Codec { config: ServiceConfig, @@ -71,11 +46,11 @@ impl Codec { /// /// `keepalive_enabled` how response `connection` header get generated. pub fn new(config: ServiceConfig) -> Self { - Codec::with_pool(RequestPool::pool(), config) + Codec::with_pool(MessagePool::pool(), config) } /// Create HTTP/1 codec with request's pool - pub(crate) fn with_pool(pool: &'static RequestPool, config: ServiceConfig) -> Self { + pub(crate) fn with_pool(pool: &'static MessagePool, config: ServiceConfig) -> Self { let flags = if config.keep_alive_enabled() { Flags::KEEPALIVE_ENABLED } else { @@ -104,13 +79,13 @@ impl Codec { } /// Check last request's message type - pub fn message_type(&self) -> InMessageType { + pub fn message_type(&self) -> MessageType { if self.flags.contains(Flags::UNHANDLED) { - InMessageType::Unhandled + MessageType::Unhandled } else if self.payload.is_none() { - InMessageType::None + MessageType::None } else { - InMessageType::Payload + MessageType::Payload } } @@ -256,14 +231,14 @@ impl Codec { } impl Decoder for Codec { - type Item = InMessage; + type Item = Message; type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { if self.payload.is_some() { Ok(match self.payload.as_mut().unwrap().decode(src)? { - Some(PayloadItem::Chunk(chunk)) => Some(InMessage::Chunk(Some(chunk))), - Some(PayloadItem::Eof) => Some(InMessage::Chunk(None)), + Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), + Some(PayloadItem::Eof) => Some(Message::Chunk(None)), None => None, }) } else if self.flags.contains(Flags::UNHANDLED) { @@ -275,22 +250,15 @@ impl Decoder for Codec { if self.flags.contains(Flags::KEEPALIVE_ENABLED) { self.flags.set(Flags::KEEPALIVE, req.keep_alive()); } - let payload = match payload { - RequestPayloadType::None => { - self.payload = None; - InMessageType::None - } - RequestPayloadType::Payload(pl) => { - self.payload = Some(pl); - InMessageType::Payload - } - RequestPayloadType::Unhandled => { + match payload { + PayloadType::None => self.payload = None, + PayloadType::Payload(pl) => self.payload = Some(pl), + PayloadType::Unhandled => { self.payload = None; self.flags.insert(Flags::UNHANDLED); - InMessageType::Unhandled } - }; - Ok(Some(InMessage::Message(req, payload))) + } + Ok(Some(Message::Item(req))) } else { Ok(None) } @@ -298,20 +266,20 @@ impl Decoder for Codec { } impl Encoder for Codec { - type Item = OutMessage; + type Item = Message; type Error = io::Error; fn encode( &mut self, item: Self::Item, dst: &mut BytesMut, ) -> Result<(), Self::Error> { match item { - OutMessage::Response(res) => { + Message::Item(res) => { self.encode_response(res, dst)?; } - OutMessage::Chunk(Some(bytes)) => { + Message::Chunk(Some(bytes)) => { self.te.encode(bytes.as_ref(), dst)?; } - OutMessage::Chunk(None) => { + Message::Chunk(None) => { self.te.encode_eof(dst)?; } } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index c2a8d0e99..2c1e6c1f2 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -5,38 +5,43 @@ use futures::{Async, Poll}; use httparse; use tokio_codec::Decoder; +use client::ClientResponse; use error::ParseError; use http::header::{HeaderName, HeaderValue}; -use http::{header, HttpTryFrom, Method, Uri, Version}; -use request::{MessageFlags, Request, RequestPool}; +use http::{header, HttpTryFrom, Method, StatusCode, Uri, Version}; +use request::{MessageFlags, MessagePool, Request}; use uri::Url; const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; -pub struct RequestDecoder(&'static RequestPool); +/// Client request decoder +pub struct RequestDecoder(&'static MessagePool); + +/// Server response decoder +pub struct ResponseDecoder(&'static MessagePool); /// Incoming request type -pub enum RequestPayloadType { +pub enum PayloadType { None, Payload(PayloadDecoder), Unhandled, } impl RequestDecoder { - pub(crate) fn with_pool(pool: &'static RequestPool) -> RequestDecoder { + pub(crate) fn with_pool(pool: &'static MessagePool) -> RequestDecoder { RequestDecoder(pool) } } impl Default for RequestDecoder { fn default() -> RequestDecoder { - RequestDecoder::with_pool(RequestPool::pool()) + RequestDecoder::with_pool(MessagePool::pool()) } } impl Decoder for RequestDecoder { - type Item = (Request, RequestPayloadType); + type Item = (Request, PayloadType); type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { @@ -77,7 +82,7 @@ impl Decoder for RequestDecoder { let slice = src.split_to(len).freeze(); // convert headers - let mut msg = RequestPool::get(self.0); + let mut msg = MessagePool::get_request(self.0); { let inner = msg.inner_mut(); inner @@ -156,18 +161,165 @@ impl Decoder for RequestDecoder { // https://tools.ietf.org/html/rfc7230#section-3.3.3 let decoder = if chunked { // Chunked encoding - RequestPayloadType::Payload(PayloadDecoder::chunked()) + PayloadType::Payload(PayloadDecoder::chunked()) } else if let Some(len) = content_length { // Content-Length - RequestPayloadType::Payload(PayloadDecoder::length(len)) + PayloadType::Payload(PayloadDecoder::length(len)) } else if has_upgrade || msg.inner.method == Method::CONNECT { // upgrade(websocket) or connect - RequestPayloadType::Unhandled + PayloadType::Unhandled } else if src.len() >= MAX_BUFFER_SIZE { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); return Err(ParseError::TooLarge); } else { - RequestPayloadType::None + PayloadType::None + }; + + Ok(Some((msg, decoder))) + } +} + +impl ResponseDecoder { + pub(crate) fn with_pool(pool: &'static MessagePool) -> ResponseDecoder { + ResponseDecoder(pool) + } +} + +impl Default for ResponseDecoder { + fn default() -> ResponseDecoder { + ResponseDecoder::with_pool(MessagePool::pool()) + } +} + +impl Decoder for ResponseDecoder { + type Item = (ClientResponse, PayloadType); + type Error = ParseError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + // Parse http message + let mut chunked = false; + let mut content_length = None; + + let msg = { + // Unsafe: we read only this data only after httparse parses headers into. + // performance bump for pipeline benchmarks. + let mut headers: [HeaderIndex; MAX_HEADERS] = + unsafe { mem::uninitialized() }; + + let (len, version, status, headers_len) = { + let mut parsed: [httparse::Header; MAX_HEADERS] = + unsafe { mem::uninitialized() }; + + let mut res = httparse::Response::new(&mut parsed); + match res.parse(src)? { + httparse::Status::Complete(len) => { + let version = if res.version.unwrap() == 1 { + Version::HTTP_11 + } else { + Version::HTTP_10 + }; + let status = StatusCode::from_u16(res.code.unwrap()) + .map_err(|_| ParseError::Status)?; + HeaderIndex::record(src, res.headers, &mut headers); + + (len, version, status, res.headers.len()) + } + httparse::Status::Partial => return Ok(None), + } + }; + + let slice = src.split_to(len).freeze(); + + // convert headers + let mut msg = MessagePool::get_response(self.0); + { + let inner = msg.inner_mut(); + inner + .flags + .get_mut() + .set(MessageFlags::KEEPALIVE, version != Version::HTTP_10); + + for idx in headers[..headers_len].iter() { + if let Ok(name) = + HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) + { + // Unsafe: httparse check header value for valid utf-8 + let value = unsafe { + HeaderValue::from_shared_unchecked( + slice.slice(idx.value.0, idx.value.1), + ) + }; + match name { + header::CONTENT_LENGTH => { + if let Ok(s) = value.to_str() { + if let Ok(len) = s.parse::() { + content_length = Some(len); + } else { + debug!("illegal Content-Length: {:?}", len); + return Err(ParseError::Header); + } + } else { + debug!("illegal Content-Length: {:?}", len); + return Err(ParseError::Header); + } + } + // transfer-encoding + header::TRANSFER_ENCODING => { + if let Ok(s) = value.to_str() { + chunked = s.to_lowercase().contains("chunked"); + } else { + return Err(ParseError::Header); + } + } + // connection keep-alive state + header::CONNECTION => { + let ka = if let Ok(conn) = value.to_str() { + if version == Version::HTTP_10 + && conn.contains("keep-alive") + { + true + } else { + version == Version::HTTP_11 && !(conn + .contains("close") + || conn.contains("upgrade")) + } + } else { + false + }; + inner.flags.get_mut().set(MessageFlags::KEEPALIVE, ka); + } + _ => (), + } + + inner.headers.append(name, value); + } else { + return Err(ParseError::Header); + } + } + + inner.status = status; + inner.version = version; + } + msg + }; + + // https://tools.ietf.org/html/rfc7230#section-3.3.3 + let decoder = if chunked { + // Chunked encoding + PayloadType::Payload(PayloadDecoder::chunked()) + } else if let Some(len) = content_length { + // Content-Length + PayloadType::Payload(PayloadDecoder::length(len)) + } else if msg.inner.status == StatusCode::SWITCHING_PROTOCOLS + || msg.inner.method == Method::CONNECT + { + // switching protocol or connect + PayloadType::Unhandled + } else if src.len() >= MAX_BUFFER_SIZE { + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + return Err(ParseError::TooLarge); + } else { + PayloadType::None }; Ok(Some((msg, decoder))) @@ -488,36 +640,30 @@ mod tests { use super::*; use error::ParseError; - use h1::{InMessage, InMessageType}; + use h1::Message; use httpmessage::HttpMessage; use request::Request; - impl RequestPayloadType { + impl PayloadType { fn unwrap(self) -> PayloadDecoder { match self { - RequestPayloadType::Payload(pl) => pl, + PayloadType::Payload(pl) => pl, _ => panic!(), } } fn is_unhandled(&self) -> bool { match self { - RequestPayloadType::Unhandled => true, + PayloadType::Unhandled => true, _ => false, } } } - impl InMessage { + impl Message { fn message(self) -> Request { match self { - InMessage::Message(req, _) => req, - _ => panic!("error"), - } - } - fn is_payload(&self) -> bool { - match *self { - InMessage::Message(_, payload) => payload == InMessageType::Payload, + Message::Item(req) => req, _ => panic!("error"), } } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 8ae2ae8ce..a7f97e8c9 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -18,8 +18,8 @@ use error::DispatchError; use request::Request; use response::Response; -use super::codec::{Codec, InMessage, InMessageType, OutMessage}; -use super::H1ServiceResult; +use super::codec::Codec; +use super::{H1ServiceResult, Message, MessageType}; const MAX_PIPELINED_MESSAGES: usize = 16; @@ -48,14 +48,14 @@ where state: State, payload: Option, - messages: VecDeque, + messages: VecDeque, unhandled: Option, ka_expire: Instant, ka_timer: Option, } -enum Message { +enum DispatcherMessage { Item(Request), Error(Response), } @@ -63,8 +63,8 @@ enum Message { enum State { None, ServiceCall(S::Future), - SendResponse(Option<(OutMessage, Body)>), - SendPayload(Option, Option), + SendResponse(Option<(Message, Body)>), + SendPayload(Option, Option>), } impl State { @@ -176,11 +176,12 @@ where State::None => loop { break if let Some(msg) = self.messages.pop_front() { match msg { - Message::Item(req) => Some(self.handle_request(req)?), - Message::Error(res) => Some(State::SendResponse(Some(( - OutMessage::Response(res), - Body::Empty, - )))), + DispatcherMessage::Item(req) => { + Some(self.handle_request(req)?) + } + DispatcherMessage::Error(res) => Some(State::SendResponse( + Some((Message::Item(res), Body::Empty)), + )), } } else { None @@ -196,10 +197,7 @@ where .get_codec_mut() .prepare_te(&mut res); let body = res.replace_body(Body::Empty); - Some(State::SendResponse(Some(( - OutMessage::Response(res), - body, - )))) + Some(State::SendResponse(Some((Message::Item(res), body)))) } Async::NotReady => None, } @@ -216,9 +214,9 @@ where self.flags.remove(Flags::FLUSHED); match body { Body::Empty => Some(State::None), - Body::Binary(bin) => Some(State::SendPayload( + Body::Binary(mut bin) => Some(State::SendPayload( None, - Some(OutMessage::Chunk(bin.into())), + Some(Message::Chunk(Some(bin.take()))), )), Body::Streaming(stream) => { Some(State::SendPayload(Some(stream), None)) @@ -257,7 +255,7 @@ where .framed .as_mut() .unwrap() - .start_send(OutMessage::Chunk(Some(item.into()))) + .start_send(Message::Chunk(Some(item.into()))) { Ok(AsyncSink::Ready) => { self.flags.remove(Flags::FLUSHED); @@ -271,7 +269,7 @@ where }, Ok(Async::Ready(None)) => Some(State::SendPayload( None, - Some(OutMessage::Chunk(None)), + Some(Message::Chunk(None)), )), Ok(Async::NotReady) => return Ok(()), // Err(err) => return Err(DispatchError::Io(err)), @@ -312,7 +310,7 @@ where .get_codec_mut() .prepare_te(&mut res); let body = res.replace_body(Body::Empty); - Ok(State::SendResponse(Some((OutMessage::Response(res), body)))) + Ok(State::SendResponse(Some((Message::Item(res), body)))) } Async::NotReady => Ok(State::ServiceCall(task)), } @@ -333,14 +331,20 @@ where self.flags.insert(Flags::STARTED); match msg { - InMessage::Message(req, payload) => { - match payload { - InMessageType::Payload => { + Message::Item(req) => { + match self + .framed + .as_ref() + .unwrap() + .get_codec() + .message_type() + { + MessageType::Payload => { let (ps, pl) = Payload::new(false); *req.inner.payload.borrow_mut() = Some(pl); self.payload = Some(ps); } - InMessageType::Unhandled => { + MessageType::Unhandled => { self.unhandled = Some(req); return Ok(updated); } @@ -351,10 +355,10 @@ where if self.state.is_empty() { self.state = self.handle_request(req)?; } else { - self.messages.push_back(Message::Item(req)); + self.messages.push_back(DispatcherMessage::Item(req)); } } - InMessage::Chunk(Some(chunk)) => { + Message::Chunk(Some(chunk)) => { if let Some(ref mut payload) = self.payload { payload.feed_data(chunk); } else { @@ -362,19 +366,19 @@ where "Internal server error: unexpected payload chunk" ); self.flags.insert(Flags::DISCONNECTED); - self.messages.push_back(Message::Error( + self.messages.push_back(DispatcherMessage::Error( Response::InternalServerError().finish(), )); self.error = Some(DispatchError::InternalError); } } - InMessage::Chunk(None) => { + Message::Chunk(None) => { if let Some(mut payload) = self.payload.take() { payload.feed_eof(); } else { error!("Internal server error: unexpected eof"); self.flags.insert(Flags::DISCONNECTED); - self.messages.push_back(Message::Error( + self.messages.push_back(DispatcherMessage::Error( Response::InternalServerError().finish(), )); self.error = Some(DispatchError::InternalError); @@ -398,8 +402,9 @@ where } // Malformed requests should be responded with 400 - self.messages - .push_back(Message::Error(Response::BadRequest().finish())); + self.messages.push_back(DispatcherMessage::Error( + Response::BadRequest().finish(), + )); self.flags.insert(Flags::DISCONNECTED); self.error = Some(e.into()); break; @@ -443,9 +448,7 @@ where trace!("Slow request timeout"); self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); self.state = State::SendResponse(Some(( - OutMessage::Response( - Response::RequestTimeout().finish(), - ), + Message::Item(Response::RequestTimeout().finish()), Body::Empty, ))); } diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index ea11f11fd..63ba05d04 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -9,6 +9,7 @@ use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; use body::{Binary, Body}; +use client::ClientRequest; use header::ContentEncoding; use http::Method; use request::Request; @@ -165,6 +166,39 @@ impl ResponseEncoder { } } +#[derive(Debug)] +pub(crate) struct RequestEncoder { + head: bool, + pub length: ResponseLength, + pub te: TransferEncoding, +} + +impl Default for RequestEncoder { + fn default() -> Self { + RequestEncoder { + head: false, + length: ResponseLength::None, + te: TransferEncoding::empty(), + } + } +} + +impl RequestEncoder { + /// Encode message + pub fn encode(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result { + self.te.encode(msg, buf) + } + + /// Encode eof + pub fn encode_eof(&mut self, buf: &mut BytesMut) -> io::Result<()> { + self.te.encode_eof(buf) + } + + pub fn update(&mut self, resp: &mut ClientRequest, head: bool, version: Version) { + self.head = head; + } +} + /// Encoders to handle different Transfer-Encodings. #[derive(Debug)] pub(crate) struct TransferEncoding { diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 1ac65912e..c33b193f5 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -1,13 +1,16 @@ //! HTTP/1 implementation use actix_net::codec::Framed; +use bytes::Bytes; +mod client; mod codec; mod decoder; mod dispatcher; mod encoder; mod service; -pub use self::codec::{Codec, InMessage, InMessageType, OutMessage}; +pub use self::client::ClientCodec; +pub use self::codec::Codec; pub use self::decoder::{PayloadDecoder, RequestDecoder}; pub use self::dispatcher::Dispatcher; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; @@ -20,3 +23,26 @@ pub enum H1ServiceResult { Shutdown(T), Unhandled(Request, Framed), } + +#[derive(Debug)] +/// Codec message +pub enum Message { + /// Http message + Item(T), + /// Payload chunk + Chunk(Option), +} + +impl From for Message { + fn from(item: T) -> Self { + Message::Item(item) + } +} + +/// Incoming request type +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MessageType { + None, + Payload, + Unhandled, +} diff --git a/src/h1/service.rs b/src/h1/service.rs index 096cb3016..d691ae75d 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -13,9 +13,9 @@ use error::{DispatchError, ParseError}; use request::Request; use response::Response; -use super::codec::{Codec, InMessage}; +use super::codec::Codec; use super::dispatcher::Dispatcher; -use super::H1ServiceResult; +use super::{H1ServiceResult, Message}; /// `NewService` implementation for HTTP1 transport pub struct H1Service { @@ -344,10 +344,10 @@ where fn poll(&mut self) -> Poll { match self.framed.as_mut().unwrap().poll()? { Async::Ready(Some(req)) => match req { - InMessage::Message(req, _) => { + Message::Item(req) => { Ok(Async::Ready((req, self.framed.take().unwrap()))) } - InMessage::Chunk(_) => unreachable!("Something is wrong"), + Message::Chunk(_) => unreachable!("Something is wrong"), }, Async::Ready(None) => Err(ParseError::Incomplete), Async::NotReady => Ok(Async::NotReady), diff --git a/src/lib.rs b/src/lib.rs index c9cebb47d..572c23ae1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,7 @@ extern crate http as modhttp; extern crate httparse; extern crate mime; extern crate net2; +extern crate percent_encoding; extern crate rand; extern crate serde; extern crate serde_json; @@ -95,12 +96,14 @@ extern crate tokio_current_thread; extern crate tokio_io; extern crate tokio_tcp; extern crate tokio_timer; +extern crate url as urlcrate; #[cfg(test)] #[macro_use] extern crate serde_derive; mod body; +pub mod client; mod config; mod extensions; mod header; diff --git a/src/request.rs b/src/request.rs index ef28e3694..ad0486395 100644 --- a/src/request.rs +++ b/src/request.rs @@ -3,8 +3,9 @@ use std::collections::VecDeque; use std::fmt; use std::rc::Rc; -use http::{header, HeaderMap, Method, Uri, Version}; +use http::{header, HeaderMap, Method, StatusCode, Uri, Version}; +use client::ClientResponse; use extensions::Extensions; use httpmessage::HttpMessage; use payload::Payload; @@ -17,23 +18,24 @@ bitflags! { } } -/// Request's context +/// Request pub struct Request { - pub(crate) inner: Rc, + pub(crate) inner: Rc, } -pub(crate) struct InnerRequest { +pub(crate) struct Message { pub(crate) version: Version, + pub(crate) status: StatusCode, pub(crate) method: Method, pub(crate) url: Url, pub(crate) flags: Cell, pub(crate) headers: HeaderMap, pub(crate) extensions: RefCell, pub(crate) payload: RefCell>, - pool: &'static RequestPool, + pub(crate) pool: &'static MessagePool, } -impl InnerRequest { +impl Message { #[inline] /// Reset request instance pub fn reset(&mut self) { @@ -64,15 +66,16 @@ impl HttpMessage for Request { impl Request { /// Create new Request instance pub fn new() -> Request { - Request::with_pool(RequestPool::pool()) + Request::with_pool(MessagePool::pool()) } /// Create new Request instance with pool - pub(crate) fn with_pool(pool: &'static RequestPool) -> Request { + pub(crate) fn with_pool(pool: &'static MessagePool) -> Request { Request { - inner: Rc::new(InnerRequest { + inner: Rc::new(Message { pool, method: Method::GET, + status: StatusCode::OK, url: Url::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), @@ -84,12 +87,12 @@ impl Request { } #[inline] - pub(crate) fn inner(&self) -> &InnerRequest { + pub(crate) fn inner(&self) -> &Message { self.inner.as_ref() } #[inline] - pub(crate) fn inner_mut(&mut self) -> &mut InnerRequest { + pub(crate) fn inner_mut(&mut self) -> &mut Message { Rc::get_mut(&mut self.inner).expect("Multiple copies exist") } @@ -201,24 +204,24 @@ impl fmt::Debug for Request { } /// Request's objects pool -pub(crate) struct RequestPool(RefCell>>); +pub(crate) struct MessagePool(RefCell>>); -thread_local!(static POOL: &'static RequestPool = RequestPool::create()); +thread_local!(static POOL: &'static MessagePool = MessagePool::create()); -impl RequestPool { - fn create() -> &'static RequestPool { - let pool = RequestPool(RefCell::new(VecDeque::with_capacity(128))); +impl MessagePool { + fn create() -> &'static MessagePool { + let pool = MessagePool(RefCell::new(VecDeque::with_capacity(128))); Box::leak(Box::new(pool)) } /// Get default request's pool - pub fn pool() -> &'static RequestPool { + pub fn pool() -> &'static MessagePool { POOL.with(|p| *p) } /// Get Request object #[inline] - pub fn get(pool: &'static RequestPool) -> Request { + pub fn get_request(pool: &'static MessagePool) -> Request { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { if let Some(r) = Rc::get_mut(&mut msg) { r.reset(); @@ -228,9 +231,21 @@ impl RequestPool { Request::with_pool(pool) } + /// Get Client Response object + #[inline] + pub fn get_response(pool: &'static MessagePool) -> ClientResponse { + if let Some(mut msg) = pool.0.borrow_mut().pop_front() { + if let Some(r) = Rc::get_mut(&mut msg) { + r.reset(); + } + return ClientResponse { inner: msg }; + } + ClientResponse::with_pool(pool) + } + #[inline] /// Release request instance - pub(crate) fn release(&self, msg: Rc) { + pub(crate) fn release(&self, msg: Rc) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { v.push_front(msg); diff --git a/src/service.rs b/src/service.rs index 4467087bc..a28529145 100644 --- a/src/service.rs +++ b/src/service.rs @@ -8,7 +8,7 @@ use futures::{Async, AsyncSink, Future, Poll, Sink}; use tokio_io::AsyncWrite; use error::ResponseError; -use h1::{Codec, OutMessage}; +use h1::{Codec, Message}; use response::Response; pub struct SendError(PhantomData<(T, R, E)>); @@ -59,7 +59,7 @@ where Ok(r) => Either::A(ok(r)), Err((e, framed)) => Either::B(SendErrorFut { framed: Some(framed), - res: Some(OutMessage::Response(e.error_response())), + res: Some(Message::Item(e.error_response())), err: Some(e), _t: PhantomData, }), @@ -68,7 +68,7 @@ where } pub struct SendErrorFut { - res: Option, + res: Option>, framed: Option>, err: Option, _t: PhantomData, @@ -149,14 +149,14 @@ where fn call(&mut self, (res, framed): Self::Request) -> Self::Future { SendResponseFut { - res: Some(OutMessage::Response(res)), + res: Some(Message::Item(res)), framed: Some(framed), } } } pub struct SendResponseFut { - res: Option, + res: Option>, framed: Option>, } diff --git a/src/ws/client/connect.rs b/src/ws/client/connect.rs new file mode 100644 index 000000000..575b4e4d8 --- /dev/null +++ b/src/ws/client/connect.rs @@ -0,0 +1,95 @@ +//! Http client request +use std::str; + +use cookie::Cookie; +use http::header::{HeaderName, HeaderValue}; +use http::{Error as HttpError, HttpTryFrom}; + +use client::{ClientRequest, ClientRequestBuilder}; +use header::IntoHeaderValue; + +use super::ClientError; + +/// `WebSocket` connection +pub struct Connect { + pub(super) request: ClientRequestBuilder, + pub(super) err: Option, + pub(super) http_err: Option, + pub(super) origin: Option, + pub(super) protocols: Option, + pub(super) max_size: usize, + pub(super) server_mode: bool, +} + +impl Connect { + /// Create new websocket connection + pub fn new>(uri: S) -> Connect { + let mut cl = Connect { + request: ClientRequest::build(), + err: None, + http_err: None, + origin: None, + protocols: None, + max_size: 65_536, + server_mode: false, + }; + cl.request.uri(uri.as_ref()); + cl + } + + /// Set supported websocket protocols + pub fn protocols(mut self, protos: U) -> Self + where + U: IntoIterator + 'static, + V: AsRef, + { + let mut protos = protos + .into_iter() + .fold(String::new(), |acc, s| acc + s.as_ref() + ","); + protos.pop(); + self.protocols = Some(protos); + self + } + + /// Set cookie for handshake request + pub fn cookie(mut self, cookie: Cookie) -> Self { + self.request.cookie(cookie); + self + } + + /// Set request Origin + pub fn origin(mut self, origin: V) -> Self + where + HeaderValue: HttpTryFrom, + { + match HeaderValue::try_from(origin) { + Ok(value) => self.origin = Some(value), + Err(e) => self.http_err = Some(e.into()), + } + self + } + + /// Set max frame size + /// + /// By default max size is set to 64kb + pub fn max_frame_size(mut self, size: usize) -> Self { + self.max_size = size; + self + } + + /// Disable payload masking. By default ws client masks frame payload. + pub fn server_mode(mut self) -> Self { + self.server_mode = true; + self + } + + /// Set request header + pub fn header(mut self, key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + self.request.header(key, value); + self + } +} diff --git a/src/ws/client/error.rs b/src/ws/client/error.rs new file mode 100644 index 000000000..62a8800aa --- /dev/null +++ b/src/ws/client/error.rs @@ -0,0 +1,87 @@ +//! Http client request +use std::io; + +use actix_net::connector::ConnectorError; +use http::header::HeaderValue; +use http::StatusCode; + +use error::ParseError; +use http::Error as HttpError; +use ws::ProtocolError; + +/// Websocket client error +#[derive(Fail, Debug)] +pub enum ClientError { + /// Invalid url + #[fail(display = "Invalid url")] + InvalidUrl, + /// Invalid response status + #[fail(display = "Invalid response status")] + InvalidResponseStatus(StatusCode), + /// Invalid upgrade header + #[fail(display = "Invalid upgrade header")] + InvalidUpgradeHeader, + /// Invalid connection header + #[fail(display = "Invalid connection header")] + InvalidConnectionHeader(HeaderValue), + /// Missing CONNECTION header + #[fail(display = "Missing CONNECTION header")] + MissingConnectionHeader, + /// Missing SEC-WEBSOCKET-ACCEPT header + #[fail(display = "Missing SEC-WEBSOCKET-ACCEPT header")] + MissingWebSocketAcceptHeader, + /// Invalid challenge response + #[fail(display = "Invalid challenge response")] + InvalidChallengeResponse(String, HeaderValue), + /// Http parsing error + #[fail(display = "Http parsing error")] + Http(HttpError), + // /// Url parsing error + // #[fail(display = "Url parsing error")] + // Url(UrlParseError), + /// Response parsing error + #[fail(display = "Response parsing error")] + ParseError(ParseError), + /// Protocol error + #[fail(display = "{}", _0)] + Protocol(#[cause] ProtocolError), + /// Connect error + #[fail(display = "{:?}", _0)] + Connect(ConnectorError), + /// IO Error + #[fail(display = "{}", _0)] + Io(io::Error), + /// "Disconnected" + #[fail(display = "Disconnected")] + Disconnected, +} + +impl From for ClientError { + fn from(err: HttpError) -> ClientError { + ClientError::Http(err) + } +} + +impl From for ClientError { + fn from(err: ConnectorError) -> ClientError { + ClientError::Connect(err) + } +} + +impl From for ClientError { + fn from(err: ProtocolError) -> ClientError { + ClientError::Protocol(err) + } +} + +impl From for ClientError { + fn from(err: io::Error) -> ClientError { + ClientError::Io(err) + } +} + +impl From for ClientError { + fn from(err: ParseError) -> ClientError { + ClientError::ParseError(err) + } +} diff --git a/src/ws/client/mod.rs b/src/ws/client/mod.rs new file mode 100644 index 000000000..0dbf081c6 --- /dev/null +++ b/src/ws/client/mod.rs @@ -0,0 +1,48 @@ +mod connect; +mod error; +mod service; + +pub use self::connect::Connect; +pub use self::error::ClientError; +pub use self::service::Client; + +#[derive(PartialEq, Hash, Debug, Clone, Copy)] +pub(crate) enum Protocol { + Http, + Https, + Ws, + Wss, +} + +impl Protocol { + fn from(s: &str) -> Option { + match s { + "http" => Some(Protocol::Http), + "https" => Some(Protocol::Https), + "ws" => Some(Protocol::Ws), + "wss" => Some(Protocol::Wss), + _ => None, + } + } + + fn is_http(self) -> bool { + match self { + Protocol::Https | Protocol::Http => true, + _ => false, + } + } + + fn is_secure(self) -> bool { + match self { + Protocol::Https | Protocol::Wss => true, + _ => false, + } + } + + fn port(self) -> u16 { + match self { + Protocol::Http | Protocol::Ws => 80, + Protocol::Https | Protocol::Wss => 443, + } + } +} diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs new file mode 100644 index 000000000..ab0e48035 --- /dev/null +++ b/src/ws/client/service.rs @@ -0,0 +1,270 @@ +//! websockets client +use std::marker::PhantomData; + +use actix_net::codec::Framed; +use actix_net::connector::{ConnectorError, DefaultConnector}; +use actix_net::service::Service; +use base64; +use futures::future::{err, Either, FutureResult}; +use futures::{Async, Future, Poll, Sink, Stream}; +use http::header::{self, HeaderValue}; +use http::{HttpTryFrom, StatusCode}; +use rand; +use sha1::Sha1; +use tokio_io::{AsyncRead, AsyncWrite}; + +use client::ClientResponse; +use h1; +use ws::Codec; + +use super::{ClientError, Connect, Protocol}; + +/// WebSocket's client +pub struct Client +where + T: Service, + T::Response: AsyncRead + AsyncWrite, +{ + connector: T, +} + +impl Client +where + T: Service, + T::Response: AsyncRead + AsyncWrite, +{ + /// Create new websocket's client factory + pub fn new(connector: T) -> Self { + Client { connector } + } +} + +impl Default for Client> { + fn default() -> Self { + Client::new(DefaultConnector::default()) + } +} + +impl Clone for Client +where + T: Service + Clone, + T::Response: AsyncRead + AsyncWrite, +{ + fn clone(&self) -> Self { + Client { + connector: self.connector.clone(), + } + } +} + +impl Service for Client +where + T: Service, + T::Response: AsyncRead + AsyncWrite + 'static, + T::Future: 'static, +{ + type Request = Connect; + type Response = Framed; + type Error = ClientError; + type Future = Either< + FutureResult, + ClientResponseFut, + >; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.connector.poll_ready().map_err(ClientError::from) + } + + fn call(&mut self, mut req: Self::Request) -> Self::Future { + if let Some(e) = req.err.take() { + Either::A(err(e)) + } else if let Some(e) = req.http_err.take() { + Either::A(err(e.into())) + } else { + // origin + if let Some(origin) = req.origin.take() { + req.request.set_header(header::ORIGIN, origin); + } + + req.request.upgrade(); + req.request.set_header(header::UPGRADE, "websocket"); + req.request.set_header(header::CONNECTION, "upgrade"); + req.request.set_header(header::SEC_WEBSOCKET_VERSION, "13"); + + if let Some(protocols) = req.protocols.take() { + req.request + .set_header(header::SEC_WEBSOCKET_PROTOCOL, protocols.as_str()); + } + let mut request = match req.request.finish() { + Ok(req) => req, + Err(e) => return Either::A(err(e.into())), + }; + + if request.uri().host().is_none() { + return Either::A(err(ClientError::InvalidUrl)); + } + + // supported protocols + let proto = if let Some(scheme) = request.uri().scheme_part() { + match Protocol::from(scheme.as_str()) { + Some(proto) => proto, + None => return Either::A(err(ClientError::InvalidUrl)), + } + } else { + return Either::A(err(ClientError::InvalidUrl)); + }; + + // Generate a random key for the `Sec-WebSocket-Key` header. + // a base64-encoded (see Section 4 of [RFC4648]) value that, + // when decoded, is 16 bytes in length (RFC 6455) + let sec_key: [u8; 16] = rand::random(); + let key = base64::encode(&sec_key); + + request.headers_mut().insert( + header::SEC_WEBSOCKET_KEY, + HeaderValue::try_from(key.as_str()).unwrap(), + ); + + // prep connection + let host = { + let uri = request.uri(); + format!( + "{}:{}", + uri.host().unwrap(), + uri.port().unwrap_or_else(|| proto.port()) + ) + }; + + let fut = Box::new( + self.connector + .call(host) + .map_err(|e| ClientError::from(e)) + .and_then(move |io| { + // h1 protocol + let framed = Framed::new(io, h1::ClientCodec::default()); + framed + .send(request.into()) + .map_err(|e| ClientError::from(e)) + .and_then(|framed| { + framed + .into_future() + .map_err(|(e, _)| ClientError::from(e)) + }) + }), + ); + + // start handshake + Either::B(ClientResponseFut { + key, + fut, + max_size: req.max_size, + server_mode: req.server_mode, + _t: PhantomData, + }) + } + } +} + +/// Future that implementes client websocket handshake process. +/// +/// It resolves to a `Framed` instance. +pub struct ClientResponseFut +where + T: AsyncRead + AsyncWrite, +{ + fut: Box< + Future< + Item = ( + Option>, + Framed, + ), + Error = ClientError, + >, + >, + key: String, + max_size: usize, + server_mode: bool, + _t: PhantomData, +} + +impl Future for ClientResponseFut +where + T: AsyncRead + AsyncWrite, +{ + type Item = Framed; + type Error = ClientError; + + fn poll(&mut self) -> Poll { + let (item, framed) = try_ready!(self.fut.poll()); + + let res = match item { + Some(h1::Message::Item(res)) => res, + Some(h1::Message::Chunk(_)) => unreachable!(), + None => return Err(ClientError::Disconnected), + }; + + // verify response + if res.status() != StatusCode::SWITCHING_PROTOCOLS { + return Err(ClientError::InvalidResponseStatus(res.status())); + } + // Check for "UPGRADE" to websocket header + let has_hdr = if let Some(hdr) = res.headers().get(header::UPGRADE) { + if let Ok(s) = hdr.to_str() { + s.to_lowercase().contains("websocket") + } else { + false + } + } else { + false + }; + if !has_hdr { + trace!("Invalid upgrade header"); + return Err(ClientError::InvalidUpgradeHeader); + } + // Check for "CONNECTION" header + if let Some(conn) = res.headers().get(header::CONNECTION) { + if let Ok(s) = conn.to_str() { + if !s.to_lowercase().contains("upgrade") { + trace!("Invalid connection header: {}", s); + return Err(ClientError::InvalidConnectionHeader(conn.clone())); + } + } else { + trace!("Invalid connection header: {:?}", conn); + return Err(ClientError::InvalidConnectionHeader(conn.clone())); + } + } else { + trace!("Missing connection header"); + return Err(ClientError::MissingConnectionHeader); + } + + if let Some(key) = res.headers().get(header::SEC_WEBSOCKET_ACCEPT) { + // field is constructed by concatenating /key/ + // with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455) + const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + let mut sha1 = Sha1::new(); + sha1.update(self.key.as_ref()); + sha1.update(WS_GUID); + let encoded = base64::encode(&sha1.digest().bytes()); + if key.as_bytes() != encoded.as_bytes() { + trace!( + "Invalid challenge response: expected: {} received: {:?}", + encoded, + key + ); + return Err(ClientError::InvalidChallengeResponse(encoded, key.clone())); + } + } else { + trace!("Missing SEC-WEBSOCKET-ACCEPT header"); + return Err(ClientError::MissingWebSocketAcceptHeader); + }; + + // websockets codec + let codec = if self.server_mode { + Codec::new().max_size(self.max_size) + } else { + Codec::new().max_size(self.max_size).client_mode() + }; + + Ok(Async::Ready(framed.into_framed(codec))) + } +} diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 690c56feb..b5a667084 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -10,6 +10,7 @@ use http::{header, Method, StatusCode}; use request::Request; use response::{ConnectionType, Response, ResponseBuilder}; +mod client; mod codec; mod frame; mod mask; @@ -17,6 +18,7 @@ mod proto; mod service; mod transport; +pub use self::client::{Client, ClientError, Connect}; pub use self::codec::{Codec, Frame, Message}; pub use self::frame::Parser; pub use self::proto::{CloseCode, CloseReason, OpCode}; diff --git a/tests/test_ws.rs b/tests/test_ws.rs index a5503c209..85770fa8e 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -11,12 +11,12 @@ use actix::System; use actix_net::codec::Framed; use actix_net::framed::IntoFramed; use actix_net::server::Server; -use actix_net::service::NewServiceExt; +use actix_net::service::{NewServiceExt, Service}; use actix_net::stream::TakeItem; use actix_web::{test, ws as web_ws}; -use bytes::Bytes; -use futures::future::{ok, Either}; -use futures::{Future, Sink, Stream}; +use bytes::{Bytes, BytesMut}; +use futures::future::{lazy, ok, Either}; +use futures::{Future, IntoFuture, Sink, Stream}; use actix_http::{h1, ws, ResponseError, ServiceConfig}; @@ -51,14 +51,14 @@ fn test_simple() { .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(req, framed): (_, Framed<_, _>)| { // validate request - if let Some(h1::InMessage::Message(req, _)) = req { + if let Some(h1::Message::Item(req)) = req { match ws::verify_handshake(&req) { Err(e) => { // validation failed let resp = e.error_response(); Either::A( framed - .send(h1::OutMessage::Response(resp)) + .send(h1::Message::Item(resp)) .map_err(|_| ()) .map(|_| ()), ) @@ -66,7 +66,7 @@ fn test_simple() { Ok(_) => Either::B( // send response framed - .send(h1::OutMessage::Response( + .send(h1::Message::Item( ws::handshake_response(&req).finish(), )).map_err(|_| ()) .and_then(|framed| { @@ -116,4 +116,43 @@ fn test_simple() { ))) ); } + + // client service + let mut client = sys + .block_on(lazy(|| Ok::<_, ()>(ws::Client::default()).into_future())) + .unwrap(); + let framed = sys + .block_on(client.call(ws::Connect::new(format!("http://{}/", addr)))) + .unwrap(); + + let framed = sys + .block_on(framed.send(ws::Message::Text("text".to_string()))) + .unwrap(); + let (item, framed) = sys.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text"))))); + + let framed = sys + .block_on(framed.send(ws::Message::Binary("text".into()))) + .unwrap(); + let (item, framed) = sys.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!( + item, + Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))) + ); + + let framed = sys + .block_on(framed.send(ws::Message::Ping("text".into()))) + .unwrap(); + let (item, framed) = sys.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into()))); + + let framed = sys + .block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))) + .unwrap(); + + let (item, _framed) = sys.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!( + item, + Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) + ) } From 4260692034d9f78f2e99ea4cd00c12dbacf42929 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Oct 2018 18:52:40 -0700 Subject: [PATCH 048/208] add DefaultClient type alias --- src/ws/client/mod.rs | 2 +- src/ws/client/service.rs | 3 +++ src/ws/mod.rs | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ws/client/mod.rs b/src/ws/client/mod.rs index 0dbf081c6..12c7229b9 100644 --- a/src/ws/client/mod.rs +++ b/src/ws/client/mod.rs @@ -4,7 +4,7 @@ mod service; pub use self::connect::Connect; pub use self::error::ClientError; -pub use self::service::Client; +pub use self::service::{Client, DefaultClient}; #[derive(PartialEq, Hash, Debug, Clone, Copy)] pub(crate) enum Protocol { diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index ab0e48035..8e97b743d 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -19,6 +19,9 @@ use ws::Codec; use super::{ClientError, Connect, Protocol}; +/// Default client, uses default connector. +pub type DefaultClient = Client>; + /// WebSocket's client pub struct Client where diff --git a/src/ws/mod.rs b/src/ws/mod.rs index b5a667084..1fbbd03dd 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -18,7 +18,7 @@ mod proto; mod service; mod transport; -pub use self::client::{Client, ClientError, Connect}; +pub use self::client::{Client, ClientError, Connect, DefaultClient}; pub use self::codec::{Codec, Frame, Message}; pub use self::frame::Parser; pub use self::proto::{CloseCode, CloseReason, OpCode}; From bc6e62349c907f633377881f95fef69399fe8416 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 23 Oct 2018 21:44:20 -0700 Subject: [PATCH 049/208] update deps; export api --- Cargo.toml | 4 ++-- src/request.rs | 24 +++++++++++++----------- src/ws/client/error.rs | 3 --- src/ws/codec.rs | 1 + 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 87bd05b8b..87e705f76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,8 +36,8 @@ cell = ["actix-net/cell"] [dependencies] actix = "0.7.5" -actix-net = "0.1.1" -#actix-net = { git="https://github.com/actix/actix-net.git" } +#actix-net = "0.1.1" +actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" diff --git a/src/request.rs b/src/request.rs index ad0486395..07632bf05 100644 --- a/src/request.rs +++ b/src/request.rs @@ -23,16 +23,16 @@ pub struct Request { pub(crate) inner: Rc, } -pub(crate) struct Message { - pub(crate) version: Version, - pub(crate) status: StatusCode, - pub(crate) method: Method, - pub(crate) url: Url, - pub(crate) flags: Cell, - pub(crate) headers: HeaderMap, - pub(crate) extensions: RefCell, - pub(crate) payload: RefCell>, +pub struct Message { + pub version: Version, + pub status: StatusCode, + pub method: Method, + pub url: Url, + pub headers: HeaderMap, + pub extensions: RefCell, + pub payload: RefCell>, pub(crate) pool: &'static MessagePool, + pub(crate) flags: Cell, } impl Message { @@ -87,12 +87,14 @@ impl Request { } #[inline] - pub(crate) fn inner(&self) -> &Message { + #[doc(hidden)] + pub fn inner(&self) -> &Message { self.inner.as_ref() } #[inline] - pub(crate) fn inner_mut(&mut self) -> &mut Message { + #[doc(hidden)] + pub fn inner_mut(&mut self) -> &mut Message { Rc::get_mut(&mut self.inner).expect("Multiple copies exist") } diff --git a/src/ws/client/error.rs b/src/ws/client/error.rs index 62a8800aa..951cd6ac4 100644 --- a/src/ws/client/error.rs +++ b/src/ws/client/error.rs @@ -36,9 +36,6 @@ pub enum ClientError { /// Http parsing error #[fail(display = "Http parsing error")] Http(HttpError), - // /// Url parsing error - // #[fail(display = "Url parsing error")] - // Url(UrlParseError), /// Response parsing error #[fail(display = "Response parsing error")] ParseError(ParseError), diff --git a/src/ws/codec.rs b/src/ws/codec.rs index 7ba10672c..5bdc58199 100644 --- a/src/ws/codec.rs +++ b/src/ws/codec.rs @@ -36,6 +36,7 @@ pub enum Frame { Close(Option), } +#[derive(Debug)] /// WebSockets protocol codec pub struct Codec { max_size: usize, From cd0223e8b7387d57bbd32dacc97b8a6d4ef580d3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 23 Oct 2018 22:41:30 -0700 Subject: [PATCH 050/208] update Connector usage --- src/ws/client/service.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 8e97b743d..b494564bc 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use actix_net::codec::Framed; -use actix_net::connector::{ConnectorError, DefaultConnector}; +use actix_net::connector::{Connect as TcpConnect, ConnectorError, DefaultConnector}; use actix_net::service::Service; use base64; use futures::future::{err, Either, FutureResult}; @@ -20,7 +20,7 @@ use ws::Codec; use super::{ClientError, Connect, Protocol}; /// Default client, uses default connector. -pub type DefaultClient = Client>; +pub type DefaultClient = Client; /// WebSocket's client pub struct Client @@ -33,7 +33,7 @@ where impl Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { /// Create new websocket's client factory @@ -42,7 +42,7 @@ where } } -impl Default for Client> { +impl Default for Client { fn default() -> Self { Client::new(DefaultConnector::default()) } @@ -50,7 +50,7 @@ impl Default for Client> { impl Clone for Client where - T: Service + Clone, + T: Service + Clone, T::Response: AsyncRead + AsyncWrite, { fn clone(&self) -> Self { @@ -62,7 +62,7 @@ where impl Service for Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite + 'static, T::Future: 'static, { @@ -129,18 +129,14 @@ where ); // prep connection - let host = { - let uri = request.uri(); - format!( - "{}:{}", - uri.host().unwrap(), - uri.port().unwrap_or_else(|| proto.port()) - ) - }; + let connect = TcpConnect::new( + request.uri().host().unwrap(), + request.uri().port().unwrap_or_else(|| proto.port()), + ); let fut = Box::new( self.connector - .call(host) + .call(connect) .map_err(|e| ClientError::from(e)) .and_then(move |io| { // h1 protocol From 540ad18432ed7be876fe7deae9f00415c116dbf4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 24 Oct 2018 16:48:45 -0700 Subject: [PATCH 051/208] add Debug impl --- src/h1/codec.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 8f7e77e05..1484d56b1 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -1,4 +1,5 @@ #![allow(unused_imports, unused_variables, dead_code)] +use std::fmt; use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; @@ -41,6 +42,12 @@ pub struct Codec { te: ResponseEncoder, } +impl fmt::Debug for Codec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "h1::Codec({:?})", self.flags) + } +} + impl Codec { /// Create HTTP/1 codec. /// From c2540cc59b8967291cf8166bcfb256a5091420a0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 29 Oct 2018 16:39:46 -0700 Subject: [PATCH 052/208] clippy warnings --- src/client/request.rs | 3 ++- src/config.rs | 4 +++- src/h1/client.rs | 8 ++++++-- src/h1/codec.rs | 8 ++++++-- src/h1/decoder.rs | 16 ++++++++++++---- src/h1/dispatcher.rs | 34 +++++++++++++++++----------------- src/h1/encoder.rs | 4 +++- src/h1/service.rs | 2 +- src/response.rs | 12 ++++++++---- src/service.rs | 6 ++---- src/ws/client/service.rs | 4 ++-- src/ws/frame.rs | 14 +++++++++++--- src/ws/mask.rs | 9 +++------ 13 files changed, 76 insertions(+), 48 deletions(-) diff --git a/src/client/request.rs b/src/client/request.rs index bf82927e9..8c7949336 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -536,7 +536,8 @@ impl ClientRequestBuilder { #[inline] fn parts<'a>( - parts: &'a mut Option, err: &Option, + parts: &'a mut Option, + err: &Option, ) -> Option<&'a mut ClientRequest> { if err.is_some() { return None; diff --git a/src/config.rs b/src/config.rs index ff22ea486..833bca7f2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -66,7 +66,9 @@ impl Default for ServiceConfig { impl ServiceConfig { /// Create instance of `ServiceConfig` pub(crate) fn new( - keep_alive: KeepAlive, client_timeout: u64, client_disconnect: u64, + keep_alive: KeepAlive, + client_timeout: u64, + client_disconnect: u64, ) -> ServiceConfig { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), diff --git a/src/h1/client.rs b/src/h1/client.rs index 8ec1f0aea..b55af185f 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -102,7 +102,9 @@ impl ClientCodec { } fn encode_response( - &mut self, msg: ClientRequest, buffer: &mut BytesMut, + &mut self, + msg: ClientRequest, + buffer: &mut BytesMut, ) -> io::Result<()> { // Connection upgrade if msg.upgrade() { @@ -187,7 +189,9 @@ impl Encoder for ClientCodec { type Error = io::Error; fn encode( - &mut self, item: Self::Item, dst: &mut BytesMut, + &mut self, + item: Self::Item, + dst: &mut BytesMut, ) -> Result<(), Self::Error> { match item { Message::Item(res) => { diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 1484d56b1..7cc516d6b 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -103,7 +103,9 @@ impl Codec { } fn encode_response( - &mut self, mut msg: Response, buffer: &mut BytesMut, + &mut self, + mut msg: Response, + buffer: &mut BytesMut, ) -> io::Result<()> { let ka = self.flags.contains(Flags::KEEPALIVE_ENABLED) && msg .keep_alive() @@ -277,7 +279,9 @@ impl Encoder for Codec { type Error = io::Error; fn encode( - &mut self, item: Self::Item, dst: &mut BytesMut, + &mut self, + item: Self::Item, + dst: &mut BytesMut, ) -> Result<(), Self::Error> { match item { Message::Item(res) => { diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 2c1e6c1f2..e8d07af12 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -334,7 +334,9 @@ pub(crate) struct HeaderIndex { impl HeaderIndex { pub(crate) fn record( - bytes: &[u8], headers: &[httparse::Header], indices: &mut [HeaderIndex], + bytes: &[u8], + headers: &[httparse::Header], + indices: &mut [HeaderIndex], ) { let bytes_ptr = bytes.as_ptr() as usize; for (header, indices) in headers.iter().zip(indices.iter_mut()) { @@ -491,7 +493,10 @@ macro_rules! byte ( impl ChunkedState { fn step( - &self, body: &mut BytesMut, size: &mut u64, buf: &mut Option, + &self, + body: &mut BytesMut, + size: &mut u64, + buf: &mut Option, ) -> Poll { use self::ChunkedState::*; match *self { @@ -554,7 +559,8 @@ impl ChunkedState { } } fn read_size_lf( - rdr: &mut BytesMut, size: &mut u64, + rdr: &mut BytesMut, + size: &mut u64, ) -> Poll { match byte!(rdr) { b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)), @@ -567,7 +573,9 @@ impl ChunkedState { } fn read_body( - rdr: &mut BytesMut, rem: &mut u64, buf: &mut Option, + rdr: &mut BytesMut, + rem: &mut u64, + buf: &mut Option, ) -> Poll { trace!("Chunked read, remaining={:?}", rem); diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index a7f97e8c9..513c19617 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -90,7 +90,10 @@ where /// Create http/1 dispatcher with slow request timeout. pub fn with_timeout( - stream: T, config: ServiceConfig, timeout: Option, service: S, + stream: T, + config: ServiceConfig, + timeout: Option, + service: S, ) -> Self { let keepalive = config.keep_alive_enabled(); let flags = if keepalive { @@ -173,19 +176,15 @@ where // process loop { let state = match self.state { - State::None => loop { - break if let Some(msg) = self.messages.pop_front() { - match msg { - DispatcherMessage::Item(req) => { - Some(self.handle_request(req)?) - } - DispatcherMessage::Error(res) => Some(State::SendResponse( - Some((Message::Item(res), Body::Empty)), - )), - } - } else { - None - }; + State::None => if let Some(msg) = self.messages.pop_front() { + match msg { + DispatcherMessage::Item(req) => Some(self.handle_request(req)?), + DispatcherMessage::Error(res) => Some(State::SendResponse( + Some((Message::Item(res), Body::Empty)), + )), + } + } else { + None }, // call inner service State::ServiceCall(ref mut fut) => { @@ -255,7 +254,7 @@ where .framed .as_mut() .unwrap() - .start_send(Message::Chunk(Some(item.into()))) + .start_send(Message::Chunk(Some(item))) { Ok(AsyncSink::Ready) => { self.flags.remove(Flags::FLUSHED); @@ -299,7 +298,8 @@ where } fn handle_request( - &mut self, req: Request, + &mut self, + req: Request, ) -> Result, DispatchError> { let mut task = self.service.call(req); match task.poll().map_err(DispatchError::Service)? { @@ -324,7 +324,7 @@ where } let mut updated = false; - 'outer: loop { + loop { match self.framed.as_mut().unwrap().poll() { Ok(Async::Ready(Some(msg))) => { updated = true; diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 63ba05d04..fc0a3a1b2 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -107,7 +107,9 @@ impl ResponseEncoder { } fn streaming_encoding( - &mut self, version: Version, resp: &mut Response, + &mut self, + version: Version, + resp: &mut Response, ) -> TransferEncoding { match resp.chunked() { Some(true) => { diff --git a/src/h1/service.rs b/src/h1/service.rs index d691ae75d..7e5e8c5fc 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -253,7 +253,7 @@ where type Future = Dispatcher; fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.srv.poll_ready().map_err(|e| DispatchError::Service(e)) + self.srv.poll_ready().map_err(DispatchError::Service) } fn call(&mut self, req: Self::Request) -> Self::Future { diff --git a/src/response.rs b/src/response.rs index e834748ef..1901443fe 100644 --- a/src/response.rs +++ b/src/response.rs @@ -690,9 +690,10 @@ impl ResponseBuilder { } #[inline] -#[cfg_attr(feature = "cargo-clippy", allow(clippy::borrowed_box))] +#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))] fn parts<'a>( - parts: &'a mut Option>, err: &Option, + parts: &'a mut Option>, + err: &Option, ) -> Option<&'a mut Box> { if err.is_some() { return None; @@ -871,7 +872,8 @@ impl ResponsePool { #[inline] pub fn get_builder( - pool: &'static ResponsePool, status: StatusCode, + pool: &'static ResponsePool, + status: StatusCode, ) -> ResponseBuilder { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { msg.status = status; @@ -894,7 +896,9 @@ impl ResponsePool { #[inline] pub fn get_response( - pool: &'static ResponsePool, status: StatusCode, body: Body, + pool: &'static ResponsePool, + status: StatusCode, + body: Body, ) -> Response { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { msg.status = status; diff --git a/src/service.rs b/src/service.rs index a28529145..16dcf8c6b 100644 --- a/src/service.rs +++ b/src/service.rs @@ -97,12 +97,10 @@ where } match self.framed.as_mut().unwrap().poll_complete() { Ok(Async::Ready(_)) => { - return Err((self.err.take().unwrap(), self.framed.take().unwrap())) + Err((self.err.take().unwrap(), self.framed.take().unwrap())) } Ok(Async::NotReady) => Ok(Async::NotReady), - Err(_) => { - return Err((self.err.take().unwrap(), self.framed.take().unwrap())) - } + Err(_) => Err((self.err.take().unwrap(), self.framed.take().unwrap())), } } } diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index b494564bc..485ce5620 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -137,13 +137,13 @@ where let fut = Box::new( self.connector .call(connect) - .map_err(|e| ClientError::from(e)) + .map_err(ClientError::from) .and_then(move |io| { // h1 protocol let framed = Framed::new(io, h1::ClientCodec::default()); framed .send(request.into()) - .map_err(|e| ClientError::from(e)) + .map_err(ClientError::from) .and_then(|framed| { framed .into_future() diff --git a/src/ws/frame.rs b/src/ws/frame.rs index de1b92394..ca5f0e892 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -13,7 +13,9 @@ pub struct Parser; impl Parser { fn parse_metadata( - src: &[u8], server: bool, max_size: usize, + src: &[u8], + server: bool, + max_size: usize, ) -> Result)>, ProtocolError> { let chunk_len = src.len(); @@ -86,7 +88,9 @@ impl Parser { /// Parse the input stream into a frame. pub fn parse( - src: &mut BytesMut, server: bool, max_size: usize, + src: &mut BytesMut, + server: bool, + max_size: usize, ) -> Result)>, ProtocolError> { // try to parse ws frame metadata let (idx, finished, opcode, length, mask) = @@ -148,7 +152,11 @@ impl Parser { /// Generate binary representation pub fn write_message>( - dst: &mut BytesMut, pl: B, op: OpCode, fin: bool, mask: bool, + dst: &mut BytesMut, + pl: B, + op: OpCode, + fin: bool, + mask: bool, ) { let payload = pl.into(); let one: u8 = if fin { diff --git a/src/ws/mask.rs b/src/ws/mask.rs index a88c21afb..e9bfb3d56 100644 --- a/src/ws/mask.rs +++ b/src/ws/mask.rs @@ -1,5 +1,5 @@ //! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs) -#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))] +#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))] use std::ptr::copy_nonoverlapping; use std::slice; @@ -19,7 +19,7 @@ impl<'a> ShortSlice<'a> { /// Faster version of `apply_mask()` which operates on 8-byte blocks. #[inline] -#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))] +#[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))] pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { // Extend the mask to 64 bits let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64); @@ -50,10 +50,7 @@ pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { // TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so // inefficient, it could be done better. The compiler does not understand that // a `ShortSlice` must be smaller than a u64. -#[cfg_attr( - feature = "cargo-clippy", - allow(clippy::needless_pass_by_value) -)] +#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] fn xor_short(buf: ShortSlice, mask: u64) { // Unsafe: we know that a `ShortSlice` fits in a u64 unsafe { From 148cf73003c10728b773d3d0fd6256c4ff6016f1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 30 Oct 2018 11:46:44 -0700 Subject: [PATCH 053/208] allow to create response with error message --- src/error.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/error.rs b/src/error.rs index 3c0902030..3064cda49 100644 --- a/src/error.rs +++ b/src/error.rs @@ -98,6 +98,14 @@ impl Error { Fail::downcast_ref(self.cause.as_fail()); compat.and_then(|e| e.get_ref().downcast_ref()) } + + /// Converts error to a response instance and set error message as response body + pub fn response_with_message(self) -> Response { + let message = format!("{}", self); + let mut resp: Response = self.into(); + resp.set_body(message); + resp + } } /// Helper trait to downcast a response error into a fail. From 79bcbb8a101cf448ebb8bc7e7c11f3249f0b0e19 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 30 Oct 2018 11:50:30 -0700 Subject: [PATCH 054/208] use error message --- src/service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/service.rs b/src/service.rs index 16dcf8c6b..5879b3881 100644 --- a/src/service.rs +++ b/src/service.rs @@ -59,7 +59,7 @@ where Ok(r) => Either::A(ok(r)), Err((e, framed)) => Either::B(SendErrorFut { framed: Some(framed), - res: Some(Message::Item(e.error_response())), + res: Some(Message::Item(e.response_with_message())), err: Some(e), _t: PhantomData, }), From da82e249547530a809fe5b3c3c4252d4abbbfef5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 30 Oct 2018 11:53:48 -0700 Subject: [PATCH 055/208] render error message as body --- src/service.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/service.rs b/src/service.rs index 5879b3881..c76530320 100644 --- a/src/service.rs +++ b/src/service.rs @@ -57,12 +57,16 @@ where fn call(&mut self, req: Self::Request) -> Self::Future { match req { Ok(r) => Either::A(ok(r)), - Err((e, framed)) => Either::B(SendErrorFut { - framed: Some(framed), - res: Some(Message::Item(e.response_with_message())), - err: Some(e), - _t: PhantomData, - }), + Err((e, framed)) => { + let mut resp = e.error_response(); + resp.set_body(format!("{}", e)); + Either::B(SendErrorFut { + framed: Some(framed), + res: Some(resp.into()), + err: Some(e), + _t: PhantomData, + }) + } } } } From f1587243c264af76aa731162687c3ba752b8a42e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 5 Nov 2018 19:32:03 -0800 Subject: [PATCH 056/208] fix body decoding --- Cargo.toml | 1 + src/h1/codec.rs | 63 +++++++++++++++++++++++++++++++++++++++++++- src/h1/decoder.rs | 11 -------- src/h1/dispatcher.rs | 2 ++ src/h1/mod.rs | 29 ++++++++++++++++++++ 5 files changed, 94 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 87e705f76..23499bbf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ cell = ["actix-net/cell"] [dependencies] actix = "0.7.5" #actix-net = "0.1.1" +#actix-net = { path="../actix-net/" } actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 7cc516d6b..dd1e27e08 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -42,6 +42,12 @@ pub struct Codec { te: ResponseEncoder, } +impl Default for Codec { + fn default() -> Self { + Codec::new(ServiceConfig::default()) + } +} + impl fmt::Debug for Codec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "h1::Codec({:?})", self.flags) @@ -247,7 +253,10 @@ impl Decoder for Codec { if self.payload.is_some() { Ok(match self.payload.as_mut().unwrap().decode(src)? { Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), - Some(PayloadItem::Eof) => Some(Message::Chunk(None)), + Some(PayloadItem::Eof) => { + self.payload.take(); + Some(Message::Chunk(None)) + } None => None, }) } else if self.flags.contains(Flags::UNHANDLED) { @@ -297,3 +306,55 @@ impl Encoder for Codec { Ok(()) } } + +#[cfg(test)] +mod tests { + use std::{cmp, io}; + + use bytes::{Buf, Bytes, BytesMut}; + use http::{Method, Version}; + use tokio_io::{AsyncRead, AsyncWrite}; + + use super::*; + use error::ParseError; + use h1::Message; + use httpmessage::HttpMessage; + use request::Request; + + #[test] + fn test_http_request_chunked_payload_and_next_message() { + let mut codec = Codec::default(); + + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n", + ); + let item = codec.decode(&mut buf).unwrap().unwrap(); + let req = item.message(); + + assert_eq!(req.method(), Method::GET); + assert!(req.chunked().unwrap()); + + buf.extend( + b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ + POST /test2 HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n" + .iter(), + ); + + let msg = codec.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"data"); + + let msg = codec.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"line"); + + let msg = codec.decode(&mut buf).unwrap().unwrap(); + assert!(msg.eof()); + + // decode next message + let item = codec.decode(&mut buf).unwrap().unwrap(); + let req = item.message(); + assert_eq!(*req.method(), Method::POST); + assert!(req.chunked().unwrap()); + } +} diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index e8d07af12..cfa0879d7 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -648,9 +648,7 @@ mod tests { use super::*; use error::ParseError; - use h1::Message; use httpmessage::HttpMessage; - use request::Request; impl PayloadType { fn unwrap(self) -> PayloadDecoder { @@ -668,15 +666,6 @@ mod tests { } } - impl Message { - fn message(self) -> Request { - match self { - Message::Item(req) => req, - _ => panic!("error"), - } - } - } - impl PayloadItem { fn chunk(self) -> Bytes { match self { diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 513c19617..fb30d881f 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -370,6 +370,7 @@ where Response::InternalServerError().finish(), )); self.error = Some(DispatchError::InternalError); + break; } } Message::Chunk(None) => { @@ -382,6 +383,7 @@ where Response::InternalServerError().finish(), )); self.error = Some(DispatchError::InternalError); + break; } } } diff --git a/src/h1/mod.rs b/src/h1/mod.rs index c33b193f5..e73771778 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -46,3 +46,32 @@ pub enum MessageType { Payload, Unhandled, } + +#[cfg(test)] +mod tests { + use super::*; + + impl Message { + pub fn message(self) -> Request { + match self { + Message::Item(req) => req, + _ => panic!("error"), + } + } + + pub fn chunk(self) -> Bytes { + match self { + Message::Chunk(Some(data)) => data, + _ => panic!("error"), + } + } + + pub fn eof(self) -> bool { + match self { + Message::Chunk(None) => true, + Message::Chunk(Some(_)) => false, + _ => panic!("error"), + } + } + } +} From 6a1d560f227294b0edfb7a6e11e4acbc75cd8015 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 8 Nov 2018 09:30:53 -0800 Subject: [PATCH 057/208] fix keep-alive timer reset --- Cargo.toml | 5 ++--- src/h1/dispatcher.rs | 9 ++++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 23499bbf6..fcc169b76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,9 +36,8 @@ cell = ["actix-net/cell"] [dependencies] actix = "0.7.5" -#actix-net = "0.1.1" -#actix-net = { path="../actix-net/" } -actix-net = { git="https://github.com/actix/actix-net.git" } +actix-net = "0.2.0" +#actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index fb30d881f..cc9ec7217 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -441,7 +441,8 @@ where if let Some(deadline) = self.config.client_disconnect_timer() { - timer.reset(deadline) + timer.reset(deadline); + let _ = timer.poll(); } else { return Ok(()); } @@ -455,10 +456,12 @@ where ))); } } else if let Some(deadline) = self.config.keep_alive_expire() { - timer.reset(deadline) + timer.reset(deadline); + let _ = timer.poll(); } } else { - timer.reset(self.ka_expire) + timer.reset(self.ka_expire); + let _ = timer.poll(); } } Ok(Async::NotReady) => (), From dea39030bcf882c879c724d6b4d803735424e1dd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 8 Nov 2018 20:38:40 -0800 Subject: [PATCH 058/208] properly handle upgrade header if content-length header is set --- src/h1/decoder.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index cfa0879d7..472e29936 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -141,6 +141,13 @@ impl Decoder for RequestDecoder { } header::UPGRADE => { has_upgrade = true; + // check content-length, some clients (dart) + // sends "content-length: 0" with websocket upgrade + if let Ok(val) = value.to_str() { + if val == "websocket" { + content_length = None; + } + } } _ => (), } From b25b083866be7369af890e74a401b33767c856f2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 8 Nov 2018 20:45:48 -0800 Subject: [PATCH 059/208] do not stop on keep-alive timer if sink is not completly flushed --- src/h1/dispatcher.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index cc9ec7217..f4dfdb219 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -432,7 +432,7 @@ where return Err(DispatchError::DisconnectTimeout); } else if timer.deadline() >= self.ka_expire { // check for any outstanding response processing - if self.state.is_empty() { + if self.state.is_empty() && self.flags.contains(Flags::FLUSHED) { if self.flags.contains(Flags::STARTED) { trace!("Keep-alive timeout, close connection"); self.flags.insert(Flags::SHUTDOWN); From 537144f0b9ef865935c3e542c40dab52da1bba96 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 11 Nov 2018 23:12:54 -0800 Subject: [PATCH 060/208] add http client connector service --- Cargo.toml | 26 +- src/client/connect.rs | 80 ++++++ src/client/connection.rs | 79 ++++++ src/client/connector.rs | 500 +++++++++++++++++++++++++++++++++ src/client/error.rs | 77 ++++++ src/client/mod.rs | 8 + src/client/pool.rs | 579 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 7 + 8 files changed, 1354 insertions(+), 2 deletions(-) create mode 100644 src/client/connect.rs create mode 100644 src/client/connection.rs create mode 100644 src/client/connector.rs create mode 100644 src/client/error.rs create mode 100644 src/client/pool.rs diff --git a/Cargo.toml b/Cargo.toml index fcc169b76..e586f34ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,16 +34,26 @@ session = ["cookie/secure"] cell = ["actix-net/cell"] +# tls +tls = ["native-tls", "actix-net/tls"] + +# openssl +ssl = ["openssl", "actix-net/ssl"] + +# rustls +rust-tls = ["rustls", "actix-net/rust-tls"] + [dependencies] actix = "0.7.5" -actix-net = "0.2.0" -#actix-net = { git="https://github.com/actix/actix-net.git" } +#actix-net = "0.2.0" +actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" http = "0.1.8" httparse = "1.3" failure = "0.1.2" +indexmap = "1.0" log = "0.4" mime = "0.3" rand = "0.5" @@ -61,6 +71,7 @@ url = { version="1.7", features=["query_encoding"] } # io net2 = "0.2" +slab = "0.4" bytes = "0.4" byteorder = "1.2" futures = "0.1" @@ -70,6 +81,17 @@ tokio-io = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" tokio-current-thread = "0.1" +trust-dns-proto = "0.5.0" +trust-dns-resolver = "0.10.0" + +# native-tls +native-tls = { version="0.2", optional = true } + +# openssl +openssl = { version="0.10", optional = true } + +#rustls +rustls = { version = "^0.14", optional = true } [dev-dependencies] actix-web = "0.7" diff --git a/src/client/connect.rs b/src/client/connect.rs new file mode 100644 index 000000000..40c3e8ec9 --- /dev/null +++ b/src/client/connect.rs @@ -0,0 +1,80 @@ +use actix_net::connector::RequestPort; +use actix_net::resolver::RequestHost; +use http::uri::Uri; +use http::{Error as HttpError, HttpTryFrom}; + +use super::error::{ConnectorError, InvalidUrlKind}; +use super::pool::Key; + +#[derive(Debug)] +/// `Connect` type represents a message that can be sent to +/// `Connector` with a connection request. +pub struct Connect { + pub(crate) uri: Uri, +} + +impl Connect { + /// Construct `Uri` instance and create `Connect` message. + pub fn new(uri: U) -> Result + where + Uri: HttpTryFrom, + { + Ok(Connect { + uri: Uri::try_from(uri).map_err(|e| e.into())?, + }) + } + + /// Create `Connect` message for specified `Uri` + pub fn with(uri: Uri) -> Connect { + Connect { uri } + } + + pub(crate) fn is_secure(&self) -> bool { + if let Some(scheme) = self.uri.scheme_part() { + scheme.as_str() == "https" + } else { + false + } + } + + pub(crate) fn key(&self) -> Key { + self.uri.authority_part().unwrap().clone().into() + } + + pub(crate) fn validate(&self) -> Result<(), ConnectorError> { + if self.uri.host().is_none() { + Err(ConnectorError::InvalidUrl(InvalidUrlKind::MissingHost)) + } else if self.uri.scheme_part().is_none() { + Err(ConnectorError::InvalidUrl(InvalidUrlKind::MissingScheme)) + } else if let Some(scheme) = self.uri.scheme_part() { + match scheme.as_str() { + "http" | "ws" | "https" | "wss" => Ok(()), + _ => Err(ConnectorError::InvalidUrl(InvalidUrlKind::UnknownScheme)), + } + } else { + Ok(()) + } + } +} + +impl RequestHost for Connect { + fn host(&self) -> &str { + &self.uri.host().unwrap() + } +} + +impl RequestPort for Connect { + fn port(&self) -> u16 { + if let Some(port) = self.uri.port() { + port + } else if let Some(scheme) = self.uri.scheme_part() { + match scheme.as_str() { + "http" | "ws" => 80, + "https" | "wss" => 443, + _ => 80, + } + } else { + 80 + } + } +} diff --git a/src/client/connection.rs b/src/client/connection.rs new file mode 100644 index 000000000..294e100c8 --- /dev/null +++ b/src/client/connection.rs @@ -0,0 +1,79 @@ +use std::{fmt, io, time}; + +use futures::Poll; +use tokio_io::{AsyncRead, AsyncWrite}; + +use super::pool::Acquired; + +/// HTTP client connection +pub struct Connection { + io: T, + created: time::Instant, + pool: Option>, +} + +impl fmt::Debug for Connection +where + T: AsyncRead + AsyncWrite + fmt::Debug + 'static, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Connection {:?}", self.io) + } +} + +impl Connection { + pub(crate) fn new(io: T, created: time::Instant, pool: Acquired) -> Self { + Connection { + io, + created, + pool: Some(pool), + } + } + + /// Raw IO stream + pub fn get_mut(&mut self) -> &mut T { + &mut self.io + } + + /// Close connection + pub fn close(mut self) { + if let Some(mut pool) = self.pool.take() { + pool.close(self) + } + } + + /// Release this connection to the connection pool + pub fn release(mut self) { + if let Some(mut pool) = self.pool.take() { + pool.release(self) + } + } + + pub(crate) fn into_inner(self) -> (T, time::Instant) { + (self.io, self.created) + } +} + +impl io::Read for Connection { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.io.read(buf) + } +} + +impl AsyncRead for Connection {} + +impl io::Write for Connection { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.io.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.io.flush() + } +} + +impl AsyncWrite for Connection { + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.io.shutdown() + } +} diff --git a/src/client/connector.rs b/src/client/connector.rs new file mode 100644 index 000000000..97da074d1 --- /dev/null +++ b/src/client/connector.rs @@ -0,0 +1,500 @@ +use std::time::Duration; +use std::{fmt, io}; + +use actix_net::connector::TcpConnector; +use actix_net::resolver::Resolver; +use actix_net::service::{Service, ServiceExt}; +use actix_net::timeout::{TimeoutError, TimeoutService}; +use futures::future::Either; +use futures::Poll; +use tokio_io::{AsyncRead, AsyncWrite}; +use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; + +use super::connect::Connect; +use super::connection::Connection; +use super::error::ConnectorError; +use super::pool::ConnectionPool; + +#[cfg(feature = "ssl")] +use actix_net::ssl::OpensslConnector; +#[cfg(feature = "ssl")] +use openssl::ssl::{SslConnector, SslMethod}; + +#[cfg(not(feature = "ssl"))] +type SslConnector = (); + +/// Http client connector builde instance. +/// `Connector` type uses builder-like pattern for connector service construction. +pub struct Connector { + resolver: Resolver, + timeout: Duration, + conn_lifetime: Duration, + conn_keep_alive: Duration, + disconnect_timeout: Duration, + limit: usize, + #[allow(dead_code)] + connector: SslConnector, +} + +impl Default for Connector { + fn default() -> Connector { + let connector = { + #[cfg(feature = "ssl")] + { + SslConnector::builder(SslMethod::tls()).unwrap().build() + } + #[cfg(not(feature = "ssl"))] + { + () + } + }; + + Connector { + connector, + resolver: Resolver::default(), + timeout: Duration::from_secs(1), + conn_lifetime: Duration::from_secs(75), + conn_keep_alive: Duration::from_secs(15), + disconnect_timeout: Duration::from_millis(3000), + limit: 100, + } + } +} + +impl Connector { + /// Use custom resolver configuration. + pub fn resolver_config(mut self, cfg: ResolverConfig, opts: ResolverOpts) -> Self { + self.resolver = Resolver::new(cfg, opts); + self + } + + /// Connection timeout, i.e. max time to connect to remote host including dns name resolution. + /// Set to 1 second by default. + pub fn timeout(mut self, timeout: Duration) -> Self { + self.timeout = timeout; + self + } + + #[cfg(feature = "ssl")] + /// Use custom `SslConnector` instance. + pub fn ssl(mut self, connector: SslConnector) -> Self { + self.connector = connector; + self + } + + /// Set total number of simultaneous connections per type of scheme. + /// + /// If limit is 0, the connector has no limit. + /// The default limit size is 100. + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } + + /// Set keep-alive period for opened connection. + /// + /// Keep-alive period is the period between connection usage. If + /// the delay between repeated usages of the same connection + /// exceeds this period, the connection is closed. + /// Default keep-alive period is 15 seconds. + pub fn conn_keep_alive(mut self, dur: Duration) -> Self { + self.conn_keep_alive = dur; + self + } + + /// Set max lifetime period for connection. + /// + /// Connection lifetime is max lifetime of any opened connection + /// until it is closed regardless of keep-alive period. + /// Default lifetime period is 75 seconds. + pub fn conn_lifetime(mut self, dur: Duration) -> Self { + self.conn_lifetime = dur; + self + } + + /// Set server connection disconnect timeout in milliseconds. + /// + /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete + /// within this time, the socket get dropped. This timeout affects only secure connections. + /// + /// To disable timeout set value to 0. + /// + /// By default disconnect timeout is set to 3000 milliseconds. + pub fn disconnect_timeout(mut self, dur: Duration) -> Self { + self.disconnect_timeout = dur; + self + } + + /// Finish configuration process and create connector service. + pub fn service( + self, + ) -> impl Service< + Request = Connect, + Response = impl AsyncRead + AsyncWrite + fmt::Debug, + Error = ConnectorError, + > + Clone { + #[cfg(not(feature = "ssl"))] + { + let connector = TimeoutService::new( + self.timeout, + self.resolver + .map_err(ConnectorError::from) + .and_then(TcpConnector::default().from_err()), + ).map_err(|e| match e { + TimeoutError::Service(e) => e, + TimeoutError::Timeout => ConnectorError::Timeout, + }); + + connect_impl::InnerConnector { + tcp_pool: ConnectionPool::new( + connector, + self.conn_lifetime, + self.conn_keep_alive, + None, + self.limit, + ), + } + } + #[cfg(feature = "ssl")] + { + let ssl_service = TimeoutService::new( + self.timeout, + self.resolver + .clone() + .map_err(ConnectorError::from) + .and_then(TcpConnector::default().from_err()) + .and_then( + OpensslConnector::service(self.connector) + .map_err(ConnectorError::SslError), + ), + ).map_err(|e| match e { + TimeoutError::Service(e) => e, + TimeoutError::Timeout => ConnectorError::Timeout, + }); + + let tcp_service = TimeoutService::new( + self.timeout, + self.resolver + .map_err(ConnectorError::from) + .and_then(TcpConnector::default().from_err()), + ).map_err(|e| match e { + TimeoutError::Service(e) => e, + TimeoutError::Timeout => ConnectorError::Timeout, + }); + + connect_impl::InnerConnector { + tcp_pool: ConnectionPool::new( + tcp_service, + self.conn_lifetime, + self.conn_keep_alive, + None, + self.limit, + ), + ssl_pool: ConnectionPool::new( + ssl_service, + self.conn_lifetime, + self.conn_keep_alive, + Some(self.disconnect_timeout), + self.limit, + ), + } + } + } +} + +#[cfg(not(feature = "ssl"))] +mod connect_impl { + use super::*; + use futures::future::{err, FutureResult}; + + pub(crate) struct InnerConnector + where + Io: AsyncRead + AsyncWrite + 'static, + T: Service, + { + pub(crate) tcp_pool: ConnectionPool, + } + + impl Clone for InnerConnector + where + Io: AsyncRead + AsyncWrite + 'static, + T: Service + + Clone, + { + fn clone(&self) -> Self { + InnerConnector { + tcp_pool: self.tcp_pool.clone(), + } + } + } + + impl Service for InnerConnector + where + Io: AsyncRead + AsyncWrite + 'static, + T: Service, + { + type Request = Connect; + type Response = Connection; + type Error = ConnectorError; + type Future = Either< + as Service>::Future, + FutureResult, ConnectorError>, + >; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.tcp_pool.poll_ready() + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + if req.is_secure() { + Either::B(err(ConnectorError::SslIsNotSupported)) + } else if let Err(e) = req.validate() { + Either::B(err(e)) + } else { + Either::A(self.tcp_pool.call(req)) + } + } + } +} + +#[cfg(feature = "ssl")] +mod connect_impl { + use std::marker::PhantomData; + + use futures::future::{err, FutureResult}; + use futures::{Async, Future, Poll}; + + use super::*; + + pub(crate) struct InnerConnector + where + Io1: AsyncRead + AsyncWrite + 'static, + Io2: AsyncRead + AsyncWrite + 'static, + T1: Service< + Request = Connect, + Response = (Connect, Io1), + Error = ConnectorError, + >, + T2: Service< + Request = Connect, + Response = (Connect, Io2), + Error = ConnectorError, + >, + { + pub(crate) tcp_pool: ConnectionPool, + pub(crate) ssl_pool: ConnectionPool, + } + + impl Clone for InnerConnector + where + Io1: AsyncRead + AsyncWrite + 'static, + Io2: AsyncRead + AsyncWrite + 'static, + T1: Service< + Request = Connect, + Response = (Connect, Io1), + Error = ConnectorError, + > + Clone, + T2: Service< + Request = Connect, + Response = (Connect, Io2), + Error = ConnectorError, + > + Clone, + { + fn clone(&self) -> Self { + InnerConnector { + tcp_pool: self.tcp_pool.clone(), + ssl_pool: self.ssl_pool.clone(), + } + } + } + + impl Service for InnerConnector + where + Io1: AsyncRead + AsyncWrite + 'static, + Io2: AsyncRead + AsyncWrite + 'static, + T1: Service< + Request = Connect, + Response = (Connect, Io1), + Error = ConnectorError, + >, + T2: Service< + Request = Connect, + Response = (Connect, Io2), + Error = ConnectorError, + >, + { + type Request = Connect; + type Response = IoEither, Connection>; + type Error = ConnectorError; + type Future = Either< + FutureResult, + Either< + InnerConnectorResponseA, + InnerConnectorResponseB, + >, + >; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.tcp_pool.poll_ready() + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + if let Err(e) = req.validate() { + Either::A(err(e)) + } else if req.is_secure() { + Either::B(Either::A(InnerConnectorResponseA { + fut: self.tcp_pool.call(req), + _t: PhantomData, + })) + } else { + Either::B(Either::B(InnerConnectorResponseB { + fut: self.ssl_pool.call(req), + _t: PhantomData, + })) + } + } + } + + pub(crate) struct InnerConnectorResponseA + where + Io1: AsyncRead + AsyncWrite + 'static, + T: Service, + { + fut: as Service>::Future, + _t: PhantomData, + } + + impl Future for InnerConnectorResponseA + where + T: Service, + Io1: AsyncRead + AsyncWrite + 'static, + Io2: AsyncRead + AsyncWrite + 'static, + { + type Item = IoEither, Connection>; + type Error = ConnectorError; + + fn poll(&mut self) -> Poll { + match self.fut.poll()? { + Async::NotReady => Ok(Async::NotReady), + Async::Ready(res) => Ok(Async::Ready(IoEither::A(res))), + } + } + } + + pub(crate) struct InnerConnectorResponseB + where + Io2: AsyncRead + AsyncWrite + 'static, + T: Service, + { + fut: as Service>::Future, + _t: PhantomData, + } + + impl Future for InnerConnectorResponseB + where + T: Service, + Io1: AsyncRead + AsyncWrite + 'static, + Io2: AsyncRead + AsyncWrite + 'static, + { + type Item = IoEither, Connection>; + type Error = ConnectorError; + + fn poll(&mut self) -> Poll { + match self.fut.poll()? { + Async::NotReady => Ok(Async::NotReady), + Async::Ready(res) => Ok(Async::Ready(IoEither::B(res))), + } + } + } +} + +pub(crate) enum IoEither { + A(Io1), + B(Io2), +} + +impl io::Read for IoEither +where + Io1: io::Read, + Io2: io::Read, +{ + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self { + IoEither::A(ref mut io) => io.read(buf), + IoEither::B(ref mut io) => io.read(buf), + } + } +} + +impl AsyncRead for IoEither +where + Io1: AsyncRead, + Io2: AsyncRead, +{ + unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { + match self { + IoEither::A(ref io) => io.prepare_uninitialized_buffer(buf), + IoEither::B(ref io) => io.prepare_uninitialized_buffer(buf), + } + } +} + +impl AsyncWrite for IoEither +where + Io1: AsyncWrite, + Io2: AsyncWrite, +{ + fn shutdown(&mut self) -> Poll<(), io::Error> { + match self { + IoEither::A(ref mut io) => io.shutdown(), + IoEither::B(ref mut io) => io.shutdown(), + } + } + + fn poll_write(&mut self, buf: &[u8]) -> Poll { + match self { + IoEither::A(ref mut io) => io.poll_write(buf), + IoEither::B(ref mut io) => io.poll_write(buf), + } + } + + fn poll_flush(&mut self) -> Poll<(), io::Error> { + match self { + IoEither::A(ref mut io) => io.poll_flush(), + IoEither::B(ref mut io) => io.poll_flush(), + } + } +} + +impl io::Write for IoEither +where + Io1: io::Write, + Io2: io::Write, +{ + fn flush(&mut self) -> io::Result<()> { + match self { + IoEither::A(ref mut io) => io.flush(), + IoEither::B(ref mut io) => io.flush(), + } + } + + fn write(&mut self, buf: &[u8]) -> io::Result { + match self { + IoEither::A(ref mut io) => io.write(buf), + IoEither::B(ref mut io) => io.write(buf), + } + } +} + +impl fmt::Debug for IoEither +where + Io1: fmt::Debug, + Io2: fmt::Debug, +{ + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + IoEither::A(ref io) => io.fmt(fmt), + IoEither::B(ref io) => io.fmt(fmt), + } + } +} diff --git a/src/client/error.rs b/src/client/error.rs new file mode 100644 index 000000000..ba6407230 --- /dev/null +++ b/src/client/error.rs @@ -0,0 +1,77 @@ +use std::io; + +use trust_dns_resolver::error::ResolveError; + +#[cfg(feature = "ssl")] +use openssl::ssl::Error as SslError; + +#[cfg(all( + feature = "tls", + not(any(feature = "ssl", feature = "rust-tls")) +))] +use native_tls::Error as SslError; + +#[cfg(all( + feature = "rust-tls", + not(any(feature = "tls", feature = "ssl")) +))] +use std::io::Error as SslError; + +/// A set of errors that can occur while connecting to an HTTP host +#[derive(Fail, Debug)] +pub enum ConnectorError { + /// Invalid URL + #[fail(display = "Invalid URL")] + InvalidUrl(InvalidUrlKind), + + /// SSL feature is not enabled + #[fail(display = "SSL is not supported")] + SslIsNotSupported, + + /// SSL error + #[cfg(any(feature = "tls", feature = "ssl", feature = "rust-tls"))] + #[fail(display = "{}", _0)] + SslError(#[cause] SslError), + + /// Failed to resolve the hostname + #[fail(display = "Failed resolving hostname: {}", _0)] + Resolver(ResolveError), + + /// No dns records + #[fail(display = "No dns records found for the input")] + NoRecords, + + /// Connecting took too long + #[fail(display = "Timeout out while establishing connection")] + Timeout, + + /// Connector has been disconnected + #[fail(display = "Internal error: connector has been disconnected")] + Disconnected, + + /// Connection io error + #[fail(display = "{}", _0)] + IoError(io::Error), +} + +#[derive(Fail, Debug)] +pub enum InvalidUrlKind { + #[fail(display = "Missing url scheme")] + MissingScheme, + #[fail(display = "Unknown url scheme")] + UnknownScheme, + #[fail(display = "Missing host name")] + MissingHost, +} + +impl From for ConnectorError { + fn from(err: io::Error) -> ConnectorError { + ConnectorError::IoError(err) + } +} + +impl From for ConnectorError { + fn from(err: ResolveError) -> ConnectorError { + ConnectorError::Resolver(err) + } +} diff --git a/src/client/mod.rs b/src/client/mod.rs index a5582fea8..714e6c694 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,6 +1,14 @@ //! Http client api +mod connect; +mod connection; +mod connector; +mod error; +mod pool; mod request; mod response; +pub use self::connect::Connect; +pub use self::connector::Connector; +pub use self::error::{ConnectorError, InvalidUrlKind}; pub use self::request::{ClientRequest, ClientRequestBuilder}; pub use self::response::ClientResponse; diff --git a/src/client/pool.rs b/src/client/pool.rs new file mode 100644 index 000000000..6ff8c96ce --- /dev/null +++ b/src/client/pool.rs @@ -0,0 +1,579 @@ +use std::cell::RefCell; +use std::collections::{HashMap, VecDeque}; +use std::io; +use std::rc::Rc; +use std::time::{Duration, Instant}; + +use actix_net::service::Service; +use futures::future::{ok, Either, FutureResult}; +use futures::sync::oneshot; +use futures::task::AtomicTask; +use futures::{Async, Future, Poll}; +use http::uri::Authority; +use indexmap::IndexSet; +use slab::Slab; +use tokio_current_thread::spawn; +use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_timer::{sleep, Delay}; + +use super::connect::Connect; +use super::connection::Connection; +use super::error::ConnectorError; + +#[derive(Hash, Eq, PartialEq, Clone, Debug)] +pub(crate) struct Key { + authority: Authority, +} + +impl From for Key { + fn from(authority: Authority) -> Key { + Key { authority } + } +} + +#[derive(Debug)] +struct AvailableConnection { + io: T, + used: Instant, + created: Instant, +} + +/// Connections pool +pub(crate) struct ConnectionPool( + T, + Rc>>, +); + +impl ConnectionPool +where + Io: AsyncRead + AsyncWrite + 'static, + T: Service, +{ + pub(crate) fn new( + connector: T, + conn_lifetime: Duration, + conn_keep_alive: Duration, + disconnect_timeout: Option, + limit: usize, + ) -> Self { + ConnectionPool( + connector, + Rc::new(RefCell::new(Inner { + conn_lifetime, + conn_keep_alive, + disconnect_timeout, + limit, + acquired: 0, + waiters: Slab::new(), + waiters_queue: IndexSet::new(), + available: HashMap::new(), + task: AtomicTask::new(), + })), + ) + } +} + +impl Clone for ConnectionPool +where + T: Clone, + Io: AsyncRead + AsyncWrite + 'static, +{ + fn clone(&self) -> Self { + ConnectionPool(self.0.clone(), self.1.clone()) + } +} + +impl Service for ConnectionPool +where + Io: AsyncRead + AsyncWrite + 'static, + T: Service, +{ + type Request = Connect; + type Response = Connection; + type Error = ConnectorError; + type Future = Either< + FutureResult, ConnectorError>, + Either, OpenConnection>, + >; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.0.poll_ready() + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + let key = req.key(); + + // acquire connection + match self.1.as_ref().borrow_mut().acquire(&key) { + Acquire::Acquired(io, created) => { + // use existing connection + Either::A(ok(Connection::new( + io, + created, + Acquired(key, Some(self.1.clone())), + ))) + } + Acquire::NotAvailable => { + // connection is not available, wait + let (rx, token) = self.1.as_ref().borrow_mut().wait_for(req); + Either::B(Either::A(WaitForConnection { + rx, + key, + token, + inner: Some(self.1.clone()), + })) + } + Acquire::Available => { + // open new connection + Either::B(Either::B(OpenConnection::new( + key, + self.1.clone(), + self.0.call(req), + ))) + } + } + } +} + +#[doc(hidden)] +pub struct WaitForConnection +where + Io: AsyncRead + AsyncWrite + 'static, +{ + key: Key, + token: usize, + rx: oneshot::Receiver, ConnectorError>>, + inner: Option>>>, +} + +impl Drop for WaitForConnection +where + Io: AsyncRead + AsyncWrite + 'static, +{ + fn drop(&mut self) { + if let Some(i) = self.inner.take() { + let mut inner = i.as_ref().borrow_mut(); + inner.release_waiter(&self.key, self.token); + inner.check_availibility(); + } + } +} + +impl Future for WaitForConnection +where + Io: AsyncRead + AsyncWrite, +{ + type Item = Connection; + type Error = ConnectorError; + + fn poll(&mut self) -> Poll { + match self.rx.poll() { + Ok(Async::Ready(item)) => match item { + Err(err) => Err(err), + Ok(conn) => { + let _ = self.inner.take(); + Ok(Async::Ready(conn)) + } + }, + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(_) => { + let _ = self.inner.take(); + Err(ConnectorError::Disconnected) + } + } + } +} + +#[doc(hidden)] +pub struct OpenConnection +where + Io: AsyncRead + AsyncWrite + 'static, +{ + fut: F, + key: Key, + inner: Option>>>, +} + +impl OpenConnection +where + F: Future, + Io: AsyncRead + AsyncWrite + 'static, +{ + fn new(key: Key, inner: Rc>>, fut: F) -> Self { + OpenConnection { + key, + fut, + inner: Some(inner), + } + } +} + +impl Drop for OpenConnection +where + Io: AsyncRead + AsyncWrite + 'static, +{ + fn drop(&mut self) { + if let Some(inner) = self.inner.take() { + let mut inner = inner.as_ref().borrow_mut(); + inner.release(); + inner.check_availibility(); + } + } +} + +impl Future for OpenConnection +where + F: Future, + Io: AsyncRead + AsyncWrite, +{ + type Item = Connection; + type Error = ConnectorError; + + fn poll(&mut self) -> Poll { + match self.fut.poll() { + Err(err) => Err(err.into()), + Ok(Async::Ready((_, io))) => { + let _ = self.inner.take(); + Ok(Async::Ready(Connection::new( + io, + Instant::now(), + Acquired(self.key.clone(), self.inner.clone()), + ))) + } + Ok(Async::NotReady) => Ok(Async::NotReady), + } + } +} + +struct OpenWaitingConnection +where + Io: AsyncRead + AsyncWrite + 'static, +{ + fut: F, + key: Key, + rx: Option, ConnectorError>>>, + inner: Option>>>, +} + +impl OpenWaitingConnection +where + F: Future + 'static, + Io: AsyncRead + AsyncWrite + 'static, +{ + fn spawn( + key: Key, + rx: oneshot::Sender, ConnectorError>>, + inner: Rc>>, + fut: F, + ) { + spawn(OpenWaitingConnection { + key, + fut, + rx: Some(rx), + inner: Some(inner), + }) + } +} + +impl Drop for OpenWaitingConnection +where + Io: AsyncRead + AsyncWrite + 'static, +{ + fn drop(&mut self) { + if let Some(inner) = self.inner.take() { + let mut inner = inner.as_ref().borrow_mut(); + inner.release(); + inner.check_availibility(); + } + } +} + +impl Future for OpenWaitingConnection +where + F: Future, + Io: AsyncRead + AsyncWrite, +{ + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + match self.fut.poll() { + Err(err) => { + let _ = self.inner.take(); + if let Some(rx) = self.rx.take() { + let _ = rx.send(Err(err)); + } + Err(()) + } + Ok(Async::Ready((_, io))) => { + let _ = self.inner.take(); + if let Some(rx) = self.rx.take() { + let _ = rx.send(Ok(Connection::new( + io, + Instant::now(), + Acquired(self.key.clone(), self.inner.clone()), + ))); + } + Ok(Async::Ready(())) + } + Ok(Async::NotReady) => Ok(Async::NotReady), + } + } +} + +enum Acquire { + Acquired(T, Instant), + Available, + NotAvailable, +} + +pub(crate) struct Inner +where + Io: AsyncRead + AsyncWrite + 'static, +{ + conn_lifetime: Duration, + conn_keep_alive: Duration, + disconnect_timeout: Option, + limit: usize, + acquired: usize, + available: HashMap>>, + waiters: Slab<( + Connect, + oneshot::Sender, ConnectorError>>, + )>, + waiters_queue: IndexSet<(Key, usize)>, + task: AtomicTask, +} + +impl Inner +where + Io: AsyncRead + AsyncWrite + 'static, +{ + /// connection is not available, wait + fn wait_for( + &mut self, + connect: Connect, + ) -> ( + oneshot::Receiver, ConnectorError>>, + usize, + ) { + let (tx, rx) = oneshot::channel(); + + let key = connect.key(); + let entry = self.waiters.vacant_entry(); + let token = entry.key(); + entry.insert((connect, tx)); + assert!(!self.waiters_queue.insert((key, token))); + (rx, token) + } + + fn release_waiter(&mut self, key: &Key, token: usize) { + self.waiters.remove(token); + self.waiters_queue.remove(&(key.clone(), token)); + } + + fn acquire(&mut self, key: &Key) -> Acquire { + // check limits + if self.limit > 0 && self.acquired >= self.limit { + return Acquire::NotAvailable; + } + + self.reserve(); + + // check if open connection is available + // cleanup stale connections at the same time + if let Some(ref mut connections) = self.available.get_mut(key) { + let now = Instant::now(); + while let Some(conn) = connections.pop_back() { + // check if it still usable + if (now - conn.used) > self.conn_keep_alive + || (now - conn.created) > self.conn_lifetime + { + if let Some(timeout) = self.disconnect_timeout { + spawn(CloseConnection::new(conn.io, timeout)) + } + } else { + let mut io = conn.io; + let mut buf = [0; 2]; + match io.read(&mut buf) { + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), + Ok(n) if n > 0 => { + if let Some(timeout) = self.disconnect_timeout { + spawn(CloseConnection::new(io, timeout)) + } + continue; + } + Ok(_) | Err(_) => continue, + } + return Acquire::Acquired(io, conn.created); + } + } + } + Acquire::Available + } + + fn reserve(&mut self) { + self.acquired += 1; + } + + fn release(&mut self) { + self.acquired -= 1; + } + + fn release_conn(&mut self, key: &Key, io: Io, created: Instant) { + self.acquired -= 1; + self.available + .entry(key.clone()) + .or_insert_with(VecDeque::new) + .push_back(AvailableConnection { + io, + created, + used: Instant::now(), + }); + } + + fn release_close(&mut self, io: Io) { + self.acquired -= 1; + if let Some(timeout) = self.disconnect_timeout { + spawn(CloseConnection::new(io, timeout)) + } + } + + fn check_availibility(&self) { + if !self.waiters_queue.is_empty() && self.acquired < self.limit { + self.task.notify() + } + } +} + +struct ConnectorPoolSupport +where + Io: AsyncRead + AsyncWrite + 'static, +{ + connector: T, + inner: Rc>>, +} + +impl Future for ConnectorPoolSupport +where + Io: AsyncRead + AsyncWrite + 'static, + T: Service, + T::Future: 'static, +{ + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + let mut inner = self.inner.as_ref().borrow_mut(); + inner.task.register(); + + // check waiters + loop { + let (key, token) = { + if let Some((key, token)) = inner.waiters_queue.get_index(0) { + (key.clone(), *token) + } else { + break; + } + }; + match inner.acquire(&key) { + Acquire::NotAvailable => break, + Acquire::Acquired(io, created) => { + let (_, tx) = inner.waiters.remove(token); + if let Err(conn) = tx.send(Ok(Connection::new( + io, + created, + Acquired(key.clone(), Some(self.inner.clone())), + ))) { + let (io, created) = conn.unwrap().into_inner(); + inner.release_conn(&key, io, created); + } + } + Acquire::Available => { + let (connect, tx) = inner.waiters.remove(token); + OpenWaitingConnection::spawn( + key.clone(), + tx, + self.inner.clone(), + self.connector.call(connect), + ); + } + } + let _ = inner.waiters_queue.swap_remove_index(0); + } + + Ok(Async::NotReady) + } +} + +struct CloseConnection { + io: T, + timeout: Delay, +} + +impl CloseConnection +where + T: AsyncWrite, +{ + fn new(io: T, timeout: Duration) -> Self { + CloseConnection { + io, + timeout: sleep(timeout), + } + } +} + +impl Future for CloseConnection +where + T: AsyncWrite, +{ + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll<(), ()> { + match self.timeout.poll() { + Ok(Async::Ready(_)) | Err(_) => Ok(Async::Ready(())), + Ok(Async::NotReady) => match self.io.shutdown() { + Ok(Async::Ready(_)) | Err(_) => Ok(Async::Ready(())), + Ok(Async::NotReady) => Ok(Async::NotReady), + }, + } + } +} + +pub(crate) struct Acquired( + Key, + Option>>>, +); + +impl Acquired +where + T: AsyncRead + AsyncWrite + 'static, +{ + pub(crate) fn close(&mut self, conn: Connection) { + if let Some(inner) = self.1.take() { + let (io, _) = conn.into_inner(); + inner.as_ref().borrow_mut().release_close(io); + } + } + pub(crate) fn release(&mut self, conn: Connection) { + if let Some(inner) = self.1.take() { + let (io, created) = conn.into_inner(); + inner + .as_ref() + .borrow_mut() + .release_conn(&self.0, io, created); + } + } +} + +impl Drop for Acquired +where + T: AsyncRead + AsyncWrite + 'static, +{ + fn drop(&mut self) { + if let Some(inner) = self.1.take() { + inner.as_ref().borrow_mut().release(); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 572c23ae1..32369d167 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,6 +83,7 @@ extern crate cookie; extern crate encoding; extern crate http as modhttp; extern crate httparse; +extern crate indexmap; extern crate mime; extern crate net2; extern crate percent_encoding; @@ -90,18 +91,24 @@ extern crate rand; extern crate serde; extern crate serde_json; extern crate serde_urlencoded; +extern crate slab; extern crate tokio; extern crate tokio_codec; extern crate tokio_current_thread; extern crate tokio_io; extern crate tokio_tcp; extern crate tokio_timer; +extern crate trust_dns_proto; +extern crate trust_dns_resolver; extern crate url as urlcrate; #[cfg(test)] #[macro_use] extern crate serde_derive; +#[cfg(feature = "ssl")] +extern crate openssl; + mod body; pub mod client; mod config; From 550c5f55b68a1eb00793c71f0bdd41c7ac12e3dd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 13 Nov 2018 22:53:30 -0800 Subject: [PATCH 061/208] add simple http client --- src/body.rs | 136 ++++++++++++++++++++++++++++- src/client/connect.rs | 12 +-- src/client/connection.rs | 4 +- src/client/connector.rs | 2 +- src/client/error.rs | 40 +++++++++ src/client/mod.rs | 6 +- src/client/pipeline.rs | 174 ++++++++++++++++++++++++++++++++++++ src/client/pool.rs | 67 +++++++------- src/client/request.rs | 184 ++++++++++++++++++++++++--------------- src/client/response.rs | 49 +++++++---- src/h1/client.rs | 60 ++++++------- src/h1/codec.rs | 14 ++- src/h1/decoder.rs | 8 +- src/h1/dispatcher.rs | 2 +- src/h1/encoder.rs | 4 +- src/h1/mod.rs | 11 ++- src/request.rs | 5 +- src/ws/client/service.rs | 7 +- tests/test_client.rs | 147 +++++++++++++++++++++++++++++++ 19 files changed, 745 insertions(+), 187 deletions(-) create mode 100644 src/client/pipeline.rs create mode 100644 tests/test_client.rs diff --git a/src/body.rs b/src/body.rs index c78ea8172..e001273c4 100644 --- a/src/body.rs +++ b/src/body.rs @@ -1,13 +1,39 @@ -use bytes::{Bytes, BytesMut}; -use futures::Stream; use std::sync::Arc; use std::{fmt, mem}; +use bytes::{Bytes, BytesMut}; +use futures::{Async, Poll, Stream}; + use error::Error; /// Type represent streaming body pub type BodyStream = Box>; +/// Different type of bory +pub enum BodyType { + None, + Zero, + Sized(usize), + Unsized, +} + +/// Type that provides this trait can be streamed to a peer. +pub trait MessageBody { + fn tp(&self) -> BodyType; + + fn poll_next(&mut self) -> Poll, Error>; +} + +impl MessageBody for () { + fn tp(&self) -> BodyType { + BodyType::Zero + } + + fn poll_next(&mut self) -> Poll, Error> { + Ok(Async::Ready(None)) + } +} + /// Represents various types of http message body. pub enum Body { /// Empty response. `Content-Length` header is set to `0` @@ -241,6 +267,112 @@ impl AsRef<[u8]> for Binary { } } +impl MessageBody for Bytes { + fn tp(&self) -> BodyType { + BodyType::Sized(self.len()) + } + + fn poll_next(&mut self) -> Poll, Error> { + if self.is_empty() { + Ok(Async::Ready(None)) + } else { + Ok(Async::Ready(Some(mem::replace(self, Bytes::new())))) + } + } +} + +impl MessageBody for &'static str { + fn tp(&self) -> BodyType { + BodyType::Sized(self.len()) + } + + fn poll_next(&mut self) -> Poll, Error> { + if self.is_empty() { + Ok(Async::Ready(None)) + } else { + Ok(Async::Ready(Some(Bytes::from_static( + mem::replace(self, "").as_ref(), + )))) + } + } +} + +impl MessageBody for &'static [u8] { + fn tp(&self) -> BodyType { + BodyType::Sized(self.len()) + } + + fn poll_next(&mut self) -> Poll, Error> { + if self.is_empty() { + Ok(Async::Ready(None)) + } else { + Ok(Async::Ready(Some(Bytes::from_static(mem::replace( + self, b"", + ))))) + } + } +} + +impl MessageBody for Vec { + fn tp(&self) -> BodyType { + BodyType::Sized(self.len()) + } + + fn poll_next(&mut self) -> Poll, Error> { + if self.is_empty() { + Ok(Async::Ready(None)) + } else { + Ok(Async::Ready(Some(Bytes::from(mem::replace( + self, + Vec::new(), + ))))) + } + } +} + +impl MessageBody for String { + fn tp(&self) -> BodyType { + BodyType::Sized(self.len()) + } + + fn poll_next(&mut self) -> Poll, Error> { + if self.is_empty() { + Ok(Async::Ready(None)) + } else { + Ok(Async::Ready(Some(Bytes::from( + mem::replace(self, String::new()).into_bytes(), + )))) + } + } +} + +#[doc(hidden)] +pub struct MessageBodyStream { + stream: S, +} + +impl MessageBodyStream +where + S: Stream, +{ + pub fn new(stream: S) -> Self { + MessageBodyStream { stream } + } +} + +impl MessageBody for MessageBodyStream +where + S: Stream, +{ + fn tp(&self) -> BodyType { + BodyType::Unsized + } + + fn poll_next(&mut self) -> Poll, Error> { + self.stream.poll() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/client/connect.rs b/src/client/connect.rs index 40c3e8ec9..a445228e3 100644 --- a/src/client/connect.rs +++ b/src/client/connect.rs @@ -14,8 +14,13 @@ pub struct Connect { } impl Connect { + /// Create `Connect` message for specified `Uri` + pub fn new(uri: Uri) -> Connect { + Connect { uri } + } + /// Construct `Uri` instance and create `Connect` message. - pub fn new(uri: U) -> Result + pub fn try_from(uri: U) -> Result where Uri: HttpTryFrom, { @@ -24,11 +29,6 @@ impl Connect { }) } - /// Create `Connect` message for specified `Uri` - pub fn with(uri: Uri) -> Connect { - Connect { uri } - } - pub(crate) fn is_secure(&self) -> bool { if let Some(scheme) = self.uri.scheme_part() { scheme.as_str() == "https" diff --git a/src/client/connection.rs b/src/client/connection.rs index 294e100c8..eec64267a 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -6,7 +6,7 @@ use tokio_io::{AsyncRead, AsyncWrite}; use super::pool::Acquired; /// HTTP client connection -pub struct Connection { +pub struct Connection { io: T, created: time::Instant, pool: Option>, @@ -14,7 +14,7 @@ pub struct Connection { impl fmt::Debug for Connection where - T: AsyncRead + AsyncWrite + fmt::Debug + 'static, + T: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Connection {:?}", self.io) diff --git a/src/client/connector.rs b/src/client/connector.rs index 97da074d1..1eae135f2 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -130,7 +130,7 @@ impl Connector { self, ) -> impl Service< Request = Connect, - Response = impl AsyncRead + AsyncWrite + fmt::Debug, + Response = Connection, Error = ConnectorError, > + Clone { #[cfg(not(feature = "ssl"))] diff --git a/src/client/error.rs b/src/client/error.rs index ba6407230..2c4753642 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -17,6 +17,8 @@ use native_tls::Error as SslError; ))] use std::io::Error as SslError; +use error::{Error, ParseError}; + /// A set of errors that can occur while connecting to an HTTP host #[derive(Fail, Debug)] pub enum ConnectorError { @@ -75,3 +77,41 @@ impl From for ConnectorError { ConnectorError::Resolver(err) } } + +/// A set of errors that can occur during request sending and response reading +#[derive(Debug)] +pub enum SendRequestError { + /// Failed to connect to host + // #[fail(display = "Failed to connect to host: {}", _0)] + Connector(ConnectorError), + /// Error sending request + Send(io::Error), + /// Error parsing response + Response(ParseError), + /// Error sending request body + Body(Error), +} + +impl From for SendRequestError { + fn from(err: io::Error) -> SendRequestError { + SendRequestError::Send(err) + } +} + +impl From for SendRequestError { + fn from(err: ConnectorError) -> SendRequestError { + SendRequestError::Connector(err) + } +} + +impl From for SendRequestError { + fn from(err: ParseError) -> SendRequestError { + SendRequestError::Response(err) + } +} + +impl From for SendRequestError { + fn from(err: Error) -> SendRequestError { + SendRequestError::Body(err) + } +} diff --git a/src/client/mod.rs b/src/client/mod.rs index 714e6c694..da0cbc670 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -3,12 +3,14 @@ mod connect; mod connection; mod connector; mod error; +mod pipeline; mod pool; mod request; mod response; pub use self::connect::Connect; +pub use self::connection::Connection; pub use self::connector::Connector; -pub use self::error::{ConnectorError, InvalidUrlKind}; -pub use self::request::{ClientRequest, ClientRequestBuilder}; +pub use self::error::{ConnectorError, InvalidUrlKind, SendRequestError}; +pub use self::request::{ClientRequest, ClientRequestBuilder, RequestHead}; pub use self::response::ClientResponse; diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs new file mode 100644 index 000000000..fff900131 --- /dev/null +++ b/src/client/pipeline.rs @@ -0,0 +1,174 @@ +use std::collections::VecDeque; + +use actix_net::codec::Framed; +use actix_net::service::Service; +use bytes::Bytes; +use futures::future::{err, ok, Either}; +use futures::{Async, Future, Poll, Sink, Stream}; +use tokio_io::{AsyncRead, AsyncWrite}; + +use super::error::{ConnectorError, SendRequestError}; +use super::request::RequestHead; +use super::response::ClientResponse; +use super::{Connect, Connection}; +use body::{BodyStream, BodyType, MessageBody}; +use error::Error; +use h1; + +pub fn send_request( + head: RequestHead, + body: B, + connector: &mut T, +) -> impl Future +where + T: Service, Error = ConnectorError>, + B: MessageBody, + Io: AsyncRead + AsyncWrite + 'static, +{ + let tp = body.tp(); + + connector + .call(Connect::new(head.uri.clone())) + .from_err() + .map(|io| Framed::new(io, h1::ClientCodec::default())) + .and_then(|framed| framed.send((head, tp).into()).from_err()) + .and_then(move |framed| match body.tp() { + BodyType::None | BodyType::Zero => Either::A(ok(framed)), + _ => Either::B(SendBody::new(body, framed)), + }).and_then(|framed| { + framed + .into_future() + .map_err(|(e, _)| SendRequestError::from(e)) + .and_then(|(item, framed)| { + if let Some(item) = item { + let mut res = item.into_item().unwrap(); + match framed.get_codec().message_type() { + h1::MessageType::None => release_connection(framed), + _ => res.payload = Some(Payload::stream(framed)), + } + ok(res) + } else { + err(ConnectorError::Disconnected.into()) + } + }) + }) +} + +struct SendBody { + body: Option, + framed: Option, h1::ClientCodec>>, + write_buf: VecDeque>, + flushed: bool, +} + +impl SendBody +where + Io: AsyncRead + AsyncWrite + 'static, + B: MessageBody, +{ + fn new(body: B, framed: Framed, h1::ClientCodec>) -> Self { + SendBody { + body: Some(body), + framed: Some(framed), + write_buf: VecDeque::new(), + flushed: true, + } + } +} + +impl Future for SendBody +where + Io: AsyncRead + AsyncWrite + 'static, + B: MessageBody, +{ + type Item = Framed, h1::ClientCodec>; + type Error = SendRequestError; + + fn poll(&mut self) -> Poll { + let mut body_ready = true; + loop { + while body_ready + && self.body.is_some() + && !self.framed.as_ref().unwrap().is_write_buf_full() + { + match self.body.as_mut().unwrap().poll_next()? { + Async::Ready(None) => { + self.flushed = false; + self.framed + .as_mut() + .unwrap() + .start_send(h1::Message::Chunk(None))?; + break; + } + Async::Ready(Some(chunk)) => { + self.flushed = false; + self.framed + .as_mut() + .unwrap() + .start_send(h1::Message::Chunk(Some(chunk)))?; + } + Async::NotReady => body_ready = false, + } + } + + if !self.flushed { + match self.framed.as_mut().unwrap().poll_complete()? { + Async::Ready(_) => { + self.flushed = true; + continue; + } + Async::NotReady => return Ok(Async::NotReady), + } + } + + if self.body.is_none() { + return Ok(Async::Ready(self.framed.take().unwrap())); + } + return Ok(Async::NotReady); + } + } +} + +struct Payload { + framed: Option, h1::ClientCodec>>, +} + +impl Payload { + fn stream(framed: Framed, h1::ClientCodec>) -> BodyStream { + Box::new(Payload { + framed: Some(framed), + }) + } +} + +impl Stream for Payload { + type Item = Bytes; + type Error = Error; + + fn poll(&mut self) -> Poll, Error> { + match self.framed.as_mut().unwrap().poll()? { + Async::NotReady => Ok(Async::NotReady), + Async::Ready(Some(chunk)) => match chunk { + h1::Message::Chunk(Some(chunk)) => Ok(Async::Ready(Some(chunk))), + h1::Message::Chunk(None) => { + release_connection(self.framed.take().unwrap()); + Ok(Async::Ready(None)) + } + h1::Message::Item(_) => unreachable!(), + }, + Async::Ready(None) => Ok(Async::Ready(None)), + } + } +} + +fn release_connection(framed: Framed, h1::ClientCodec>) +where + Io: AsyncRead + AsyncWrite + 'static, +{ + let parts = framed.into_parts(); + if parts.read_buf.is_empty() && parts.write_buf.is_empty() { + parts.io.release() + } else { + parts.io.close() + } +} diff --git a/src/client/pool.rs b/src/client/pool.rs index 6ff8c96ce..25296a6dd 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -327,10 +327,7 @@ enum Acquire { NotAvailable, } -pub(crate) struct Inner -where - Io: AsyncRead + AsyncWrite + 'static, -{ +pub(crate) struct Inner { conn_lifetime: Duration, conn_keep_alive: Duration, disconnect_timeout: Option, @@ -345,6 +342,33 @@ where task: AtomicTask, } +impl Inner { + fn reserve(&mut self) { + self.acquired += 1; + } + + fn release(&mut self) { + self.acquired -= 1; + } + + fn release_waiter(&mut self, key: &Key, token: usize) { + self.waiters.remove(token); + self.waiters_queue.remove(&(key.clone(), token)); + } + + fn release_conn(&mut self, key: &Key, io: Io, created: Instant) { + self.acquired -= 1; + self.available + .entry(key.clone()) + .or_insert_with(VecDeque::new) + .push_back(AvailableConnection { + io, + created, + used: Instant::now(), + }); + } +} + impl Inner where Io: AsyncRead + AsyncWrite + 'static, @@ -367,11 +391,6 @@ where (rx, token) } - fn release_waiter(&mut self, key: &Key, token: usize) { - self.waiters.remove(token); - self.waiters_queue.remove(&(key.clone(), token)); - } - fn acquire(&mut self, key: &Key) -> Acquire { // check limits if self.limit > 0 && self.acquired >= self.limit { @@ -412,26 +431,6 @@ where Acquire::Available } - fn reserve(&mut self) { - self.acquired += 1; - } - - fn release(&mut self) { - self.acquired -= 1; - } - - fn release_conn(&mut self, key: &Key, io: Io, created: Instant) { - self.acquired -= 1; - self.available - .entry(key.clone()) - .or_insert_with(VecDeque::new) - .push_back(AvailableConnection { - io, - created, - used: Instant::now(), - }); - } - fn release_close(&mut self, io: Io) { self.acquired -= 1; if let Some(timeout) = self.disconnect_timeout { @@ -541,10 +540,7 @@ where } } -pub(crate) struct Acquired( - Key, - Option>>>, -); +pub(crate) struct Acquired(Key, Option>>>); impl Acquired where @@ -567,10 +563,7 @@ where } } -impl Drop for Acquired -where - T: AsyncRead + AsyncWrite + 'static, -{ +impl Drop for Acquired { fn drop(&mut self) { if let Some(inner) = self.1.take() { inner.as_ref().borrow_mut().release(); diff --git a/src/client/request.rs b/src/client/request.rs index 8c7949336..603374135 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -2,17 +2,25 @@ use std::fmt; use std::fmt::Write as FmtWrite; use std::io::Write; -use bytes::{BufMut, BytesMut}; +use actix_net::service::Service; +use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; +use futures::{Future, Stream}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use tokio_io::{AsyncRead, AsyncWrite}; use urlcrate::Url; +use body::{MessageBody, MessageBodyStream}; +use error::Error; use header::{self, Header, IntoHeaderValue}; use http::{ uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, Uri, Version, }; +use super::response::ClientResponse; +use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; + /// An HTTP Client Request /// /// ```rust @@ -38,29 +46,40 @@ use http::{ /// ); /// } /// ``` -pub struct ClientRequest { - uri: Uri, - method: Method, - version: Version, - headers: HeaderMap, - chunked: bool, - upgrade: bool, +pub struct ClientRequest { + head: RequestHead, + body: B, } -impl Default for ClientRequest { - fn default() -> ClientRequest { - ClientRequest { +pub struct RequestHead { + pub uri: Uri, + pub method: Method, + pub version: Version, + pub headers: HeaderMap, +} + +impl Default for RequestHead { + fn default() -> RequestHead { + RequestHead { uri: Uri::default(), method: Method::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), - chunked: false, - upgrade: false, } } } -impl ClientRequest { +impl ClientRequest<()> { + /// Create client request builder + pub fn build() -> ClientRequestBuilder { + ClientRequestBuilder { + head: Some(RequestHead::default()), + err: None, + cookies: None, + default_headers: true, + } + } + /// Create request builder for `GET` request pub fn get>(uri: U) -> ClientRequestBuilder { let mut builder = ClientRequest::build(); @@ -97,87 +116,90 @@ impl ClientRequest { } } -impl ClientRequest { - /// Create client request builder - pub fn build() -> ClientRequestBuilder { - ClientRequestBuilder { - request: Some(ClientRequest::default()), - err: None, - cookies: None, - default_headers: true, - } - } - +impl ClientRequest +where + B: MessageBody, +{ /// Get the request URI #[inline] pub fn uri(&self) -> &Uri { - &self.uri + &self.head.uri } /// Set client request URI #[inline] pub fn set_uri(&mut self, uri: Uri) { - self.uri = uri + self.head.uri = uri } /// Get the request method #[inline] pub fn method(&self) -> &Method { - &self.method + &self.head.method } /// Set HTTP `Method` for the request #[inline] pub fn set_method(&mut self, method: Method) { - self.method = method + self.head.method = method } /// Get HTTP version for the request #[inline] pub fn version(&self) -> Version { - self.version + self.head.version } /// Set http `Version` for the request #[inline] pub fn set_version(&mut self, version: Version) { - self.version = version + self.head.version = version } /// Get the headers from the request #[inline] pub fn headers(&self) -> &HeaderMap { - &self.headers + &self.head.headers } /// Get a mutable reference to the headers #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.headers + &mut self.head.headers } - /// is chunked encoding enabled - #[inline] - pub fn chunked(&self) -> bool { - self.chunked + /// Deconstruct ClientRequest to a RequestHead and body tuple + pub fn into_parts(self) -> (RequestHead, B) { + (self.head, self.body) } - /// is upgrade request - #[inline] - pub fn upgrade(&self) -> bool { - self.upgrade + // Send request + /// + /// This method returns a future that resolves to a ClientResponse + pub fn send( + self, + connector: &mut T, + ) -> impl Future + where + T: Service, Error = ConnectorError>, + Io: AsyncRead + AsyncWrite + 'static, + { + pipeline::send_request(self.head, self.body, connector) } } -impl fmt::Debug for ClientRequest { +impl fmt::Debug for ClientRequest +where + B: MessageBody, +{ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!( f, "\nClientRequest {:?} {}:{}", - self.version, self.method, self.uri + self.head.version, self.head.method, self.head.uri )?; writeln!(f, " headers:")?; - for (key, val) in self.headers.iter() { + for (key, val) in self.head.headers.iter() { writeln!(f, " {:?}: {:?}", key, val)?; } Ok(()) @@ -189,7 +211,7 @@ impl fmt::Debug for ClientRequest { /// This type can be used to construct an instance of `ClientRequest` through a /// builder-like pattern. pub struct ClientRequestBuilder { - request: Option, + head: Option, err: Option, cookies: Option, default_headers: bool, @@ -208,7 +230,7 @@ impl ClientRequestBuilder { fn _uri(&mut self, url: &str) -> &mut Self { match Uri::try_from(url) { Ok(uri) => { - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { parts.uri = uri; } } @@ -220,7 +242,7 @@ impl ClientRequestBuilder { /// Set HTTP method of this request. #[inline] pub fn method(&mut self, method: Method) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { parts.method = method; } self @@ -229,7 +251,7 @@ impl ClientRequestBuilder { /// Set HTTP method of this request. #[inline] pub fn get_method(&mut self) -> &Method { - let parts = self.request.as_ref().expect("cannot reuse request builder"); + let parts = self.head.as_ref().expect("cannot reuse request builder"); &parts.method } @@ -238,7 +260,7 @@ impl ClientRequestBuilder { /// By default requests's HTTP version depends on network stream #[inline] pub fn version(&mut self, version: Version) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { parts.version = version; } self @@ -263,7 +285,7 @@ impl ClientRequestBuilder { /// ``` #[doc(hidden)] pub fn set(&mut self, hdr: H) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match hdr.try_into() { Ok(value) => { parts.headers.insert(H::name(), value); @@ -299,7 +321,7 @@ impl ClientRequestBuilder { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match HeaderName::try_from(key) { Ok(key) => match value.try_into() { Ok(value) => { @@ -319,7 +341,7 @@ impl ClientRequestBuilder { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match HeaderName::try_from(key) { Ok(key) => match value.try_into() { Ok(value) => { @@ -339,7 +361,7 @@ impl ClientRequestBuilder { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match HeaderName::try_from(key) { Ok(key) => if !parts.headers.contains_key(&key) { match value.try_into() { @@ -357,11 +379,12 @@ impl ClientRequestBuilder { /// Enable connection upgrade #[inline] - pub fn upgrade(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.upgrade = true; - } - self + pub fn upgrade(&mut self, value: V) -> &mut Self + where + V: IntoHeaderValue, + { + self.set_header(header::UPGRADE, value) + .set_header(header::CONNECTION, "upgrade") } /// Set request's content type @@ -370,7 +393,7 @@ impl ClientRequestBuilder { where HeaderValue: HttpTryFrom, { - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match HeaderValue::try_from(value) { Ok(value) => { parts.headers.insert(header::CONTENT_TYPE, value); @@ -454,14 +477,17 @@ impl ClientRequestBuilder { /// Set a body and generate `ClientRequest`. /// /// `ClientRequestBuilder` can not be used after this call. - pub fn finish(&mut self) -> Result { + pub fn body( + &mut self, + body: B, + ) -> Result, HttpError> { if let Some(e) = self.err.take() { return Err(e); } if self.default_headers { // enable br only for https - let https = if let Some(parts) = parts(&mut self.request, &self.err) { + let https = if let Some(parts) = parts(&mut self.head, &self.err) { parts .uri .scheme_part() @@ -478,7 +504,7 @@ impl ClientRequestBuilder { } // set request host header - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { if let Some(host) = parts.uri.host() { if !parts.headers.contains_key(header::HOST) { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); @@ -505,7 +531,7 @@ impl ClientRequestBuilder { ); } - let mut request = self.request.take().expect("cannot reuse request builder"); + let mut head = self.head.take().expect("cannot reuse request builder"); // set cookies if let Some(ref mut jar) = self.cookies { @@ -515,18 +541,38 @@ impl ClientRequestBuilder { let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); let _ = write!(&mut cookie, "; {}={}", name, value); } - request.headers.insert( + head.headers.insert( header::COOKIE, HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), ); } - Ok(request) + Ok(ClientRequest { head, body }) + } + + /// Set an streaming body and generate `ClientRequest`. + /// + /// `ClientRequestBuilder` can not be used after this call. + pub fn stream( + &mut self, + stream: S, + ) -> Result, HttpError> + where + S: Stream, + { + self.body(MessageBodyStream::new(stream)) + } + + /// Set an empty body and generate `ClientRequest`. + /// + /// `ClientRequestBuilder` can not be used after this call. + pub fn finish(&mut self) -> Result, HttpError> { + self.body(()) } /// This method construct new `ClientRequestBuilder` pub fn take(&mut self) -> ClientRequestBuilder { ClientRequestBuilder { - request: self.request.take(), + head: self.head.take(), err: self.err.take(), cookies: self.cookies.take(), default_headers: self.default_headers, @@ -536,9 +582,9 @@ impl ClientRequestBuilder { #[inline] fn parts<'a>( - parts: &'a mut Option, + parts: &'a mut Option, err: &Option, -) -> Option<&'a mut ClientRequest> { +) -> Option<&'a mut RequestHead> { if err.is_some() { return None; } @@ -547,7 +593,7 @@ fn parts<'a>( impl fmt::Debug for ClientRequestBuilder { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(ref parts) = self.request { + if let Some(ref parts) = self.head { writeln!( f, "\nClientRequestBuilder {:?} {}:{}", diff --git a/src/client/response.rs b/src/client/response.rs index 627a1c78e..0d5a87a0d 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -2,35 +2,38 @@ use std::cell::{Cell, Ref, RefCell, RefMut}; use std::fmt; use std::rc::Rc; +use bytes::Bytes; +use futures::{Async, Poll, Stream}; use http::{HeaderMap, Method, StatusCode, Version}; +use body::BodyStream; +use error::Error; use extensions::Extensions; -use httpmessage::HttpMessage; -use payload::Payload; use request::{Message, MessageFlags, MessagePool}; use uri::Url; /// Client Response pub struct ClientResponse { pub(crate) inner: Rc, + pub(crate) payload: Option, } -impl HttpMessage for ClientResponse { - type Stream = Payload; +// impl HttpMessage for ClientResponse { +// type Stream = Payload; - fn headers(&self) -> &HeaderMap { - &self.inner.headers - } +// fn headers(&self) -> &HeaderMap { +// &self.inner.headers +// } - #[inline] - fn payload(&self) -> Payload { - if let Some(payload) = self.inner.payload.borrow_mut().take() { - payload - } else { - Payload::empty() - } - } -} +// #[inline] +// fn payload(&self) -> Payload { +// if let Some(payload) = self.inner.payload.borrow_mut().take() { +// payload +// } else { +// Payload::empty() +// } +// } +// } impl ClientResponse { /// Create new Request instance @@ -52,6 +55,7 @@ impl ClientResponse { payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), }), + payload: None, } } @@ -108,6 +112,19 @@ impl ClientResponse { } } +impl Stream for ClientResponse { + type Item = Bytes; + type Error = Error; + + fn poll(&mut self) -> Poll, Error> { + if let Some(ref mut payload) = self.payload { + payload.poll() + } else { + Ok(Async::Ready(None)) + } + } +} + impl Drop for ClientResponse { fn drop(&mut self) { if Rc::strong_count(&self.inner) == 1 { diff --git a/src/h1/client.rs b/src/h1/client.rs index b55af185f..9ace98e0e 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -7,12 +7,14 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType, ResponseDecoder}; use super::encoder::{RequestEncoder, ResponseLength}; use super::{Message, MessageType}; -use body::{Binary, Body}; -use client::{ClientRequest, ClientResponse}; +use body::{Binary, Body, BodyType}; +use client::{ClientResponse, RequestHead}; use config::ServiceConfig; use error::ParseError; use helpers; -use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; +use http::header::{ + HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, +}; use http::{Method, Version}; use request::MessagePool; @@ -22,7 +24,7 @@ bitflags! { const UPGRADE = 0b0000_0010; const KEEPALIVE = 0b0000_0100; const KEEPALIVE_ENABLED = 0b0000_1000; - const UNHANDLED = 0b0001_0000; + const STREAM = 0b0001_0000; } } @@ -86,8 +88,8 @@ impl ClientCodec { /// Check last request's message type pub fn message_type(&self) -> MessageType { - if self.flags.contains(Flags::UNHANDLED) { - MessageType::Unhandled + if self.flags.contains(Flags::STREAM) { + MessageType::Stream } else if self.payload.is_none() { MessageType::None } else { @@ -96,38 +98,31 @@ impl ClientCodec { } /// prepare transfer encoding - pub fn prepare_te(&mut self, res: &mut ClientRequest) { + pub fn prepare_te(&mut self, head: &mut RequestHead, btype: BodyType) { self.te - .update(res, self.flags.contains(Flags::HEAD), self.version); + .update(head, self.flags.contains(Flags::HEAD), self.version); } fn encode_response( &mut self, - msg: ClientRequest, + msg: RequestHead, + btype: BodyType, buffer: &mut BytesMut, ) -> io::Result<()> { - // Connection upgrade - if msg.upgrade() { - self.flags.insert(Flags::UPGRADE); - } - // render message { // status line writeln!( Writer(buffer), "{} {} {:?}\r", - msg.method(), - msg.uri() - .path_and_query() - .map(|u| u.as_str()) - .unwrap_or("/"), - msg.version() + msg.method, + msg.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), + msg.version ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; // write headers - buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE); - for (key, value) in msg.headers() { + buffer.reserve(msg.headers.len() * AVERAGE_HEADER_SIZE); + for (key, value) in &msg.headers { let v = value.as_ref(); let k = key.as_str().as_bytes(); buffer.reserve(k.len() + v.len() + 4); @@ -135,10 +130,15 @@ impl ClientCodec { buffer.put_slice(b": "); buffer.put_slice(v); buffer.put_slice(b"\r\n"); + + // Connection upgrade + if key == UPGRADE { + self.flags.insert(Flags::UPGRADE); + } } // set date header - if !msg.headers().contains_key(DATE) { + if !msg.headers.contains_key(DATE) { self.config.set_date(buffer); } else { buffer.extend_from_slice(b"\r\n"); @@ -160,8 +160,6 @@ impl Decoder for ClientCodec { Some(PayloadItem::Eof) => Some(Message::Chunk(None)), None => None, }) - } else if self.flags.contains(Flags::UNHANDLED) { - Ok(None) } else if let Some((req, payload)) = self.decoder.decode(src)? { self.flags .set(Flags::HEAD, req.inner.method == Method::HEAD); @@ -172,9 +170,9 @@ impl Decoder for ClientCodec { match payload { PayloadType::None => self.payload = None, PayloadType::Payload(pl) => self.payload = Some(pl), - PayloadType::Unhandled => { - self.payload = None; - self.flags.insert(Flags::UNHANDLED); + PayloadType::Stream(pl) => { + self.payload = Some(pl); + self.flags.insert(Flags::STREAM); } }; Ok(Some(Message::Item(req))) @@ -185,7 +183,7 @@ impl Decoder for ClientCodec { } impl Encoder for ClientCodec { - type Item = Message; + type Item = Message<(RequestHead, BodyType)>; type Error = io::Error; fn encode( @@ -194,8 +192,8 @@ impl Encoder for ClientCodec { dst: &mut BytesMut, ) -> Result<(), Self::Error> { match item { - Message::Item(res) => { - self.encode_response(res, dst)?; + Message::Item((msg, btype)) => { + self.encode_response(msg, btype, dst)?; } Message::Chunk(Some(bytes)) => { self.te.encode(bytes.as_ref(), dst)?; diff --git a/src/h1/codec.rs b/src/h1/codec.rs index dd1e27e08..44a1b81fc 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -23,7 +23,7 @@ bitflags! { const UPGRADE = 0b0000_0010; const KEEPALIVE = 0b0000_0100; const KEEPALIVE_ENABLED = 0b0000_1000; - const UNHANDLED = 0b0001_0000; + const STREAM = 0b0001_0000; } } @@ -93,8 +93,8 @@ impl Codec { /// Check last request's message type pub fn message_type(&self) -> MessageType { - if self.flags.contains(Flags::UNHANDLED) { - MessageType::Unhandled + if self.flags.contains(Flags::STREAM) { + MessageType::Stream } else if self.payload.is_none() { MessageType::None } else { @@ -259,8 +259,6 @@ impl Decoder for Codec { } None => None, }) - } else if self.flags.contains(Flags::UNHANDLED) { - Ok(None) } else if let Some((req, payload)) = self.decoder.decode(src)? { self.flags .set(Flags::HEAD, req.inner.method == Method::HEAD); @@ -271,9 +269,9 @@ impl Decoder for Codec { match payload { PayloadType::None => self.payload = None, PayloadType::Payload(pl) => self.payload = Some(pl), - PayloadType::Unhandled => { - self.payload = None; - self.flags.insert(Flags::UNHANDLED); + PayloadType::Stream(pl) => { + self.payload = Some(pl); + self.flags.insert(Flags::STREAM); } } Ok(Some(Message::Item(req))) diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 472e29936..f2a3ee3f7 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -25,7 +25,7 @@ pub struct ResponseDecoder(&'static MessagePool); pub enum PayloadType { None, Payload(PayloadDecoder), - Unhandled, + Stream(PayloadDecoder), } impl RequestDecoder { @@ -174,7 +174,7 @@ impl Decoder for RequestDecoder { PayloadType::Payload(PayloadDecoder::length(len)) } else if has_upgrade || msg.inner.method == Method::CONNECT { // upgrade(websocket) or connect - PayloadType::Unhandled + PayloadType::Stream(PayloadDecoder::eof()) } else if src.len() >= MAX_BUFFER_SIZE { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); return Err(ParseError::TooLarge); @@ -321,7 +321,7 @@ impl Decoder for ResponseDecoder { || msg.inner.method == Method::CONNECT { // switching protocol or connect - PayloadType::Unhandled + PayloadType::Stream(PayloadDecoder::eof()) } else if src.len() >= MAX_BUFFER_SIZE { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); return Err(ParseError::TooLarge); @@ -667,7 +667,7 @@ mod tests { fn is_unhandled(&self) -> bool { match self { - PayloadType::Unhandled => true, + PayloadType::Stream(_) => true, _ => false, } } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index f4dfdb219..b23af9648 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -344,7 +344,7 @@ where *req.inner.payload.borrow_mut() = Some(pl); self.payload = Some(ps); } - MessageType::Unhandled => { + MessageType::Stream => { self.unhandled = Some(req); return Ok(updated); } diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index fc0a3a1b2..caad113d9 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -9,7 +9,7 @@ use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; use body::{Binary, Body}; -use client::ClientRequest; +use client::RequestHead; use header::ContentEncoding; use http::Method; use request::Request; @@ -196,7 +196,7 @@ impl RequestEncoder { self.te.encode_eof(buf) } - pub fn update(&mut self, resp: &mut ClientRequest, head: bool, version: Version) { + pub fn update(&mut self, resp: &mut RequestHead, head: bool, version: Version) { self.head = head; } } diff --git a/src/h1/mod.rs b/src/h1/mod.rs index e73771778..e7e0759b9 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -33,6 +33,15 @@ pub enum Message { Chunk(Option), } +impl Message { + pub fn into_item(self) -> Option { + match self { + Message::Item(item) => Some(item), + _ => None, + } + } +} + impl From for Message { fn from(item: T) -> Self { Message::Item(item) @@ -44,7 +53,7 @@ impl From for Message { pub enum MessageType { None, Payload, - Unhandled, + Stream, } #[cfg(test)] diff --git a/src/request.rs b/src/request.rs index 07632bf05..593080fa6 100644 --- a/src/request.rs +++ b/src/request.rs @@ -240,7 +240,10 @@ impl MessagePool { if let Some(r) = Rc::get_mut(&mut msg) { r.reset(); } - return ClientResponse { inner: msg }; + return ClientResponse { + inner: msg, + payload: None, + }; } ClientResponse::with_pool(pool) } diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 485ce5620..80cf98684 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -13,6 +13,7 @@ use rand; use sha1::Sha1; use tokio_io::{AsyncRead, AsyncWrite}; +use body::BodyType; use client::ClientResponse; use h1; use ws::Codec; @@ -89,9 +90,7 @@ where req.request.set_header(header::ORIGIN, origin); } - req.request.upgrade(); - req.request.set_header(header::UPGRADE, "websocket"); - req.request.set_header(header::CONNECTION, "upgrade"); + req.request.upgrade("websocket"); req.request.set_header(header::SEC_WEBSOCKET_VERSION, "13"); if let Some(protocols) = req.protocols.take() { @@ -142,7 +141,7 @@ where // h1 protocol let framed = Framed::new(io, h1::ClientCodec::default()); framed - .send(request.into()) + .send((request.into_parts().0, BodyType::None).into()) .map_err(ClientError::from) .and_then(|framed| { framed diff --git a/tests/test_client.rs b/tests/test_client.rs new file mode 100644 index 000000000..6741a2c61 --- /dev/null +++ b/tests/test_client.rs @@ -0,0 +1,147 @@ +extern crate actix; +extern crate actix_http; +extern crate actix_net; +extern crate bytes; +extern crate futures; + +use std::{thread, time}; + +use actix::System; +use actix_net::server::Server; +use actix_net::service::NewServiceExt; +use futures::future::{self, lazy, ok}; + +use actix_http::{client, h1, test, Request, Response}; + +const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World"; + +#[test] +fn test_h1_v2() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::build() + .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .map(|_| ()) + }).unwrap() + .run(); + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let mut connector = sys + .block_on(lazy(|| Ok::<_, ()>(client::Connector::default().service()))) + .unwrap(); + + let req = client::ClientRequest::get(format!("http://{}/", addr)) + .finish() + .unwrap(); + + let response = sys.block_on(req.send(&mut connector)).unwrap(); + assert!(response.status().is_success()); + + let request = client::ClientRequest::get(format!("http://{}/", addr)) + .header("x-test", "111") + .finish() + .unwrap(); + let repr = format!("{:?}", request); + assert!(repr.contains("ClientRequest")); + assert!(repr.contains("x-test")); + + let response = sys.block_on(request.send(&mut connector)).unwrap(); + assert!(response.status().is_success()); + + // read response + // let bytes = srv.execute(response.body()).unwrap(); + // assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + let request = client::ClientRequest::post(format!("http://{}/", addr)) + .finish() + .unwrap(); + let response = sys.block_on(request.send(&mut connector)).unwrap(); + assert!(response.status().is_success()); + + // read response + // let bytes = srv.execute(response.body()).unwrap(); + // assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_connection_close() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::build() + .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map(|_| ()) + }).unwrap() + .run(); + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let mut connector = sys + .block_on(lazy(|| Ok::<_, ()>(client::Connector::default().service()))) + .unwrap(); + + let request = client::ClientRequest::get(format!("http://{}/", addr)) + .header("Connection", "close") + .finish() + .unwrap(); + let response = sys.block_on(request.send(&mut connector)).unwrap(); + assert!(response.status().is_success()); +} + +#[test] +fn test_with_query_parameter() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::build() + .finish(|req: Request| { + if req.uri().query().unwrap().contains("qp=") { + ok::<_, ()>(Response::Ok().finish()) + } else { + ok::<_, ()>(Response::BadRequest().finish()) + } + }).map(|_| ()) + }).unwrap() + .run(); + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let mut connector = sys + .block_on(lazy(|| Ok::<_, ()>(client::Connector::default().service()))) + .unwrap(); + + let request = client::ClientRequest::get(format!("http://{}/?qp=5", addr)) + .finish() + .unwrap(); + + let response = sys.block_on(request.send(&mut connector)).unwrap(); + assert!(response.status().is_success()); +} From 6297fe0d4117bd93a4e215b36fee91e7bcb8d750 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 14 Nov 2018 09:38:16 -0800 Subject: [PATCH 062/208] refactor client response payload handling --- Cargo.toml | 3 +- src/body.rs | 7 ++- src/client/pipeline.rs | 51 +++++++++++++------- src/client/response.rs | 45 +++++++++-------- src/error.rs | 8 +++- src/h1/client.rs | 106 ++++++++++++++++++++++++++++++----------- src/h1/dispatcher.rs | 7 ++- src/h1/mod.rs | 2 +- src/payload.rs | 8 ++-- src/request.rs | 2 +- tests/test_client.rs | 10 ++-- 11 files changed, 166 insertions(+), 83 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e586f34ed..074e53631 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,8 @@ rust-tls = ["rustls", "actix-net/rust-tls"] [dependencies] actix = "0.7.5" #actix-net = "0.2.0" -actix-net = { git="https://github.com/actix/actix-net.git" } +#actix-net = { git="https://github.com/actix/actix-net.git" } +actix-net = { path="../actix-net" } base64 = "0.9" bitflags = "1.0" diff --git a/src/body.rs b/src/body.rs index e001273c4..1165909e8 100644 --- a/src/body.rs +++ b/src/body.rs @@ -4,10 +4,13 @@ use std::{fmt, mem}; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll, Stream}; -use error::Error; +use error::{Error, PayloadError}; /// Type represent streaming body -pub type BodyStream = Box>; +pub type BodyStream = Box>; + +/// Type represent streaming payload +pub type PayloadStream = Box>; /// Different type of bory pub enum BodyType { diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index fff900131..4081b6354 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -11,8 +11,8 @@ use super::error::{ConnectorError, SendRequestError}; use super::request::RequestHead; use super::response::ClientResponse; use super::{Connect, Connection}; -use body::{BodyStream, BodyType, MessageBody}; -use error::Error; +use body::{BodyType, MessageBody, PayloadStream}; +use error::PayloadError; use h1; pub fn send_request( @@ -44,7 +44,7 @@ where let mut res = item.into_item().unwrap(); match framed.get_codec().message_type() { h1::MessageType::None => release_connection(framed), - _ => res.payload = Some(Payload::stream(framed)), + _ => *res.payload.borrow_mut() = Some(Payload::stream(framed)), } ok(res) } else { @@ -129,41 +129,56 @@ where } } -struct Payload { - framed: Option, h1::ClientCodec>>, +struct EmptyPayload; + +impl Stream for EmptyPayload { + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll, Self::Error> { + Ok(Async::Ready(None)) + } +} + +pub(crate) struct Payload { + framed: Option, h1::ClientPayloadCodec>>, +} + +impl Payload<()> { + pub fn empty() -> PayloadStream { + Box::new(EmptyPayload) + } } impl Payload { - fn stream(framed: Framed, h1::ClientCodec>) -> BodyStream { + fn stream(framed: Framed, h1::ClientCodec>) -> PayloadStream { Box::new(Payload { - framed: Some(framed), + framed: Some(framed.map_codec(|codec| codec.into_payload_codec())), }) } } impl Stream for Payload { type Item = Bytes; - type Error = Error; + type Error = PayloadError; - fn poll(&mut self) -> Poll, Error> { + fn poll(&mut self) -> Poll, Self::Error> { match self.framed.as_mut().unwrap().poll()? { Async::NotReady => Ok(Async::NotReady), - Async::Ready(Some(chunk)) => match chunk { - h1::Message::Chunk(Some(chunk)) => Ok(Async::Ready(Some(chunk))), - h1::Message::Chunk(None) => { - release_connection(self.framed.take().unwrap()); - Ok(Async::Ready(None)) - } - h1::Message::Item(_) => unreachable!(), + Async::Ready(Some(chunk)) => if let Some(chunk) = chunk { + Ok(Async::Ready(Some(chunk))) + } else { + release_connection(self.framed.take().unwrap()); + Ok(Async::Ready(None)) }, Async::Ready(None) => Ok(Async::Ready(None)), } } } -fn release_connection(framed: Framed, h1::ClientCodec>) +fn release_connection(framed: Framed, U>) where - Io: AsyncRead + AsyncWrite + 'static, + T: AsyncRead + AsyncWrite + 'static, { let parts = framed.into_parts(); if parts.read_buf.is_empty() && parts.write_buf.is_empty() { diff --git a/src/client/response.rs b/src/client/response.rs index 0d5a87a0d..e8e63e4f7 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -6,34 +6,37 @@ use bytes::Bytes; use futures::{Async, Poll, Stream}; use http::{HeaderMap, Method, StatusCode, Version}; -use body::BodyStream; -use error::Error; +use body::PayloadStream; +use error::PayloadError; use extensions::Extensions; +use httpmessage::HttpMessage; use request::{Message, MessageFlags, MessagePool}; use uri::Url; +use super::pipeline::Payload; + /// Client Response pub struct ClientResponse { pub(crate) inner: Rc, - pub(crate) payload: Option, + pub(crate) payload: RefCell>, } -// impl HttpMessage for ClientResponse { -// type Stream = Payload; +impl HttpMessage for ClientResponse { + type Stream = PayloadStream; -// fn headers(&self) -> &HeaderMap { -// &self.inner.headers -// } + fn headers(&self) -> &HeaderMap { + &self.inner.headers + } -// #[inline] -// fn payload(&self) -> Payload { -// if let Some(payload) = self.inner.payload.borrow_mut().take() { -// payload -// } else { -// Payload::empty() -// } -// } -// } + #[inline] + fn payload(&self) -> Self::Stream { + if let Some(payload) = self.payload.borrow_mut().take() { + payload + } else { + Payload::empty() + } + } +} impl ClientResponse { /// Create new Request instance @@ -55,7 +58,7 @@ impl ClientResponse { payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), }), - payload: None, + payload: RefCell::new(None), } } @@ -114,10 +117,10 @@ impl ClientResponse { impl Stream for ClientResponse { type Item = Bytes; - type Error = Error; + type Error = PayloadError; - fn poll(&mut self) -> Poll, Error> { - if let Some(ref mut payload) = self.payload { + fn poll(&mut self) -> Poll, Self::Error> { + if let Some(ref mut payload) = &mut *self.payload.borrow_mut() { payload.poll() } else { Ok(Async::Ready(None)) diff --git a/src/error.rs b/src/error.rs index 3064cda49..956ec4eb4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -339,7 +339,7 @@ impl From for ParseError { pub enum PayloadError { /// A payload reached EOF, but is not complete. #[fail(display = "A payload reached EOF, but is not complete.")] - Incomplete, + Incomplete(Option), /// Content encoding stream corruption #[fail(display = "Can not decode content-encoding.")] EncodingCorrupted, @@ -351,6 +351,12 @@ pub enum PayloadError { UnknownLength, } +impl From for PayloadError { + fn from(err: io::Error) -> Self { + PayloadError::Incomplete(Some(err)) + } +} + /// `PayloadError` returns two possible results: /// /// - `Overflow` returns `PayloadTooLarge` diff --git a/src/h1/client.rs b/src/h1/client.rs index 9ace98e0e..eb7bdc233 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -10,7 +10,7 @@ use super::{Message, MessageType}; use body::{Binary, Body, BodyType}; use client::{ClientResponse, RequestHead}; use config::ServiceConfig; -use error::ParseError; +use error::{ParseError, PayloadError}; use helpers; use http::header::{ HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, @@ -32,6 +32,15 @@ const AVERAGE_HEADER_SIZE: usize = 30; /// HTTP/1 Codec pub struct ClientCodec { + inner: ClientCodecInner, +} + +/// HTTP/1 Payload Codec +pub struct ClientPayloadCodec { + inner: ClientCodecInner, +} + +struct ClientCodecInner { config: ServiceConfig, decoder: ResponseDecoder, payload: Option, @@ -65,32 +74,34 @@ impl ClientCodec { Flags::empty() }; ClientCodec { - config, - decoder: ResponseDecoder::with_pool(pool), - payload: None, - version: Version::HTTP_11, + inner: ClientCodecInner { + config, + decoder: ResponseDecoder::with_pool(pool), + payload: None, + version: Version::HTTP_11, - flags, - headers_size: 0, - te: RequestEncoder::default(), + flags, + headers_size: 0, + te: RequestEncoder::default(), + }, } } /// Check if request is upgrade pub fn upgrade(&self) -> bool { - self.flags.contains(Flags::UPGRADE) + self.inner.flags.contains(Flags::UPGRADE) } /// Check if last response is keep-alive pub fn keepalive(&self) -> bool { - self.flags.contains(Flags::KEEPALIVE) + self.inner.flags.contains(Flags::KEEPALIVE) } /// Check last request's message type pub fn message_type(&self) -> MessageType { - if self.flags.contains(Flags::STREAM) { + if self.inner.flags.contains(Flags::STREAM) { MessageType::Stream - } else if self.payload.is_none() { + } else if self.inner.payload.is_none() { MessageType::None } else { MessageType::Payload @@ -99,10 +110,27 @@ impl ClientCodec { /// prepare transfer encoding pub fn prepare_te(&mut self, head: &mut RequestHead, btype: BodyType) { - self.te - .update(head, self.flags.contains(Flags::HEAD), self.version); + self.inner.te.update( + head, + self.inner.flags.contains(Flags::HEAD), + self.inner.version, + ); } + /// Convert message codec to a payload codec + pub fn into_payload_codec(self) -> ClientPayloadCodec { + ClientPayloadCodec { inner: self.inner } + } +} + +impl ClientPayloadCodec { + /// Transform payload codec to a message codec + pub fn into_message_codec(self) -> ClientCodec { + ClientCodec { inner: self.inner } + } +} + +impl ClientCodecInner { fn encode_response( &mut self, msg: RequestHead, @@ -154,25 +182,26 @@ impl Decoder for ClientCodec { type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - if self.payload.is_some() { - Ok(match self.payload.as_mut().unwrap().decode(src)? { + if self.inner.payload.is_some() { + Ok(match self.inner.payload.as_mut().unwrap().decode(src)? { Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), Some(PayloadItem::Eof) => Some(Message::Chunk(None)), None => None, }) - } else if let Some((req, payload)) = self.decoder.decode(src)? { - self.flags + } else if let Some((req, payload)) = self.inner.decoder.decode(src)? { + self.inner + .flags .set(Flags::HEAD, req.inner.method == Method::HEAD); - self.version = req.inner.version; - if self.flags.contains(Flags::KEEPALIVE_ENABLED) { - self.flags.set(Flags::KEEPALIVE, req.keep_alive()); + self.inner.version = req.inner.version; + if self.inner.flags.contains(Flags::KEEPALIVE_ENABLED) { + self.inner.flags.set(Flags::KEEPALIVE, req.keep_alive()); } match payload { - PayloadType::None => self.payload = None, - PayloadType::Payload(pl) => self.payload = Some(pl), + PayloadType::None => self.inner.payload = None, + PayloadType::Payload(pl) => self.inner.payload = Some(pl), PayloadType::Stream(pl) => { - self.payload = Some(pl); - self.flags.insert(Flags::STREAM); + self.inner.payload = Some(pl); + self.inner.flags.insert(Flags::STREAM); } }; Ok(Some(Message::Item(req))) @@ -182,6 +211,27 @@ impl Decoder for ClientCodec { } } +impl Decoder for ClientPayloadCodec { + type Item = Option; + type Error = PayloadError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + assert!( + self.inner.payload.is_some(), + "Payload decoder is not specified" + ); + + Ok(match self.inner.payload.as_mut().unwrap().decode(src)? { + Some(PayloadItem::Chunk(chunk)) => Some(Some(chunk)), + Some(PayloadItem::Eof) => { + self.inner.payload.take(); + Some(None) + } + None => None, + }) + } +} + impl Encoder for ClientCodec { type Item = Message<(RequestHead, BodyType)>; type Error = io::Error; @@ -193,13 +243,13 @@ impl Encoder for ClientCodec { ) -> Result<(), Self::Error> { match item { Message::Item((msg, btype)) => { - self.encode_response(msg, btype, dst)?; + self.inner.encode_response(msg, btype, dst)?; } Message::Chunk(Some(bytes)) => { - self.te.encode(bytes.as_ref(), dst)?; + self.inner.te.encode(bytes.as_ref(), dst)?; } Message::Chunk(None) => { - self.te.encode_eof(dst)?; + self.inner.te.encode_eof(dst)?; } } Ok(()) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index b23af9648..71d6435c1 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -143,7 +143,7 @@ where fn client_disconnected(&mut self) { self.flags.insert(Flags::DISCONNECTED); if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete); + payload.set_error(PayloadError::Incomplete(None)); } } @@ -228,7 +228,7 @@ where } Err(err) => { if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete); + payload.set_error(PayloadError::Incomplete(None)); } return Err(DispatchError::Io(err)); } @@ -236,7 +236,10 @@ where } // Send payload State::SendPayload(ref mut stream, ref mut bin) => { + println!("SEND payload"); if let Some(item) = bin.take() { + let mut framed = self.framed.as_mut().unwrap(); + if framed.is_ match self.framed.as_mut().unwrap().start_send(item) { Ok(AsyncSink::Ready) => { self.flags.remove(Flags::FLUSHED); diff --git a/src/h1/mod.rs b/src/h1/mod.rs index e7e0759b9..21261e99c 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -9,7 +9,7 @@ mod dispatcher; mod encoder; mod service; -pub use self::client::ClientCodec; +pub use self::client::{ClientCodec, ClientPayloadCodec}; pub use self::codec::Codec; pub use self::decoder::{PayloadDecoder, RequestDecoder}; pub use self::dispatcher::Dispatcher; diff --git a/src/payload.rs b/src/payload.rs index 54539c408..b05924969 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -527,7 +527,7 @@ mod tests { #[test] fn test_error() { - let err = PayloadError::Incomplete; + let err = PayloadError::Incomplete(None); assert_eq!( format!("{}", err), "A payload reached EOF, but is not complete." @@ -584,7 +584,7 @@ mod tests { assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - sender.set_error(PayloadError::Incomplete); + sender.set_error(PayloadError::Incomplete(None)); payload.readany().err().unwrap(); let res: Result<(), ()> = Ok(()); result(res) @@ -644,7 +644,7 @@ mod tests { ); assert_eq!(payload.len, 4); - sender.set_error(PayloadError::Incomplete); + sender.set_error(PayloadError::Incomplete(None)); payload.read_exact(10).err().unwrap(); let res: Result<(), ()> = Ok(()); @@ -677,7 +677,7 @@ mod tests { ); assert_eq!(payload.len, 0); - sender.set_error(PayloadError::Incomplete); + sender.set_error(PayloadError::Incomplete(None)); payload.read_until(b"b").err().unwrap(); let res: Result<(), ()> = Ok(()); diff --git a/src/request.rs b/src/request.rs index 593080fa6..fb5cb183b 100644 --- a/src/request.rs +++ b/src/request.rs @@ -242,7 +242,7 @@ impl MessagePool { } return ClientResponse { inner: msg, - payload: None, + payload: RefCell::new(None), }; } ClientResponse::with_pool(pool) diff --git a/tests/test_client.rs b/tests/test_client.rs index 6741a2c61..40920d1b8 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -9,8 +9,10 @@ use std::{thread, time}; use actix::System; use actix_net::server::Server; use actix_net::service::NewServiceExt; +use bytes::Bytes; use futures::future::{self, lazy, ok}; +use actix_http::HttpMessage; use actix_http::{client, h1, test, Request, Response}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -73,8 +75,8 @@ fn test_h1_v2() { assert!(response.status().is_success()); // read response - // let bytes = srv.execute(response.body()).unwrap(); - // assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + let bytes = sys.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); let request = client::ClientRequest::post(format!("http://{}/", addr)) .finish() @@ -83,8 +85,8 @@ fn test_h1_v2() { assert!(response.status().is_success()); // read response - // let bytes = srv.execute(response.body()).unwrap(); - // assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + let bytes = sys.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } #[test] From 03ad9a3105d95aaf5c8f3bb2a58cd8d600380344 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 14 Nov 2018 10:52:40 -0800 Subject: [PATCH 063/208] simplify client decoder --- Cargo.toml | 6 +-- src/client/pipeline.rs | 18 +++------ src/h1/client.rs | 16 +++----- src/h1/dispatcher.rs | 79 +++++++++++++++------------------------- src/h1/mod.rs | 9 ----- src/ws/client/service.rs | 8 +--- 6 files changed, 46 insertions(+), 90 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 074e53631..80d245951 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,9 +45,9 @@ rust-tls = ["rustls", "actix-net/rust-tls"] [dependencies] actix = "0.7.5" -#actix-net = "0.2.0" -#actix-net = { git="https://github.com/actix/actix-net.git" } -actix-net = { path="../actix-net" } +#actix-net = "0.2.2" +actix-net = { git="https://github.com/actix/actix-net.git" } +#actix-net = { path="../actix-net" } base64 = "0.9" bitflags = "1.0" diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 4081b6354..24ec8366d 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -40,11 +40,12 @@ where .into_future() .map_err(|(e, _)| SendRequestError::from(e)) .and_then(|(item, framed)| { - if let Some(item) = item { - let mut res = item.into_item().unwrap(); + if let Some(res) = item { match framed.get_codec().message_type() { h1::MessageType::None => release_connection(framed), - _ => *res.payload.borrow_mut() = Some(Payload::stream(framed)), + _ => { + *res.payload.borrow_mut() = Some(Payload::stream(framed)) + } } ok(res) } else { @@ -92,21 +93,14 @@ where && !self.framed.as_ref().unwrap().is_write_buf_full() { match self.body.as_mut().unwrap().poll_next()? { - Async::Ready(None) => { + Async::Ready(item) => { self.flushed = false; self.framed .as_mut() .unwrap() - .start_send(h1::Message::Chunk(None))?; + .force_send(h1::Message::Chunk(item))?; break; } - Async::Ready(Some(chunk)) => { - self.flushed = false; - self.framed - .as_mut() - .unwrap() - .start_send(h1::Message::Chunk(Some(chunk)))?; - } Async::NotReady => body_ready = false, } } diff --git a/src/h1/client.rs b/src/h1/client.rs index eb7bdc233..d2ac20348 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -178,17 +178,13 @@ impl ClientCodecInner { } impl Decoder for ClientCodec { - type Item = Message; + type Item = ClientResponse; type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - if self.inner.payload.is_some() { - Ok(match self.inner.payload.as_mut().unwrap().decode(src)? { - Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), - Some(PayloadItem::Eof) => Some(Message::Chunk(None)), - None => None, - }) - } else if let Some((req, payload)) = self.inner.decoder.decode(src)? { + debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set"); + + if let Some((req, payload)) = self.inner.decoder.decode(src)? { self.inner .flags .set(Flags::HEAD, req.inner.method == Method::HEAD); @@ -204,7 +200,7 @@ impl Decoder for ClientCodec { self.inner.flags.insert(Flags::STREAM); } }; - Ok(Some(Message::Item(req))) + Ok(Some(req)) } else { Ok(None) } @@ -216,7 +212,7 @@ impl Decoder for ClientPayloadCodec { type Error = PayloadError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - assert!( + debug_assert!( self.inner.payload.is_some(), "Payload decoder is not specified" ); diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 71d6435c1..470d3df93 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -64,7 +64,7 @@ enum State { None, ServiceCall(S::Future), SendResponse(Option<(Message, Body)>), - SendPayload(Option, Option>), + SendPayload(BodyStream), } impl State { @@ -204,21 +204,23 @@ where // send respons State::SendResponse(ref mut item) => { let (msg, body) = item.take().expect("SendResponse is empty"); - match self.framed.as_mut().unwrap().start_send(msg) { + let framed = self.framed.as_mut().unwrap(); + match framed.start_send(msg) { Ok(AsyncSink::Ready) => { - self.flags.set( - Flags::KEEPALIVE, - self.framed.as_mut().unwrap().get_codec().keepalive(), - ); + self.flags + .set(Flags::KEEPALIVE, framed.get_codec().keepalive()); self.flags.remove(Flags::FLUSHED); match body { Body::Empty => Some(State::None), - Body::Binary(mut bin) => Some(State::SendPayload( - None, - Some(Message::Chunk(Some(bin.take()))), - )), Body::Streaming(stream) => { - Some(State::SendPayload(Some(stream), None)) + Some(State::SendPayload(stream)) + } + Body::Binary(mut bin) => { + self.flags.remove(Flags::FLUSHED); + framed + .force_send(Message::Chunk(Some(bin.take())))?; + framed.force_send(Message::Chunk(None))?; + Some(State::None) } } } @@ -235,51 +237,28 @@ where } } // Send payload - State::SendPayload(ref mut stream, ref mut bin) => { - println!("SEND payload"); - if let Some(item) = bin.take() { - let mut framed = self.framed.as_mut().unwrap(); - if framed.is_ - match self.framed.as_mut().unwrap().start_send(item) { - Ok(AsyncSink::Ready) => { - self.flags.remove(Flags::FLUSHED); - } - Ok(AsyncSink::NotReady(item)) => { - *bin = Some(item); - return Ok(()); - } - Err(err) => return Err(DispatchError::Io(err)), - } - } - if let Some(ref mut stream) = stream { - match stream.poll() { - Ok(Async::Ready(Some(item))) => match self - .framed - .as_mut() - .unwrap() - .start_send(Message::Chunk(Some(item))) - { - Ok(AsyncSink::Ready) => { + State::SendPayload(ref mut stream) => { + let mut framed = self.framed.as_mut().unwrap(); + loop { + if !framed.is_write_buf_full() { + match stream.poll().map_err(|_| DispatchError::Unknown)? { + Async::Ready(Some(item)) => { self.flags.remove(Flags::FLUSHED); + framed.force_send(Message::Chunk(Some(item)))?; continue; } - Ok(AsyncSink::NotReady(msg)) => { - *bin = Some(msg); - return Ok(()); + Async::Ready(None) => { + self.flags.remove(Flags::FLUSHED); + framed.force_send(Message::Chunk(None))?; } - Err(err) => return Err(DispatchError::Io(err)), - }, - Ok(Async::Ready(None)) => Some(State::SendPayload( - None, - Some(Message::Chunk(None)), - )), - Ok(Async::NotReady) => return Ok(()), - // Err(err) => return Err(DispatchError::Io(err)), - Err(_) => return Err(DispatchError::Unknown), + Async::NotReady => return Ok(()), + } + } else { + return Ok(()); } - } else { - Some(State::None) + break; } + None } }; diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 21261e99c..818387004 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -33,15 +33,6 @@ pub enum Message { Chunk(Option), } -impl Message { - pub fn into_item(self) -> Option { - match self { - Message::Item(item) => Some(item), - _ => None, - } - } -} - impl From for Message { fn from(item: T) -> Self { Message::Item(item) diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 80cf98684..34a151444 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -172,10 +172,7 @@ where { fut: Box< Future< - Item = ( - Option>, - Framed, - ), + Item = (Option, Framed), Error = ClientError, >, >, @@ -196,8 +193,7 @@ where let (item, framed) = try_ready!(self.fut.poll()); let res = match item { - Some(h1::Message::Item(res)) => res, - Some(h1::Message::Chunk(_)) => unreachable!(), + Some(res) => res, None => return Err(ClientError::Disconnected), }; From 6e7560e28781c71318af9fc28b29bd54a50fa83f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 14 Nov 2018 18:57:58 -0800 Subject: [PATCH 064/208] SendResponse service sends body as well --- src/service.rs | 109 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 89 insertions(+), 20 deletions(-) diff --git a/src/service.rs b/src/service.rs index c76530320..774efb74e 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,13 +1,13 @@ -use std::io; use std::marker::PhantomData; use actix_net::codec::Framed; use actix_net::service::{NewService, Service}; use futures::future::{ok, Either, FutureResult}; use futures::{Async, AsyncSink, Future, Poll, Sink}; -use tokio_io::AsyncWrite; +use tokio_io::{AsyncRead, AsyncWrite}; -use error::ResponseError; +use body::Body; +use error::{Error, ResponseError}; use h1::{Codec, Message}; use response::Response; @@ -113,20 +113,43 @@ pub struct SendResponse(PhantomData<(T,)>); impl Default for SendResponse where - T: AsyncWrite, + T: AsyncRead + AsyncWrite, { fn default() -> Self { SendResponse(PhantomData) } } +impl SendResponse +where + T: AsyncRead + AsyncWrite, +{ + pub fn send( + mut framed: Framed, + mut res: Response, + ) -> impl Future, Error = Error> { + // init codec + framed.get_codec_mut().prepare_te(&mut res); + + // extract body from response + let body = res.replace_body(Body::Empty); + + // write response + SendResponseFut { + res: Some(Message::Item(res)), + body: Some(body), + framed: Some(framed), + } + } +} + impl NewService for SendResponse where - T: AsyncWrite, + T: AsyncRead + AsyncWrite, { type Request = (Response, Framed); type Response = Framed; - type Error = io::Error; + type Error = Error; type InitError = (); type Service = SendResponse; type Future = FutureResult; @@ -138,20 +161,23 @@ where impl Service for SendResponse where - T: AsyncWrite, + T: AsyncRead + AsyncWrite, { type Request = (Response, Framed); type Response = Framed; - type Error = io::Error; + type Error = Error; type Future = SendResponseFut; fn poll_ready(&mut self) -> Poll<(), Self::Error> { Ok(Async::Ready(())) } - fn call(&mut self, (res, framed): Self::Request) -> Self::Future { + fn call(&mut self, (mut res, mut framed): Self::Request) -> Self::Future { + framed.get_codec_mut().prepare_te(&mut res); + let body = res.replace_body(Body::Empty); SendResponseFut { res: Some(Message::Item(res)), + body: Some(body), framed: Some(framed), } } @@ -159,29 +185,72 @@ where pub struct SendResponseFut { res: Option>, + body: Option, framed: Option>, } impl Future for SendResponseFut where - T: AsyncWrite, + T: AsyncRead + AsyncWrite, { type Item = Framed; - type Error = io::Error; + type Error = Error; fn poll(&mut self) -> Poll { - if let Some(res) = self.res.take() { - match self.framed.as_mut().unwrap().start_send(res)? { - AsyncSink::Ready => (), - AsyncSink::NotReady(res) => { - self.res = Some(res); - return Ok(Async::NotReady); + // send response + if self.res.is_some() { + let framed = self.framed.as_mut().unwrap(); + if !framed.is_write_buf_full() { + if let Some(res) = self.res.take() { + println!("SEND RESP: {:?}", res); + framed.force_send(res)?; } } } - match self.framed.as_mut().unwrap().poll_complete()? { - Async::Ready(_) => Ok(Async::Ready(self.framed.take().unwrap())), - Async::NotReady => Ok(Async::NotReady), + + // send body + if self.res.is_none() && self.body.is_some() { + let framed = self.framed.as_mut().unwrap(); + if !framed.is_write_buf_full() { + let body = self.body.take().unwrap(); + match body { + Body::Empty => (), + Body::Streaming(mut stream) => loop { + match stream.poll()? { + Async::Ready(item) => { + let done = item.is_none(); + framed.force_send(Message::Chunk(item.into()))?; + if !done { + if !framed.is_write_buf_full() { + continue; + } else { + self.body = Some(Body::Streaming(stream)); + break; + } + } + } + Async::NotReady => { + self.body = Some(Body::Streaming(stream)); + break; + } + } + }, + Body::Binary(mut bin) => { + framed.force_send(Message::Chunk(Some(bin.take())))?; + framed.force_send(Message::Chunk(None))?; + } + } + } } + + // flush + match self.framed.as_mut().unwrap().poll_complete()? { + Async::Ready(_) => if self.res.is_some() || self.body.is_some() { + return self.poll(); + }, + Async::NotReady => return Ok(Async::NotReady), + } + + Ok(Async::Ready(self.framed.take().unwrap())) } } From acd42f92d8f507358c5884dd387b48c69e4eeca4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 14 Nov 2018 19:08:52 -0800 Subject: [PATCH 065/208] remove debug print --- src/service.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/service.rs b/src/service.rs index 774efb74e..3aa5d1e41 100644 --- a/src/service.rs +++ b/src/service.rs @@ -202,7 +202,6 @@ where let framed = self.framed.as_mut().unwrap(); if !framed.is_write_buf_full() { if let Some(res) = self.res.take() { - println!("SEND RESP: {:?}", res); framed.force_send(res)?; } } From 6d9733cdf79ae196015624e13be16515c96d9479 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 15 Nov 2018 11:10:23 -0800 Subject: [PATCH 066/208] define generic client Connection trait --- src/client/connection.rs | 69 ++++++++++++++++++++++++++-------------- src/client/connector.rs | 58 ++++++++++++++++++++++----------- src/client/pipeline.rs | 44 ++++++++++++++----------- src/client/pool.rs | 32 +++++++++---------- src/client/request.rs | 7 ++-- 5 files changed, 129 insertions(+), 81 deletions(-) diff --git a/src/client/connection.rs b/src/client/connection.rs index eec64267a..363a4ece9 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -5,14 +5,23 @@ use tokio_io::{AsyncRead, AsyncWrite}; use super::pool::Acquired; +pub trait Connection: AsyncRead + AsyncWrite + 'static { + /// Close connection + fn close(&mut self); + + /// Release connection to the connection pool + fn release(&mut self); +} + +#[doc(hidden)] /// HTTP client connection -pub struct Connection { - io: T, +pub struct IoConnection { + io: Option, created: time::Instant, pool: Option>, } -impl fmt::Debug for Connection +impl fmt::Debug for IoConnection where T: fmt::Debug, { @@ -21,59 +30,73 @@ where } } -impl Connection { +impl IoConnection { pub(crate) fn new(io: T, created: time::Instant, pool: Acquired) -> Self { - Connection { - io, + IoConnection { created, + io: Some(io), pool: Some(pool), } } /// Raw IO stream pub fn get_mut(&mut self) -> &mut T { - &mut self.io + self.io.as_mut().unwrap() } + pub(crate) fn into_inner(self) -> (T, time::Instant) { + (self.io.unwrap(), self.created) + } +} + +impl Connection for IoConnection { /// Close connection - pub fn close(mut self) { + fn close(&mut self) { if let Some(mut pool) = self.pool.take() { - pool.close(self) + if let Some(io) = self.io.take() { + pool.close(IoConnection { + io: Some(io), + created: self.created, + pool: None, + }) + } } } /// Release this connection to the connection pool - pub fn release(mut self) { + fn release(&mut self) { if let Some(mut pool) = self.pool.take() { - pool.release(self) + if let Some(io) = self.io.take() { + pool.release(IoConnection { + io: Some(io), + created: self.created, + pool: None, + }) + } } } - - pub(crate) fn into_inner(self) -> (T, time::Instant) { - (self.io, self.created) - } } -impl io::Read for Connection { +impl io::Read for IoConnection { fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.io.read(buf) + self.io.as_mut().unwrap().read(buf) } } -impl AsyncRead for Connection {} +impl AsyncRead for IoConnection {} -impl io::Write for Connection { +impl io::Write for IoConnection { fn write(&mut self, buf: &[u8]) -> io::Result { - self.io.write(buf) + self.io.as_mut().unwrap().write(buf) } fn flush(&mut self) -> io::Result<()> { - self.io.flush() + self.io.as_mut().unwrap().flush() } } -impl AsyncWrite for Connection { +impl AsyncWrite for IoConnection { fn shutdown(&mut self) -> Poll<(), io::Error> { - self.io.shutdown() + self.io.as_mut().unwrap().shutdown() } } diff --git a/src/client/connector.rs b/src/client/connector.rs index 1eae135f2..818085214 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -11,7 +11,7 @@ use tokio_io::{AsyncRead, AsyncWrite}; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; use super::connect::Connect; -use super::connection::Connection; +use super::connection::{Connection, IoConnection}; use super::error::ConnectorError; use super::pool::ConnectionPool; @@ -130,7 +130,7 @@ impl Connector { self, ) -> impl Service< Request = Connect, - Response = Connection, + Response = impl Connection, Error = ConnectorError, > + Clone { #[cfg(not(feature = "ssl"))] @@ -234,11 +234,11 @@ mod connect_impl { T: Service, { type Request = Connect; - type Response = Connection; + type Response = IoConnection; type Error = ConnectorError; type Future = Either< as Service>::Future, - FutureResult, ConnectorError>, + FutureResult, ConnectorError>, >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -324,7 +324,7 @@ mod connect_impl { >, { type Request = Connect; - type Response = IoEither, Connection>; + type Response = IoEither, IoConnection>; type Error = ConnectorError; type Future = Either< FutureResult, @@ -342,13 +342,13 @@ mod connect_impl { if let Err(e) = req.validate() { Either::A(err(e)) } else if req.is_secure() { - Either::B(Either::A(InnerConnectorResponseA { - fut: self.tcp_pool.call(req), + Either::B(Either::B(InnerConnectorResponseB { + fut: self.ssl_pool.call(req), _t: PhantomData, })) } else { - Either::B(Either::B(InnerConnectorResponseB { - fut: self.ssl_pool.call(req), + Either::B(Either::A(InnerConnectorResponseA { + fut: self.tcp_pool.call(req), _t: PhantomData, })) } @@ -370,7 +370,7 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { - type Item = IoEither, Connection>; + type Item = IoEither, IoConnection>; type Error = ConnectorError; fn poll(&mut self) -> Poll { @@ -396,7 +396,7 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { - type Item = IoEither, Connection>; + type Item = IoEither, IoConnection>; type Error = ConnectorError; fn poll(&mut self) -> Poll { @@ -413,10 +413,30 @@ pub(crate) enum IoEither { B(Io2), } +impl Connection for IoEither +where + Io1: Connection, + Io2: Connection, +{ + fn close(&mut self) { + match self { + IoEither::A(ref mut io) => io.close(), + IoEither::B(ref mut io) => io.close(), + } + } + + fn release(&mut self) { + match self { + IoEither::A(ref mut io) => io.release(), + IoEither::B(ref mut io) => io.release(), + } + } +} + impl io::Read for IoEither where - Io1: io::Read, - Io2: io::Read, + Io1: Connection, + Io2: Connection, { fn read(&mut self, buf: &mut [u8]) -> io::Result { match self { @@ -428,8 +448,8 @@ where impl AsyncRead for IoEither where - Io1: AsyncRead, - Io2: AsyncRead, + Io1: Connection, + Io2: Connection, { unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { match self { @@ -441,8 +461,8 @@ where impl AsyncWrite for IoEither where - Io1: AsyncWrite, - Io2: AsyncWrite, + Io1: Connection, + Io2: Connection, { fn shutdown(&mut self) -> Poll<(), io::Error> { match self { @@ -468,8 +488,8 @@ where impl io::Write for IoEither where - Io1: io::Write, - Io2: io::Write, + Io1: Connection, + Io2: Connection, { fn flush(&mut self) -> io::Result<()> { match self { diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 24ec8366d..dc6a644dc 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -15,27 +15,32 @@ use body::{BodyType, MessageBody, PayloadStream}; use error::PayloadError; use h1; -pub fn send_request( +pub(crate) fn send_request( head: RequestHead, body: B, connector: &mut T, ) -> impl Future where - T: Service, Error = ConnectorError>, + T: Service, B: MessageBody, - Io: AsyncRead + AsyncWrite + 'static, + I: Connection, { let tp = body.tp(); connector + // connect to the host .call(Connect::new(head.uri.clone())) .from_err() + // create Framed and send reqest .map(|io| Framed::new(io, h1::ClientCodec::default())) .and_then(|framed| framed.send((head, tp).into()).from_err()) + // send request body .and_then(move |framed| match body.tp() { BodyType::None | BodyType::Zero => Either::A(ok(framed)), _ => Either::B(SendBody::new(body, framed)), - }).and_then(|framed| { + }) + // read response and init read body + .and_then(|framed| { framed .into_future() .map_err(|(e, _)| SendRequestError::from(e)) @@ -55,19 +60,20 @@ where }) } -struct SendBody { +/// Future responsible for sending request body to the peer +struct SendBody { body: Option, - framed: Option, h1::ClientCodec>>, + framed: Option>, write_buf: VecDeque>, flushed: bool, } -impl SendBody +impl SendBody where - Io: AsyncRead + AsyncWrite + 'static, + I: AsyncRead + AsyncWrite + 'static, B: MessageBody, { - fn new(body: B, framed: Framed, h1::ClientCodec>) -> Self { + fn new(body: B, framed: Framed) -> Self { SendBody { body: Some(body), framed: Some(framed), @@ -77,12 +83,12 @@ where } } -impl Future for SendBody +impl Future for SendBody where - Io: AsyncRead + AsyncWrite + 'static, + I: Connection, B: MessageBody, { - type Item = Framed, h1::ClientCodec>; + type Item = Framed; type Error = SendRequestError; fn poll(&mut self) -> Poll { @@ -135,7 +141,7 @@ impl Stream for EmptyPayload { } pub(crate) struct Payload { - framed: Option, h1::ClientPayloadCodec>>, + framed: Option>, } impl Payload<()> { @@ -144,15 +150,15 @@ impl Payload<()> { } } -impl Payload { - fn stream(framed: Framed, h1::ClientCodec>) -> PayloadStream { +impl Payload { + fn stream(framed: Framed) -> PayloadStream { Box::new(Payload { framed: Some(framed.map_codec(|codec| codec.into_payload_codec())), }) } } -impl Stream for Payload { +impl Stream for Payload { type Item = Bytes; type Error = PayloadError; @@ -170,11 +176,11 @@ impl Stream for Payload { } } -fn release_connection(framed: Framed, U>) +fn release_connection(framed: Framed) where - T: AsyncRead + AsyncWrite + 'static, + T: Connection, { - let parts = framed.into_parts(); + let mut parts = framed.into_parts(); if parts.read_buf.is_empty() && parts.write_buf.is_empty() { parts.io.release() } else { diff --git a/src/client/pool.rs b/src/client/pool.rs index 25296a6dd..44008f346 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -17,7 +17,7 @@ use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::{sleep, Delay}; use super::connect::Connect; -use super::connection::Connection; +use super::connection::IoConnection; use super::error::ConnectorError; #[derive(Hash, Eq, PartialEq, Clone, Debug)] @@ -89,10 +89,10 @@ where T: Service, { type Request = Connect; - type Response = Connection; + type Response = IoConnection; type Error = ConnectorError; type Future = Either< - FutureResult, ConnectorError>, + FutureResult, ConnectorError>, Either, OpenConnection>, >; @@ -107,7 +107,7 @@ where match self.1.as_ref().borrow_mut().acquire(&key) { Acquire::Acquired(io, created) => { // use existing connection - Either::A(ok(Connection::new( + Either::A(ok(IoConnection::new( io, created, Acquired(key, Some(self.1.clone())), @@ -142,7 +142,7 @@ where { key: Key, token: usize, - rx: oneshot::Receiver, ConnectorError>>, + rx: oneshot::Receiver, ConnectorError>>, inner: Option>>>, } @@ -163,7 +163,7 @@ impl Future for WaitForConnection where Io: AsyncRead + AsyncWrite, { - type Item = Connection; + type Item = IoConnection; type Error = ConnectorError; fn poll(&mut self) -> Poll { @@ -226,7 +226,7 @@ where F: Future, Io: AsyncRead + AsyncWrite, { - type Item = Connection; + type Item = IoConnection; type Error = ConnectorError; fn poll(&mut self) -> Poll { @@ -234,7 +234,7 @@ where Err(err) => Err(err.into()), Ok(Async::Ready((_, io))) => { let _ = self.inner.take(); - Ok(Async::Ready(Connection::new( + Ok(Async::Ready(IoConnection::new( io, Instant::now(), Acquired(self.key.clone(), self.inner.clone()), @@ -251,7 +251,7 @@ where { fut: F, key: Key, - rx: Option, ConnectorError>>>, + rx: Option, ConnectorError>>>, inner: Option>>>, } @@ -262,7 +262,7 @@ where { fn spawn( key: Key, - rx: oneshot::Sender, ConnectorError>>, + rx: oneshot::Sender, ConnectorError>>, inner: Rc>>, fut: F, ) { @@ -308,7 +308,7 @@ where Ok(Async::Ready((_, io))) => { let _ = self.inner.take(); if let Some(rx) = self.rx.take() { - let _ = rx.send(Ok(Connection::new( + let _ = rx.send(Ok(IoConnection::new( io, Instant::now(), Acquired(self.key.clone(), self.inner.clone()), @@ -336,7 +336,7 @@ pub(crate) struct Inner { available: HashMap>>, waiters: Slab<( Connect, - oneshot::Sender, ConnectorError>>, + oneshot::Sender, ConnectorError>>, )>, waiters_queue: IndexSet<(Key, usize)>, task: AtomicTask, @@ -378,7 +378,7 @@ where &mut self, connect: Connect, ) -> ( - oneshot::Receiver, ConnectorError>>, + oneshot::Receiver, ConnectorError>>, usize, ) { let (tx, rx) = oneshot::channel(); @@ -479,7 +479,7 @@ where Acquire::NotAvailable => break, Acquire::Acquired(io, created) => { let (_, tx) = inner.waiters.remove(token); - if let Err(conn) = tx.send(Ok(Connection::new( + if let Err(conn) = tx.send(Ok(IoConnection::new( io, created, Acquired(key.clone(), Some(self.inner.clone())), @@ -546,13 +546,13 @@ impl Acquired where T: AsyncRead + AsyncWrite + 'static, { - pub(crate) fn close(&mut self, conn: Connection) { + pub(crate) fn close(&mut self, conn: IoConnection) { if let Some(inner) = self.1.take() { let (io, _) = conn.into_inner(); inner.as_ref().borrow_mut().release_close(io); } } - pub(crate) fn release(&mut self, conn: Connection) { + pub(crate) fn release(&mut self, conn: IoConnection) { if let Some(inner) = self.1.take() { let (io, created) = conn.into_inner(); inner diff --git a/src/client/request.rs b/src/client/request.rs index 603374135..602abed59 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -7,7 +7,6 @@ use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; use futures::{Future, Stream}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; -use tokio_io::{AsyncRead, AsyncWrite}; use urlcrate::Url; use body::{MessageBody, MessageBodyStream}; @@ -176,13 +175,13 @@ where // Send request /// /// This method returns a future that resolves to a ClientResponse - pub fn send( + pub fn send( self, connector: &mut T, ) -> impl Future where - T: Service, Error = ConnectorError>, - Io: AsyncRead + AsyncWrite + 'static, + T: Service, + I: Connection, { pipeline::send_request(self.head, self.body, connector) } From 3b7bc41418ad51a8e2acea2b7dbbc84a68914a29 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 15 Nov 2018 22:34:29 -0800 Subject: [PATCH 067/208] use RequestHead for Request --- src/client/mod.rs | 2 +- src/client/pipeline.rs | 2 +- src/client/request.rs | 19 +++---------- src/client/response.rs | 16 +++++------ src/h1/client.rs | 8 +++--- src/h1/codec.rs | 4 +-- src/h1/decoder.rs | 14 +++++----- src/h1/encoder.rs | 3 +-- src/request.rs | 61 ++++++++++++++++++++++++++---------------- src/test.rs | 6 ++--- 10 files changed, 68 insertions(+), 67 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index da0cbc670..76c3f8b88 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -12,5 +12,5 @@ pub use self::connect::Connect; pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectorError, InvalidUrlKind, SendRequestError}; -pub use self::request::{ClientRequest, ClientRequestBuilder, RequestHead}; +pub use self::request::{ClientRequest, ClientRequestBuilder}; pub use self::response::ClientResponse; diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index dc6a644dc..26bd1c62a 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -8,12 +8,12 @@ use futures::{Async, Future, Poll, Sink, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; use super::error::{ConnectorError, SendRequestError}; -use super::request::RequestHead; use super::response::ClientResponse; use super::{Connect, Connection}; use body::{BodyType, MessageBody, PayloadStream}; use error::PayloadError; use h1; +use request::RequestHead; pub(crate) fn send_request( head: RequestHead, diff --git a/src/client/request.rs b/src/client/request.rs index 602abed59..c4c7f2f6a 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -16,6 +16,7 @@ use http::{ uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, Uri, Version, }; +use request::RequestHead; use super::response::ClientResponse; use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; @@ -50,21 +51,9 @@ pub struct ClientRequest { body: B, } -pub struct RequestHead { - pub uri: Uri, - pub method: Method, - pub version: Version, - pub headers: HeaderMap, -} - -impl Default for RequestHead { - fn default() -> RequestHead { - RequestHead { - uri: Uri::default(), - method: Method::default(), - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), - } +impl RequestHead { + pub fn clear(&mut self) { + self.headers.clear() } } diff --git a/src/client/response.rs b/src/client/response.rs index e8e63e4f7..56e13fa47 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -4,13 +4,13 @@ use std::rc::Rc; use bytes::Bytes; use futures::{Async, Poll, Stream}; -use http::{HeaderMap, Method, StatusCode, Version}; +use http::{HeaderMap, StatusCode, Version}; use body::PayloadStream; use error::PayloadError; use extensions::Extensions; use httpmessage::HttpMessage; -use request::{Message, MessageFlags, MessagePool}; +use request::{Message, MessageFlags, MessagePool, RequestHead}; use uri::Url; use super::pipeline::Payload; @@ -25,7 +25,7 @@ impl HttpMessage for ClientResponse { type Stream = PayloadStream; fn headers(&self) -> &HeaderMap { - &self.inner.headers + &self.inner.head.headers } #[inline] @@ -49,11 +49,9 @@ impl ClientResponse { ClientResponse { inner: Rc::new(Message { pool, - method: Method::GET, + head: RequestHead::default(), status: StatusCode::OK, url: Url::default(), - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), flags: Cell::new(MessageFlags::empty()), payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), @@ -75,7 +73,7 @@ impl ClientResponse { /// Read the Request Version. #[inline] pub fn version(&self) -> Version { - self.inner().version + self.inner().head.version } /// Get the status from the server. @@ -87,13 +85,13 @@ impl ClientResponse { #[inline] /// Returns Request's headers. pub fn headers(&self) -> &HeaderMap { - &self.inner().headers + &self.inner().head.headers } #[inline] /// Returns mutable Request's headers. pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.inner_mut().headers + &mut self.inner_mut().head.headers } /// Checks if a connection should be kept alive. diff --git a/src/h1/client.rs b/src/h1/client.rs index d2ac20348..81a6f5689 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -8,7 +8,7 @@ use super::decoder::{PayloadDecoder, PayloadItem, PayloadType, ResponseDecoder}; use super::encoder::{RequestEncoder, ResponseLength}; use super::{Message, MessageType}; use body::{Binary, Body, BodyType}; -use client::{ClientResponse, RequestHead}; +use client::ClientResponse; use config::ServiceConfig; use error::{ParseError, PayloadError}; use helpers; @@ -16,7 +16,7 @@ use http::header::{ HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, }; use http::{Method, Version}; -use request::MessagePool; +use request::{MessagePool, RequestHead}; bitflags! { struct Flags: u8 { @@ -187,8 +187,8 @@ impl Decoder for ClientCodec { if let Some((req, payload)) = self.inner.decoder.decode(src)? { self.inner .flags - .set(Flags::HEAD, req.inner.method == Method::HEAD); - self.inner.version = req.inner.version; + .set(Flags::HEAD, req.inner.head.method == Method::HEAD); + self.inner.version = req.inner.head.version; if self.inner.flags.contains(Flags::KEEPALIVE_ENABLED) { self.inner.flags.set(Flags::KEEPALIVE, req.keep_alive()); } diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 44a1b81fc..57e848982 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -261,8 +261,8 @@ impl Decoder for Codec { }) } else if let Some((req, payload)) = self.decoder.decode(src)? { self.flags - .set(Flags::HEAD, req.inner.method == Method::HEAD); - self.version = req.inner.version; + .set(Flags::HEAD, req.inner.head.method == Method::HEAD); + self.version = req.inner.head.version; if self.flags.contains(Flags::KEEPALIVE_ENABLED) { self.flags.set(Flags::KEEPALIVE, req.keep_alive()); } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index f2a3ee3f7..75bd0f7c1 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -152,15 +152,15 @@ impl Decoder for RequestDecoder { _ => (), } - inner.headers.append(name, value); + inner.head.headers.append(name, value); } else { return Err(ParseError::Header); } } inner.url = path; - inner.method = method; - inner.version = version; + inner.head.method = method; + inner.head.version = version; } msg }; @@ -172,7 +172,7 @@ impl Decoder for RequestDecoder { } else if let Some(len) = content_length { // Content-Length PayloadType::Payload(PayloadDecoder::length(len)) - } else if has_upgrade || msg.inner.method == Method::CONNECT { + } else if has_upgrade || msg.inner.head.method == Method::CONNECT { // upgrade(websocket) or connect PayloadType::Stream(PayloadDecoder::eof()) } else if src.len() >= MAX_BUFFER_SIZE { @@ -298,14 +298,14 @@ impl Decoder for ResponseDecoder { _ => (), } - inner.headers.append(name, value); + inner.head.headers.append(name, value); } else { return Err(ParseError::Header); } } inner.status = status; - inner.version = version; + inner.head.version = version; } msg }; @@ -318,7 +318,7 @@ impl Decoder for ResponseDecoder { // Content-Length PayloadType::Payload(PayloadDecoder::length(len)) } else if msg.inner.status == StatusCode::SWITCHING_PROTOCOLS - || msg.inner.method == Method::CONNECT + || msg.inner.head.method == Method::CONNECT { // switching protocol or connect PayloadType::Stream(PayloadDecoder::eof()) diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index caad113d9..7527eeea3 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -9,10 +9,9 @@ use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; use body::{Binary, Body}; -use client::RequestHead; use header::ContentEncoding; use http::Method; -use request::Request; +use request::{Request, RequestHead}; use response::Response; #[derive(Debug)] diff --git a/src/request.rs b/src/request.rs index fb5cb183b..edad4d508 100644 --- a/src/request.rs +++ b/src/request.rs @@ -23,12 +23,28 @@ pub struct Request { pub(crate) inner: Rc, } -pub struct Message { - pub version: Version, - pub status: StatusCode, +pub struct RequestHead { + pub uri: Uri, pub method: Method, - pub url: Url, + pub version: Version, pub headers: HeaderMap, +} + +impl Default for RequestHead { + fn default() -> RequestHead { + RequestHead { + uri: Uri::default(), + method: Method::default(), + version: Version::HTTP_11, + headers: HeaderMap::with_capacity(16), + } + } +} + +pub struct Message { + pub head: RequestHead, + pub url: Url, + pub status: StatusCode, pub extensions: RefCell, pub payload: RefCell>, pub(crate) pool: &'static MessagePool, @@ -39,7 +55,7 @@ impl Message { #[inline] /// Reset request instance pub fn reset(&mut self) { - self.headers.clear(); + self.head.clear(); self.extensions.borrow_mut().clear(); self.flags.set(MessageFlags::empty()); *self.payload.borrow_mut() = None; @@ -50,7 +66,7 @@ impl HttpMessage for Request { type Stream = Payload; fn headers(&self) -> &HeaderMap { - &self.inner.headers + &self.inner.head.headers } #[inline] @@ -74,11 +90,9 @@ impl Request { Request { inner: Rc::new(Message { pool, - method: Method::GET, - status: StatusCode::OK, url: Url::default(), - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), + head: RequestHead::default(), + status: StatusCode::OK, flags: Cell::new(MessageFlags::empty()), payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), @@ -98,27 +112,28 @@ impl Request { Rc::get_mut(&mut self.inner).expect("Multiple copies exist") } - #[inline] - pub fn url(&self) -> &Url { - &self.inner().url - } - - /// Read the Request Uri. + /// Request's uri. #[inline] pub fn uri(&self) -> &Uri { - self.inner().url.uri() + &self.inner().head.uri + } + + /// Mutable reference to the request's uri. + #[inline] + pub fn uri_mut(&mut self) -> &mut Uri { + &mut self.inner_mut().head.uri } /// Read the Request method. #[inline] pub fn method(&self) -> &Method { - &self.inner().method + &self.inner().head.method } /// Read the Request Version. #[inline] pub fn version(&self) -> Version { - self.inner().version + self.inner().head.version } /// The target path of this Request. @@ -130,13 +145,13 @@ impl Request { #[inline] /// Returns Request's headers. pub fn headers(&self) -> &HeaderMap { - &self.inner().headers + &self.inner().head.headers } #[inline] /// Returns mutable Request's headers. pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.inner_mut().headers + &mut self.inner_mut().head.headers } /// Checks if a connection should be kept alive. @@ -159,12 +174,12 @@ impl Request { /// Check if request requires connection upgrade pub fn upgrade(&self) -> bool { - if let Some(conn) = self.inner().headers.get(header::CONNECTION) { + if let Some(conn) = self.inner().head.headers.get(header::CONNECTION) { if let Ok(s) = conn.to_str() { return s.to_lowercase().contains("upgrade"); } } - self.inner().method == Method::CONNECT + self.inner().head.method == Method::CONNECT } #[doc(hidden)] diff --git a/src/test.rs b/src/test.rs index 71145cee8..b0b8dc83d 100644 --- a/src/test.rs +++ b/src/test.rs @@ -391,10 +391,10 @@ impl TestRequest { let mut req = Request::new(); { let inner = req.inner_mut(); - inner.method = method; + inner.head.method = method; inner.url = InnerUrl::new(uri); - inner.version = version; - inner.headers = headers; + inner.head.version = version; + inner.head.headers = headers; *inner.payload.borrow_mut() = payload; } // req.set_cookies(cookies); From 625469f0f421fef4ee7266fd459c6efed31cbdb0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 16 Nov 2018 19:28:07 -0800 Subject: [PATCH 068/208] refactor decoder --- src/client/pipeline.rs | 2 +- src/client/request.rs | 2 +- src/client/response.rs | 64 ++--- src/h1/client.rs | 21 +- src/h1/codec.rs | 13 +- src/h1/decoder.rs | 551 ++++++++++++++++++++--------------------- src/h1/encoder.rs | 3 +- src/h1/mod.rs | 1 - src/lib.rs | 1 + src/message.rs | 163 ++++++++++++ src/request.rs | 148 ++--------- src/test.rs | 2 +- src/uri.rs | 17 +- 13 files changed, 493 insertions(+), 495 deletions(-) create mode 100644 src/message.rs diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 26bd1c62a..17ba93e7c 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -13,7 +13,7 @@ use super::{Connect, Connection}; use body::{BodyType, MessageBody, PayloadStream}; use error::PayloadError; use h1; -use request::RequestHead; +use message::RequestHead; pub(crate) fn send_request( head: RequestHead, diff --git a/src/client/request.rs b/src/client/request.rs index c4c7f2f6a..d3d1544c2 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -16,7 +16,7 @@ use http::{ uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, Uri, Version, }; -use request::RequestHead; +use message::RequestHead; use super::response::ClientResponse; use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; diff --git a/src/client/response.rs b/src/client/response.rs index 56e13fa47..797c88df8 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,6 +1,5 @@ -use std::cell::{Cell, Ref, RefCell, RefMut}; +use std::cell::RefCell; use std::fmt; -use std::rc::Rc; use bytes::Bytes; use futures::{Async, Poll, Stream}; @@ -8,16 +7,14 @@ use http::{HeaderMap, StatusCode, Version}; use body::PayloadStream; use error::PayloadError; -use extensions::Extensions; use httpmessage::HttpMessage; -use request::{Message, MessageFlags, MessagePool, RequestHead}; -use uri::Url; +use message::{MessageFlags, ResponseHead}; use super::pipeline::Payload; /// Client Response pub struct ClientResponse { - pub(crate) inner: Rc, + pub(crate) head: ResponseHead, pub(crate) payload: RefCell>, } @@ -25,7 +22,7 @@ impl HttpMessage for ClientResponse { type Stream = PayloadStream; fn headers(&self) -> &HeaderMap { - &self.inner.head.headers + &self.head.headers } #[inline] @@ -41,75 +38,50 @@ impl HttpMessage for ClientResponse { impl ClientResponse { /// Create new Request instance pub fn new() -> ClientResponse { - ClientResponse::with_pool(MessagePool::pool()) - } - - /// Create new Request instance with pool - pub(crate) fn with_pool(pool: &'static MessagePool) -> ClientResponse { ClientResponse { - inner: Rc::new(Message { - pool, - head: RequestHead::default(), - status: StatusCode::OK, - url: Url::default(), - flags: Cell::new(MessageFlags::empty()), - payload: RefCell::new(None), - extensions: RefCell::new(Extensions::new()), - }), + head: ResponseHead::default(), payload: RefCell::new(None), } } #[inline] - pub(crate) fn inner(&self) -> &Message { - self.inner.as_ref() + pub(crate) fn head(&self) -> &ResponseHead { + &self.head } #[inline] - pub(crate) fn inner_mut(&mut self) -> &mut Message { - Rc::get_mut(&mut self.inner).expect("Multiple copies exist") + pub(crate) fn head_mut(&mut self) -> &mut ResponseHead { + &mut self.head } /// Read the Request Version. #[inline] pub fn version(&self) -> Version { - self.inner().head.version + self.head().version.clone().unwrap() } /// Get the status from the server. #[inline] pub fn status(&self) -> StatusCode { - self.inner().status + self.head().status } #[inline] /// Returns Request's headers. pub fn headers(&self) -> &HeaderMap { - &self.inner().head.headers + &self.head().headers } #[inline] /// Returns mutable Request's headers. pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.inner_mut().head.headers + &mut self.head_mut().headers } /// Checks if a connection should be kept alive. #[inline] pub fn keep_alive(&self) -> bool { - self.inner().flags.get().contains(MessageFlags::KEEPALIVE) - } - - /// Request extensions - #[inline] - pub fn extensions(&self) -> Ref { - self.inner().extensions.borrow() - } - - /// Mutable reference to a the request's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut { - self.inner().extensions.borrow_mut() + self.head().flags.contains(MessageFlags::KEEPALIVE) } } @@ -126,14 +98,6 @@ impl Stream for ClientResponse { } } -impl Drop for ClientResponse { - fn drop(&mut self) { - if Rc::strong_count(&self.inner) == 1 { - self.inner.pool.release(self.inner.clone()); - } - } -} - impl fmt::Debug for ClientResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status(),)?; diff --git a/src/h1/client.rs b/src/h1/client.rs index 81a6f5689..8d4051d3e 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -4,7 +4,7 @@ use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; -use super::decoder::{PayloadDecoder, PayloadItem, PayloadType, ResponseDecoder}; +use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; use super::encoder::{RequestEncoder, ResponseLength}; use super::{Message, MessageType}; use body::{Binary, Body, BodyType}; @@ -16,7 +16,7 @@ use http::header::{ HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, }; use http::{Method, Version}; -use request::{MessagePool, RequestHead}; +use message::{MessagePool, RequestHead}; bitflags! { struct Flags: u8 { @@ -42,7 +42,7 @@ pub struct ClientPayloadCodec { struct ClientCodecInner { config: ServiceConfig, - decoder: ResponseDecoder, + decoder: MessageDecoder, payload: Option, version: Version, @@ -63,11 +63,6 @@ impl ClientCodec { /// /// `keepalive_enabled` how response `connection` header get generated. pub fn new(config: ServiceConfig) -> Self { - ClientCodec::with_pool(MessagePool::pool(), config) - } - - /// Create HTTP/1 codec with request's pool - pub(crate) fn with_pool(pool: &'static MessagePool, config: ServiceConfig) -> Self { let flags = if config.keep_alive_enabled() { Flags::KEEPALIVE_ENABLED } else { @@ -76,7 +71,7 @@ impl ClientCodec { ClientCodec { inner: ClientCodecInner { config, - decoder: ResponseDecoder::with_pool(pool), + decoder: MessageDecoder::default(), payload: None, version: Version::HTTP_11, @@ -185,10 +180,10 @@ impl Decoder for ClientCodec { debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set"); if let Some((req, payload)) = self.inner.decoder.decode(src)? { - self.inner - .flags - .set(Flags::HEAD, req.inner.head.method == Method::HEAD); - self.inner.version = req.inner.head.version; + // self.inner + // .flags + // .set(Flags::HEAD, req.head.method == Method::HEAD); + // self.inner.version = req.head.version; if self.inner.flags.contains(Flags::KEEPALIVE_ENABLED) { self.inner.flags.set(Flags::KEEPALIVE, req.keep_alive()); } diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 57e848982..c1c6091de 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -5,7 +5,7 @@ use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; -use super::decoder::{PayloadDecoder, PayloadItem, PayloadType, RequestDecoder}; +use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; use super::encoder::{ResponseEncoder, ResponseLength}; use super::{Message, MessageType}; use body::{Binary, Body}; @@ -14,7 +14,7 @@ use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{Method, Version}; -use request::{MessagePool, Request}; +use request::Request; use response::Response; bitflags! { @@ -32,7 +32,7 @@ const AVERAGE_HEADER_SIZE: usize = 30; /// HTTP/1 Codec pub struct Codec { config: ServiceConfig, - decoder: RequestDecoder, + decoder: MessageDecoder, payload: Option, version: Version, @@ -59,11 +59,6 @@ impl Codec { /// /// `keepalive_enabled` how response `connection` header get generated. pub fn new(config: ServiceConfig) -> Self { - Codec::with_pool(MessagePool::pool(), config) - } - - /// Create HTTP/1 codec with request's pool - pub(crate) fn with_pool(pool: &'static MessagePool, config: ServiceConfig) -> Self { let flags = if config.keep_alive_enabled() { Flags::KEEPALIVE_ENABLED } else { @@ -71,7 +66,7 @@ impl Codec { }; Codec { config, - decoder: RequestDecoder::with_pool(pool), + decoder: MessageDecoder::default(), payload: None, version: Version::HTTP_11, diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 75bd0f7c1..fe2aa707a 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -1,3 +1,4 @@ +use std::marker::PhantomData; use std::{io, mem}; use bytes::{Bytes, BytesMut}; @@ -8,327 +9,305 @@ use tokio_codec::Decoder; use client::ClientResponse; use error::ParseError; use http::header::{HeaderName, HeaderValue}; -use http::{header, HttpTryFrom, Method, StatusCode, Uri, Version}; -use request::{MessageFlags, MessagePool, Request}; -use uri::Url; +use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version}; +use message::MessageFlags; +use request::Request; const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; -/// Client request decoder -pub struct RequestDecoder(&'static MessagePool); - -/// Server response decoder -pub struct ResponseDecoder(&'static MessagePool); +/// Incoming messagd decoder +pub(crate) struct MessageDecoder(PhantomData); /// Incoming request type -pub enum PayloadType { +pub(crate) enum PayloadType { None, Payload(PayloadDecoder), Stream(PayloadDecoder), } -impl RequestDecoder { - pub(crate) fn with_pool(pool: &'static MessagePool) -> RequestDecoder { - RequestDecoder(pool) +impl Default for MessageDecoder { + fn default() -> Self { + MessageDecoder(PhantomData) } } -impl Default for RequestDecoder { - fn default() -> RequestDecoder { - RequestDecoder::with_pool(MessagePool::pool()) - } -} - -impl Decoder for RequestDecoder { - type Item = (Request, PayloadType); +impl Decoder for MessageDecoder { + type Item = (T, PayloadType); type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - // Parse http message + T::decode(src) + } +} + +pub(crate) enum PayloadLength { + None, + Chunked, + Upgrade, + Length(u64), +} + +pub(crate) trait MessageTypeDecoder: Sized { + fn keep_alive(&mut self); + + fn headers_mut(&mut self) -> &mut HeaderMap; + + fn decode(src: &mut BytesMut) -> Result, ParseError>; + + fn process_headers( + &mut self, + slice: &Bytes, + version: Version, + raw_headers: &[HeaderIndex], + ) -> Result { + let mut ka = version != Version::HTTP_10; let mut has_upgrade = false; let mut chunked = false; let mut content_length = None; - let msg = { - // Unsafe: we read only this data only after httparse parses headers into. - // performance bump for pipeline benchmarks. - let mut headers: [HeaderIndex; MAX_HEADERS] = + { + let headers = self.headers_mut(); + + for idx in raw_headers.iter() { + if let Ok(name) = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) + { + // Unsafe: httparse check header value for valid utf-8 + let value = unsafe { + HeaderValue::from_shared_unchecked( + slice.slice(idx.value.0, idx.value.1), + ) + }; + match name { + header::CONTENT_LENGTH => { + if let Ok(s) = value.to_str() { + if let Ok(len) = s.parse::() { + content_length = Some(len); + } else { + debug!("illegal Content-Length: {:?}", s); + return Err(ParseError::Header); + } + } else { + debug!("illegal Content-Length: {:?}", value); + return Err(ParseError::Header); + } + } + // transfer-encoding + header::TRANSFER_ENCODING => { + if let Ok(s) = value.to_str() { + chunked = s.to_lowercase().contains("chunked"); + } else { + return Err(ParseError::Header); + } + } + // connection keep-alive state + header::CONNECTION => { + ka = if let Ok(conn) = value.to_str() { + if version == Version::HTTP_10 + && conn.contains("keep-alive") + { + true + } else { + version == Version::HTTP_11 && !(conn + .contains("close") + || conn.contains("upgrade")) + } + } else { + false + } + } + header::UPGRADE => { + has_upgrade = true; + // check content-length, some clients (dart) + // sends "content-length: 0" with websocket upgrade + if let Ok(val) = value.to_str() { + if val == "websocket" { + content_length = None; + } + } + } + _ => (), + } + + headers.append(name, value); + } else { + return Err(ParseError::Header); + } + } + } + + if ka { + self.keep_alive(); + } + + if chunked { + Ok(PayloadLength::Chunked) + } else if let Some(len) = content_length { + Ok(PayloadLength::Length(len)) + } else if has_upgrade { + Ok(PayloadLength::Upgrade) + } else { + Ok(PayloadLength::None) + } + } +} + +impl MessageTypeDecoder for Request { + fn keep_alive(&mut self) { + self.inner_mut().flags.set(MessageFlags::KEEPALIVE); + } + + fn headers_mut(&mut self) -> &mut HeaderMap { + self.headers_mut() + } + + fn decode(src: &mut BytesMut) -> Result, ParseError> { + // Unsafe: we read only this data only after httparse parses headers into. + // performance bump for pipeline benchmarks. + let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; + + let (len, method, uri, version, headers_len) = { + let mut parsed: [httparse::Header; MAX_HEADERS] = unsafe { mem::uninitialized() }; - let (len, method, path, version, headers_len) = { - let mut parsed: [httparse::Header; MAX_HEADERS] = - unsafe { mem::uninitialized() }; - - let mut req = httparse::Request::new(&mut parsed); - match req.parse(src)? { - httparse::Status::Complete(len) => { - let method = Method::from_bytes(req.method.unwrap().as_bytes()) - .map_err(|_| ParseError::Method)?; - let path = Url::new(Uri::try_from(req.path.unwrap())?); - let version = if req.version.unwrap() == 1 { - Version::HTTP_11 - } else { - Version::HTTP_10 - }; - HeaderIndex::record(src, req.headers, &mut headers); - - (len, method, path, version, req.headers.len()) - } - httparse::Status::Partial => return Ok(None), - } - }; - - let slice = src.split_to(len).freeze(); - - // convert headers - let mut msg = MessagePool::get_request(self.0); - { - let inner = msg.inner_mut(); - inner - .flags - .get_mut() - .set(MessageFlags::KEEPALIVE, version != Version::HTTP_10); - - for idx in headers[..headers_len].iter() { - if let Ok(name) = - HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) - { - // Unsafe: httparse check header value for valid utf-8 - let value = unsafe { - HeaderValue::from_shared_unchecked( - slice.slice(idx.value.0, idx.value.1), - ) - }; - match name { - header::CONTENT_LENGTH => { - if let Ok(s) = value.to_str() { - if let Ok(len) = s.parse::() { - content_length = Some(len); - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } - // transfer-encoding - header::TRANSFER_ENCODING => { - if let Ok(s) = value.to_str() { - chunked = s.to_lowercase().contains("chunked"); - } else { - return Err(ParseError::Header); - } - } - // connection keep-alive state - header::CONNECTION => { - let ka = if let Ok(conn) = value.to_str() { - if version == Version::HTTP_10 - && conn.contains("keep-alive") - { - true - } else { - version == Version::HTTP_11 && !(conn - .contains("close") - || conn.contains("upgrade")) - } - } else { - false - }; - inner.flags.get_mut().set(MessageFlags::KEEPALIVE, ka); - } - header::UPGRADE => { - has_upgrade = true; - // check content-length, some clients (dart) - // sends "content-length: 0" with websocket upgrade - if let Ok(val) = value.to_str() { - if val == "websocket" { - content_length = None; - } - } - } - _ => (), - } - - inner.head.headers.append(name, value); + let mut req = httparse::Request::new(&mut parsed); + match req.parse(src)? { + httparse::Status::Complete(len) => { + let method = Method::from_bytes(req.method.unwrap().as_bytes()) + .map_err(|_| ParseError::Method)?; + let uri = Uri::try_from(req.path.unwrap())?; + let version = if req.version.unwrap() == 1 { + Version::HTTP_11 } else { - return Err(ParseError::Header); - } - } + Version::HTTP_10 + }; + HeaderIndex::record(src, req.headers, &mut headers); - inner.url = path; - inner.head.method = method; - inner.head.version = version; + (len, method, uri, version, req.headers.len()) + } + httparse::Status::Partial => return Ok(None), } - msg }; + // convert headers + let mut msg = Request::new(); + + let len = msg.process_headers( + &src.split_to(len).freeze(), + version, + &headers[..headers_len], + )?; + // https://tools.ietf.org/html/rfc7230#section-3.3.3 - let decoder = if chunked { - // Chunked encoding - PayloadType::Payload(PayloadDecoder::chunked()) - } else if let Some(len) = content_length { - // Content-Length - PayloadType::Payload(PayloadDecoder::length(len)) - } else if has_upgrade || msg.inner.head.method == Method::CONNECT { - // upgrade(websocket) or connect - PayloadType::Stream(PayloadDecoder::eof()) - } else if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ParseError::TooLarge); - } else { - PayloadType::None + let decoder = match len { + PayloadLength::Chunked => { + // Chunked encoding + PayloadType::Payload(PayloadDecoder::chunked()) + } + PayloadLength::Length(len) => { + // Content-Length + PayloadType::Payload(PayloadDecoder::length(len)) + } + PayloadLength::Upgrade => { + // upgrade(websocket) or connect + PayloadType::Stream(PayloadDecoder::eof()) + } + PayloadLength::None => { + if method == Method::CONNECT { + // upgrade(websocket) or connect + PayloadType::Stream(PayloadDecoder::eof()) + } else if src.len() >= MAX_BUFFER_SIZE { + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + return Err(ParseError::TooLarge); + } else { + PayloadType::None + } + } }; + { + let inner = msg.inner_mut(); + inner.url.update(&uri); + inner.head.uri = uri; + inner.head.method = method; + inner.head.version = version; + } + Ok(Some((msg, decoder))) } } -impl ResponseDecoder { - pub(crate) fn with_pool(pool: &'static MessagePool) -> ResponseDecoder { - ResponseDecoder(pool) +impl MessageTypeDecoder for ClientResponse { + fn keep_alive(&mut self) { + self.head.flags.insert(MessageFlags::KEEPALIVE); } -} -impl Default for ResponseDecoder { - fn default() -> ResponseDecoder { - ResponseDecoder::with_pool(MessagePool::pool()) + fn headers_mut(&mut self) -> &mut HeaderMap { + self.headers_mut() } -} -impl Decoder for ResponseDecoder { - type Item = (ClientResponse, PayloadType); - type Error = ParseError; + fn decode(src: &mut BytesMut) -> Result, ParseError> { + // Unsafe: we read only this data only after httparse parses headers into. + // performance bump for pipeline benchmarks. + let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - // Parse http message - let mut chunked = false; - let mut content_length = None; - - let msg = { - // Unsafe: we read only this data only after httparse parses headers into. - // performance bump for pipeline benchmarks. - let mut headers: [HeaderIndex; MAX_HEADERS] = + let (len, version, status, headers_len) = { + let mut parsed: [httparse::Header; MAX_HEADERS] = unsafe { mem::uninitialized() }; - let (len, version, status, headers_len) = { - let mut parsed: [httparse::Header; MAX_HEADERS] = - unsafe { mem::uninitialized() }; - - let mut res = httparse::Response::new(&mut parsed); - match res.parse(src)? { - httparse::Status::Complete(len) => { - let version = if res.version.unwrap() == 1 { - Version::HTTP_11 - } else { - Version::HTTP_10 - }; - let status = StatusCode::from_u16(res.code.unwrap()) - .map_err(|_| ParseError::Status)?; - HeaderIndex::record(src, res.headers, &mut headers); - - (len, version, status, res.headers.len()) - } - httparse::Status::Partial => return Ok(None), - } - }; - - let slice = src.split_to(len).freeze(); - - // convert headers - let mut msg = MessagePool::get_response(self.0); - { - let inner = msg.inner_mut(); - inner - .flags - .get_mut() - .set(MessageFlags::KEEPALIVE, version != Version::HTTP_10); - - for idx in headers[..headers_len].iter() { - if let Ok(name) = - HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) - { - // Unsafe: httparse check header value for valid utf-8 - let value = unsafe { - HeaderValue::from_shared_unchecked( - slice.slice(idx.value.0, idx.value.1), - ) - }; - match name { - header::CONTENT_LENGTH => { - if let Ok(s) = value.to_str() { - if let Ok(len) = s.parse::() { - content_length = Some(len); - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } - // transfer-encoding - header::TRANSFER_ENCODING => { - if let Ok(s) = value.to_str() { - chunked = s.to_lowercase().contains("chunked"); - } else { - return Err(ParseError::Header); - } - } - // connection keep-alive state - header::CONNECTION => { - let ka = if let Ok(conn) = value.to_str() { - if version == Version::HTTP_10 - && conn.contains("keep-alive") - { - true - } else { - version == Version::HTTP_11 && !(conn - .contains("close") - || conn.contains("upgrade")) - } - } else { - false - }; - inner.flags.get_mut().set(MessageFlags::KEEPALIVE, ka); - } - _ => (), - } - - inner.head.headers.append(name, value); + let mut res = httparse::Response::new(&mut parsed); + match res.parse(src)? { + httparse::Status::Complete(len) => { + let version = if res.version.unwrap() == 1 { + Version::HTTP_11 } else { - return Err(ParseError::Header); - } - } + Version::HTTP_10 + }; + let status = StatusCode::from_u16(res.code.unwrap()) + .map_err(|_| ParseError::Status)?; + HeaderIndex::record(src, res.headers, &mut headers); - inner.status = status; - inner.head.version = version; + (len, version, status, res.headers.len()) + } + httparse::Status::Partial => return Ok(None), } - msg }; + let mut msg = ClientResponse::new(); + + // convert headers + let len = msg.process_headers( + &src.split_to(len).freeze(), + version, + &headers[..headers_len], + )?; + // https://tools.ietf.org/html/rfc7230#section-3.3.3 - let decoder = if chunked { - // Chunked encoding - PayloadType::Payload(PayloadDecoder::chunked()) - } else if let Some(len) = content_length { - // Content-Length - PayloadType::Payload(PayloadDecoder::length(len)) - } else if msg.inner.status == StatusCode::SWITCHING_PROTOCOLS - || msg.inner.head.method == Method::CONNECT - { - // switching protocol or connect - PayloadType::Stream(PayloadDecoder::eof()) - } else if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ParseError::TooLarge); - } else { - PayloadType::None + let decoder = match len { + PayloadLength::Chunked => { + // Chunked encoding + PayloadType::Payload(PayloadDecoder::chunked()) + } + PayloadLength::Length(len) => { + // Content-Length + PayloadType::Payload(PayloadDecoder::length(len)) + } + _ => { + if status == StatusCode::SWITCHING_PROTOCOLS { + // switching protocol or connect + PayloadType::Stream(PayloadDecoder::eof()) + } else if src.len() >= MAX_BUFFER_SIZE { + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + return Err(ParseError::TooLarge); + } else { + PayloadType::None + } + } }; + msg.head.status = status; + msg.head.version = Some(version); + Ok(Some((msg, decoder))) } } @@ -690,7 +669,7 @@ mod tests { macro_rules! parse_ready { ($e:expr) => {{ - match RequestDecoder::default().decode($e) { + match MessageDecoder::::default().decode($e) { Ok(Some((msg, _))) => msg, Ok(_) => unreachable!("Eof during parsing http request"), Err(err) => unreachable!("Error during parsing http request: {:?}", err), @@ -700,7 +679,7 @@ mod tests { macro_rules! expect_parse_err { ($e:expr) => {{ - match RequestDecoder::default().decode($e) { + match MessageDecoder::::default().decode($e) { Err(err) => match err { ParseError::Io(_) => unreachable!("Parse error expected"), _ => (), @@ -779,7 +758,7 @@ mod tests { fn test_parse() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); match reader.decode(&mut buf) { Ok(Some((req, _))) => { assert_eq!(req.version(), Version::HTTP_11); @@ -794,7 +773,7 @@ mod tests { fn test_parse_partial() { let mut buf = BytesMut::from("PUT /test HTTP/1"); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b".1\r\n\r\n"); @@ -808,7 +787,7 @@ mod tests { fn test_parse_post() { let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); assert_eq!(req.version(), Version::HTTP_10); assert_eq!(*req.method(), Method::POST); @@ -820,7 +799,7 @@ mod tests { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); let mut pl = pl.unwrap(); assert_eq!(req.version(), Version::HTTP_11); @@ -837,7 +816,7 @@ mod tests { let mut buf = BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); let mut pl = pl.unwrap(); assert_eq!(req.version(), Version::HTTP_11); @@ -852,7 +831,7 @@ mod tests { #[test] fn test_parse_partial_eof() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b"\r\n"); @@ -866,7 +845,7 @@ mod tests { fn test_headers_split_field() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); assert!{ reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"t"); @@ -890,7 +869,7 @@ mod tests { Set-Cookie: c1=cookie1\r\n\ Set-Cookie: c2=cookie2\r\n\r\n", ); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); let val: Vec<_> = req @@ -1090,7 +1069,7 @@ mod tests { upgrade: websocket\r\n\r\n\ some raw data", ); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); assert!(!req.keep_alive()); assert!(req.upgrade()); @@ -1139,7 +1118,7 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n", ); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); let mut pl = pl.unwrap(); assert!(req.chunked().unwrap()); @@ -1162,7 +1141,7 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n", ); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); let mut pl = pl.unwrap(); assert!(req.chunked().unwrap()); @@ -1193,7 +1172,7 @@ mod tests { transfer-encoding: chunked\r\n\r\n", ); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); let mut pl = pl.unwrap(); assert!(req.chunked().unwrap()); @@ -1238,7 +1217,7 @@ mod tests { transfer-encoding: chunked\r\n\r\n"[..], ); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (msg, pl) = reader.decode(&mut buf).unwrap().unwrap(); let mut pl = pl.unwrap(); assert!(msg.chunked().unwrap()); diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 7527eeea3..de45351d1 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -11,7 +11,8 @@ use http::{StatusCode, Version}; use body::{Binary, Body}; use header::ContentEncoding; use http::Method; -use request::{Request, RequestHead}; +use message::RequestHead; +use request::Request; use response::Response; #[derive(Debug)] diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 818387004..395d66199 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -11,7 +11,6 @@ mod service; pub use self::client::{ClientCodec, ClientPayloadCodec}; pub use self::codec::Codec; -pub use self::decoder::{PayloadDecoder, RequestDecoder}; pub use self::dispatcher::Dispatcher; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; diff --git a/src/lib.rs b/src/lib.rs index 32369d167..f64876e31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,6 +117,7 @@ mod header; mod httpcodes; mod httpmessage; mod json; +mod message; mod payload; mod request; mod response; diff --git a/src/message.rs b/src/message.rs new file mode 100644 index 000000000..b38c2f801 --- /dev/null +++ b/src/message.rs @@ -0,0 +1,163 @@ +use std::cell::{Cell, RefCell}; +use std::collections::VecDeque; +use std::rc::Rc; + +use http::{HeaderMap, Method, StatusCode, Uri, Version}; + +use extensions::Extensions; +use payload::Payload; +use uri::Url; + +#[doc(hidden)] +pub trait Head: Default + 'static { + fn clear(&mut self); + + fn pool() -> &'static MessagePool; +} + +bitflags! { + pub(crate) struct MessageFlags: u8 { + const KEEPALIVE = 0b0000_0001; + } +} + +pub struct RequestHead { + pub uri: Uri, + pub method: Method, + pub version: Version, + pub headers: HeaderMap, + pub(crate) flags: MessageFlags, +} + +impl Default for RequestHead { + fn default() -> RequestHead { + RequestHead { + uri: Uri::default(), + method: Method::default(), + version: Version::HTTP_11, + headers: HeaderMap::with_capacity(16), + flags: MessageFlags::empty(), + } + } +} + +impl Head for RequestHead { + fn clear(&mut self) { + self.headers.clear(); + self.flags = MessageFlags::empty(); + } + + fn pool() -> &'static MessagePool { + REQUEST_POOL.with(|p| *p) + } +} + +pub struct ResponseHead { + pub version: Option, + pub status: StatusCode, + pub headers: HeaderMap, + pub reason: Option<&'static str>, + pub(crate) flags: MessageFlags, +} + +impl Default for ResponseHead { + fn default() -> ResponseHead { + ResponseHead { + version: None, + status: StatusCode::OK, + headers: HeaderMap::with_capacity(16), + reason: None, + flags: MessageFlags::empty(), + } + } +} + +impl Head for ResponseHead { + fn clear(&mut self) { + self.headers.clear(); + self.flags = MessageFlags::empty(); + } + + fn pool() -> &'static MessagePool { + RESPONSE_POOL.with(|p| *p) + } +} + +pub struct Message { + pub head: T, + pub url: Url, + pub status: StatusCode, + pub extensions: RefCell, + pub payload: RefCell>, + pub(crate) pool: &'static MessagePool, + pub(crate) flags: Cell, +} + +impl Message { + #[inline] + /// Reset request instance + pub fn reset(&mut self) { + self.head.clear(); + self.extensions.borrow_mut().clear(); + self.flags.set(MessageFlags::empty()); + *self.payload.borrow_mut() = None; + } +} + +impl Default for Message { + fn default() -> Self { + Message { + pool: T::pool(), + url: Url::default(), + head: T::default(), + status: StatusCode::OK, + flags: Cell::new(MessageFlags::empty()), + payload: RefCell::new(None), + extensions: RefCell::new(Extensions::new()), + } + } +} + +#[doc(hidden)] +/// Request's objects pool +pub struct MessagePool(RefCell>>>); + +thread_local!(static REQUEST_POOL: &'static MessagePool = MessagePool::::create()); +thread_local!(static RESPONSE_POOL: &'static MessagePool = MessagePool::::create()); + +impl MessagePool { + /// Get default request's pool + pub fn pool() -> &'static MessagePool { + REQUEST_POOL.with(|p| *p) + } + + /// Get Request object + #[inline] + pub fn get_message() -> Rc> { + REQUEST_POOL.with(|pool| { + if let Some(mut msg) = pool.0.borrow_mut().pop_front() { + if let Some(r) = Rc::get_mut(&mut msg) { + r.reset(); + } + return msg; + } + Rc::new(Message::default()) + }) + } +} + +impl MessagePool { + fn create() -> &'static MessagePool { + let pool = MessagePool(RefCell::new(VecDeque::with_capacity(128))); + Box::leak(Box::new(pool)) + } + + #[inline] + /// Release request instance + pub(crate) fn release(&self, msg: Rc>) { + let v = &mut self.0.borrow_mut(); + if v.len() < 128 { + v.push_front(msg); + } + } +} diff --git a/src/request.rs b/src/request.rs index edad4d508..1e191047a 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,65 +1,18 @@ -use std::cell::{Cell, Ref, RefCell, RefMut}; -use std::collections::VecDeque; +use std::cell::{Ref, RefMut}; use std::fmt; use std::rc::Rc; -use http::{header, HeaderMap, Method, StatusCode, Uri, Version}; +use http::{header, HeaderMap, Method, Uri, Version}; -use client::ClientResponse; use extensions::Extensions; use httpmessage::HttpMessage; use payload::Payload; -use uri::Url; -bitflags! { - pub(crate) struct MessageFlags: u8 { - const KEEPALIVE = 0b0000_0001; - const CONN_INFO = 0b0000_0010; - } -} +use message::{Message, MessageFlags, MessagePool, RequestHead}; /// Request pub struct Request { - pub(crate) inner: Rc, -} - -pub struct RequestHead { - pub uri: Uri, - pub method: Method, - pub version: Version, - pub headers: HeaderMap, -} - -impl Default for RequestHead { - fn default() -> RequestHead { - RequestHead { - uri: Uri::default(), - method: Method::default(), - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), - } - } -} - -pub struct Message { - pub head: RequestHead, - pub url: Url, - pub status: StatusCode, - pub extensions: RefCell, - pub payload: RefCell>, - pub(crate) pool: &'static MessagePool, - pub(crate) flags: Cell, -} - -impl Message { - #[inline] - /// Reset request instance - pub fn reset(&mut self) { - self.head.clear(); - self.extensions.borrow_mut().clear(); - self.flags.set(MessageFlags::empty()); - *self.payload.borrow_mut() = None; - } + pub(crate) inner: Rc>, } impl HttpMessage for Request { @@ -82,33 +35,35 @@ impl HttpMessage for Request { impl Request { /// Create new Request instance pub fn new() -> Request { - Request::with_pool(MessagePool::pool()) - } - - /// Create new Request instance with pool - pub(crate) fn with_pool(pool: &'static MessagePool) -> Request { Request { - inner: Rc::new(Message { - pool, - url: Url::default(), - head: RequestHead::default(), - status: StatusCode::OK, - flags: Cell::new(MessageFlags::empty()), - payload: RefCell::new(None), - extensions: RefCell::new(Extensions::new()), - }), + inner: MessagePool::get_message(), } } + // /// Create new Request instance with pool + // pub(crate) fn with_pool(pool: &'static MessagePool) -> Request { + // Request { + // inner: Rc::new(Message { + // pool, + // url: Url::default(), + // head: RequestHead::default(), + // status: StatusCode::OK, + // flags: Cell::new(MessageFlags::empty()), + // payload: RefCell::new(None), + // extensions: RefCell::new(Extensions::new()), + // }), + // } + // } + #[inline] #[doc(hidden)] - pub fn inner(&self) -> &Message { + pub fn inner(&self) -> &Message { self.inner.as_ref() } #[inline] #[doc(hidden)] - pub fn inner_mut(&mut self) -> &mut Message { + pub fn inner_mut(&mut self) -> &mut Message { Rc::get_mut(&mut self.inner).expect("Multiple copies exist") } @@ -139,7 +94,11 @@ impl Request { /// The target path of this Request. #[inline] pub fn path(&self) -> &str { - self.inner().url.path() + if let Some(path) = self.inner().url.path() { + path + } else { + self.inner().head.uri.path() + } } #[inline] @@ -219,56 +178,3 @@ impl fmt::Debug for Request { Ok(()) } } - -/// Request's objects pool -pub(crate) struct MessagePool(RefCell>>); - -thread_local!(static POOL: &'static MessagePool = MessagePool::create()); - -impl MessagePool { - fn create() -> &'static MessagePool { - let pool = MessagePool(RefCell::new(VecDeque::with_capacity(128))); - Box::leak(Box::new(pool)) - } - - /// Get default request's pool - pub fn pool() -> &'static MessagePool { - POOL.with(|p| *p) - } - - /// Get Request object - #[inline] - pub fn get_request(pool: &'static MessagePool) -> Request { - if let Some(mut msg) = pool.0.borrow_mut().pop_front() { - if let Some(r) = Rc::get_mut(&mut msg) { - r.reset(); - } - return Request { inner: msg }; - } - Request::with_pool(pool) - } - - /// Get Client Response object - #[inline] - pub fn get_response(pool: &'static MessagePool) -> ClientResponse { - if let Some(mut msg) = pool.0.borrow_mut().pop_front() { - if let Some(r) = Rc::get_mut(&mut msg) { - r.reset(); - } - return ClientResponse { - inner: msg, - payload: RefCell::new(None), - }; - } - ClientResponse::with_pool(pool) - } - - #[inline] - /// Release request instance - pub(crate) fn release(&self, msg: Rc) { - let v = &mut self.0.borrow_mut(); - if v.len() < 128 { - v.push_front(msg); - } - } -} diff --git a/src/test.rs b/src/test.rs index b0b8dc83d..439749343 100644 --- a/src/test.rs +++ b/src/test.rs @@ -392,7 +392,7 @@ impl TestRequest { { let inner = req.inner_mut(); inner.head.method = method; - inner.url = InnerUrl::new(uri); + inner.url = InnerUrl::new(&uri); inner.head.version = version; inner.head.headers = headers; *inner.payload.borrow_mut() = payload; diff --git a/src/uri.rs b/src/uri.rs index 6edd220ce..89f6d3b1e 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -37,27 +37,22 @@ lazy_static! { #[derive(Default, Clone, Debug)] pub struct Url { - uri: Uri, path: Option>, } impl Url { - pub fn new(uri: Uri) -> Url { + pub fn new(uri: &Uri) -> Url { let path = DEFAULT_QUOTER.requote(uri.path().as_bytes()); - Url { uri, path } + Url { path } } - pub fn uri(&self) -> &Uri { - &self.uri + pub(crate) fn update(&mut self, uri: &Uri) { + self.path = DEFAULT_QUOTER.requote(uri.path().as_bytes()); } - pub fn path(&self) -> &str { - if let Some(ref s) = self.path { - s - } else { - self.uri.path() - } + pub fn path(&self) -> Option<&str> { + self.path.as_ref().map(|s| s.as_str()) } } From aa20e2670d44b893ae47ad01c61fc1fd3b65dcce Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 16 Nov 2018 21:09:33 -0800 Subject: [PATCH 069/208] refactor h1 dispatcher --- src/h1/client.rs | 69 +++++++-- src/h1/decoder.rs | 90 +++++------- src/h1/dispatcher.rs | 338 ++++++++++++++++++++++--------------------- 3 files changed, 267 insertions(+), 230 deletions(-) diff --git a/src/h1/client.rs b/src/h1/client.rs index 8d4051d3e..f871cb338 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -125,6 +125,15 @@ impl ClientPayloadCodec { } } +fn prn_version(ver: Version) -> &'static str { + match ver { + Version::HTTP_09 => "HTTP/0.9", + Version::HTTP_10 => "HTTP/1.0", + Version::HTTP_11 => "HTTP/1.1", + Version::HTTP_2 => "HTTP/2.0", + } +} + impl ClientCodecInner { fn encode_response( &mut self, @@ -135,33 +144,63 @@ impl ClientCodecInner { // render message { // status line - writeln!( + write!( Writer(buffer), - "{} {} {:?}\r", + "{} {} {}", msg.method, msg.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), - msg.version + prn_version(msg.version) ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; // write headers buffer.reserve(msg.headers.len() * AVERAGE_HEADER_SIZE); - for (key, value) in &msg.headers { - let v = value.as_ref(); - let k = key.as_str().as_bytes(); - buffer.reserve(k.len() + v.len() + 4); - buffer.put_slice(k); - buffer.put_slice(b": "); - buffer.put_slice(v); - buffer.put_slice(b"\r\n"); - // Connection upgrade - if key == UPGRADE { - self.flags.insert(Flags::UPGRADE); + // content length + let mut len_is_set = true; + match btype { + BodyType::Sized(len) => { + buffer.extend_from_slice(b"\r\ncontent-length: "); + write!(buffer.writer(), "{}", len)?; + buffer.extend_from_slice(b"\r\n"); } + BodyType::Unsized => { + buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") + } + BodyType::Zero => { + len_is_set = false; + buffer.extend_from_slice(b"\r\n") + } + BodyType::None => buffer.extend_from_slice(b"\r\n"), + } + + let mut has_date = false; + + for (key, value) in &msg.headers { + match *key { + TRANSFER_ENCODING => continue, + CONTENT_LENGTH => match btype { + BodyType::None => (), + BodyType::Zero => len_is_set = true, + _ => continue, + }, + DATE => has_date = true, + UPGRADE => self.flags.insert(Flags::UPGRADE), + _ => (), + } + + buffer.put_slice(key.as_ref()); + buffer.put_slice(b": "); + buffer.put_slice(value.as_ref()); + buffer.put_slice(b"\r\n"); + } + + // set content length + if !len_is_set { + buffer.extend_from_slice(b"content-length: 0\r\n") } // set date header - if !msg.headers.contains_key(DATE) { + if !has_date { self.config.set_date(buffer); } else { buffer.extend_from_slice(b"\r\n"); diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index fe2aa707a..61cab7ad8 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -42,10 +42,9 @@ impl Decoder for MessageDecoder { } pub(crate) enum PayloadLength { - None, - Chunked, + Payload(PayloadType), Upgrade, - Length(u64), + None, } pub(crate) trait MessageTypeDecoder: Sized { @@ -55,7 +54,7 @@ pub(crate) trait MessageTypeDecoder: Sized { fn decode(src: &mut BytesMut) -> Result, ParseError>; - fn process_headers( + fn set_headers( &mut self, slice: &Bytes, version: Version, @@ -140,10 +139,17 @@ pub(crate) trait MessageTypeDecoder: Sized { self.keep_alive(); } + // https://tools.ietf.org/html/rfc7230#section-3.3.3 if chunked { - Ok(PayloadLength::Chunked) + // Chunked encoding + Ok(PayloadLength::Payload(PayloadType::Payload( + PayloadDecoder::chunked(), + ))) } else if let Some(len) = content_length { - Ok(PayloadLength::Length(len)) + // Content-Length + Ok(PayloadLength::Payload(PayloadType::Payload( + PayloadDecoder::length(len), + ))) } else if has_upgrade { Ok(PayloadLength::Upgrade) } else { @@ -166,7 +172,7 @@ impl MessageTypeDecoder for Request { // performance bump for pipeline benchmarks. let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; - let (len, method, uri, version, headers_len) = { + let (len, method, uri, ver, h_len) = { let mut parsed: [httparse::Header; MAX_HEADERS] = unsafe { mem::uninitialized() }; @@ -189,35 +195,24 @@ impl MessageTypeDecoder for Request { } }; - // convert headers let mut msg = Request::new(); - let len = msg.process_headers( - &src.split_to(len).freeze(), - version, - &headers[..headers_len], - )?; + // convert headers + let len = + msg.set_headers(&src.split_to(len).freeze(), ver, &headers[..h_len])?; - // https://tools.ietf.org/html/rfc7230#section-3.3.3 + // payload decoder let decoder = match len { - PayloadLength::Chunked => { - // Chunked encoding - PayloadType::Payload(PayloadDecoder::chunked()) - } - PayloadLength::Length(len) => { - // Content-Length - PayloadType::Payload(PayloadDecoder::length(len)) - } + PayloadLength::Payload(pl) => pl, PayloadLength::Upgrade => { - // upgrade(websocket) or connect + // upgrade(websocket) PayloadType::Stream(PayloadDecoder::eof()) } PayloadLength::None => { if method == Method::CONNECT { - // upgrade(websocket) or connect PayloadType::Stream(PayloadDecoder::eof()) } else if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + trace!("MAX_BUFFER_SIZE unprocessed data reached, closing"); return Err(ParseError::TooLarge); } else { PayloadType::None @@ -230,7 +225,7 @@ impl MessageTypeDecoder for Request { inner.url.update(&uri); inner.head.uri = uri; inner.head.method = method; - inner.head.version = version; + inner.head.version = ver; } Ok(Some((msg, decoder))) @@ -251,7 +246,7 @@ impl MessageTypeDecoder for ClientResponse { // performance bump for pipeline benchmarks. let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; - let (len, version, status, headers_len) = { + let (len, ver, status, h_len) = { let mut parsed: [httparse::Header; MAX_HEADERS] = unsafe { mem::uninitialized() }; @@ -276,37 +271,26 @@ impl MessageTypeDecoder for ClientResponse { let mut msg = ClientResponse::new(); // convert headers - let len = msg.process_headers( - &src.split_to(len).freeze(), - version, - &headers[..headers_len], - )?; + let len = + msg.set_headers(&src.split_to(len).freeze(), ver, &headers[..h_len])?; - // https://tools.ietf.org/html/rfc7230#section-3.3.3 - let decoder = match len { - PayloadLength::Chunked => { - // Chunked encoding - PayloadType::Payload(PayloadDecoder::chunked()) - } - PayloadLength::Length(len) => { - // Content-Length - PayloadType::Payload(PayloadDecoder::length(len)) - } - _ => { - if status == StatusCode::SWITCHING_PROTOCOLS { - // switching protocol or connect - PayloadType::Stream(PayloadDecoder::eof()) - } else if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ParseError::TooLarge); - } else { - PayloadType::None - } + // message payload + let decoder = if let PayloadLength::Payload(pl) = len { + pl + } else { + if status == StatusCode::SWITCHING_PROTOCOLS { + // switching protocol or connect + PayloadType::Stream(PayloadDecoder::eof()) + } else if src.len() >= MAX_BUFFER_SIZE { + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + return Err(ParseError::TooLarge); + } else { + PayloadType::None } }; msg.head.status = status; - msg.head.version = Some(version); + msg.head.version = Some(ver); Ok(Some((msg, decoder))) } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 470d3df93..4a0bce723 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -1,11 +1,12 @@ use std::collections::VecDeque; use std::fmt::Debug; +use std::mem; use std::time::Instant; use actix_net::codec::Framed; use actix_net::service::Service; -use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; +use futures::{Async, Future, Poll, Sink, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; @@ -37,12 +38,19 @@ bitflags! { /// Dispatcher for HTTP/1.1 protocol pub struct Dispatcher +where + S::Error: Debug, +{ + inner: Option>, +} + +struct InnerDispatcher where S::Error: Debug, { service: S, flags: Flags, - framed: Option>, + framed: Framed, error: Option>, config: ServiceConfig, @@ -63,7 +71,6 @@ enum DispatcherMessage { enum State { None, ServiceCall(S::Future), - SendResponse(Option<(Message, Body)>), SendPayload(BodyStream), } @@ -113,20 +120,29 @@ where }; Dispatcher { - payload: None, - state: State::None, - error: None, - messages: VecDeque::new(), - framed: Some(framed), - unhandled: None, - service, - flags, - config, - ka_expire, - ka_timer, + inner: Some(InnerDispatcher { + framed, + payload: None, + state: State::None, + error: None, + messages: VecDeque::new(), + unhandled: None, + service, + flags, + config, + ka_expire, + ka_timer, + }), } } +} +impl InnerDispatcher +where + T: AsyncRead + AsyncWrite, + S: Service, + S::Error: Debug, +{ fn can_read(&self) -> bool { if self.flags.contains(Flags::DISCONNECTED) { return false; @@ -150,7 +166,7 @@ where /// Flush stream fn poll_flush(&mut self) -> Poll<(), DispatchError> { if !self.flags.contains(Flags::FLUSHED) { - match self.framed.as_mut().unwrap().poll_complete() { + match self.framed.poll_complete() { Ok(Async::NotReady) => Ok(Async::NotReady), Err(err) => { debug!("Error sending data: {}", err); @@ -170,90 +186,82 @@ where } } + fn send_response( + &mut self, + message: Response, + body: Body, + ) -> Result, DispatchError> { + self.framed + .force_send(Message::Item(message)) + .map_err(|err| { + if let Some(mut payload) = self.payload.take() { + payload.set_error(PayloadError::Incomplete(None)); + } + DispatchError::Io(err) + })?; + + self.flags + .set(Flags::KEEPALIVE, self.framed.get_codec().keepalive()); + self.flags.remove(Flags::FLUSHED); + match body { + Body::Empty => Ok(State::None), + Body::Streaming(stream) => Ok(State::SendPayload(stream)), + Body::Binary(mut bin) => { + self.flags.remove(Flags::FLUSHED); + self.framed.force_send(Message::Chunk(Some(bin.take())))?; + self.framed.force_send(Message::Chunk(None))?; + Ok(State::None) + } + } + } + fn poll_response(&mut self) -> Result<(), DispatchError> { let mut retry = self.can_read(); - - // process loop { - let state = match self.state { - State::None => if let Some(msg) = self.messages.pop_front() { - match msg { - DispatcherMessage::Item(req) => Some(self.handle_request(req)?), - DispatcherMessage::Error(res) => Some(State::SendResponse( - Some((Message::Item(res), Body::Empty)), - )), + let state = match mem::replace(&mut self.state, State::None) { + State::None => match self.messages.pop_front() { + Some(DispatcherMessage::Item(req)) => { + Some(self.handle_request(req)?) } - } else { - None + Some(DispatcherMessage::Error(res)) => { + Some(self.send_response(res, Body::Empty)?) + } + None => None, }, - // call inner service - State::ServiceCall(ref mut fut) => { + State::ServiceCall(mut fut) => { match fut.poll().map_err(DispatchError::Service)? { Async::Ready(mut res) => { - self.framed - .as_mut() - .unwrap() - .get_codec_mut() - .prepare_te(&mut res); + self.framed.get_codec_mut().prepare_te(&mut res); let body = res.replace_body(Body::Empty); - Some(State::SendResponse(Some((Message::Item(res), body)))) + Some(self.send_response(res, body)?) } - Async::NotReady => None, - } - } - // send respons - State::SendResponse(ref mut item) => { - let (msg, body) = item.take().expect("SendResponse is empty"); - let framed = self.framed.as_mut().unwrap(); - match framed.start_send(msg) { - Ok(AsyncSink::Ready) => { - self.flags - .set(Flags::KEEPALIVE, framed.get_codec().keepalive()); - self.flags.remove(Flags::FLUSHED); - match body { - Body::Empty => Some(State::None), - Body::Streaming(stream) => { - Some(State::SendPayload(stream)) - } - Body::Binary(mut bin) => { - self.flags.remove(Flags::FLUSHED); - framed - .force_send(Message::Chunk(Some(bin.take())))?; - framed.force_send(Message::Chunk(None))?; - Some(State::None) - } - } - } - Ok(AsyncSink::NotReady(msg)) => { - *item = Some((msg, body)); - return Ok(()); - } - Err(err) => { - if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete(None)); - } - return Err(DispatchError::Io(err)); + Async::NotReady => { + self.state = State::ServiceCall(fut); + None } } } - // Send payload - State::SendPayload(ref mut stream) => { - let mut framed = self.framed.as_mut().unwrap(); + State::SendPayload(mut stream) => { loop { - if !framed.is_write_buf_full() { + if !self.framed.is_write_buf_full() { match stream.poll().map_err(|_| DispatchError::Unknown)? { Async::Ready(Some(item)) => { self.flags.remove(Flags::FLUSHED); - framed.force_send(Message::Chunk(Some(item)))?; + self.framed + .force_send(Message::Chunk(Some(item)))?; continue; } Async::Ready(None) => { self.flags.remove(Flags::FLUSHED); - framed.force_send(Message::Chunk(None))?; + self.framed.force_send(Message::Chunk(None))?; + } + Async::NotReady => { + self.state = State::SendPayload(stream); + return Ok(()); } - Async::NotReady => return Ok(()), } } else { + self.state = State::SendPayload(stream); return Ok(()); } break; @@ -266,7 +274,7 @@ where Some(state) => self.state = state, None => { // if read-backpressure is enabled and we consumed some data. - // we may read more dataand retry + // we may read more data and retry if !retry && self.can_read() && self.poll_request()? { retry = self.can_read(); continue; @@ -286,13 +294,9 @@ where let mut task = self.service.call(req); match task.poll().map_err(DispatchError::Service)? { Async::Ready(mut res) => { - self.framed - .as_mut() - .unwrap() - .get_codec_mut() - .prepare_te(&mut res); + self.framed.get_codec_mut().prepare_te(&mut res); let body = res.replace_body(Body::Empty); - Ok(State::SendResponse(Some((Message::Item(res), body)))) + self.send_response(res, body) } Async::NotReady => Ok(State::ServiceCall(task)), } @@ -307,20 +311,14 @@ where let mut updated = false; loop { - match self.framed.as_mut().unwrap().poll() { + match self.framed.poll() { Ok(Async::Ready(Some(msg))) => { updated = true; self.flags.insert(Flags::STARTED); match msg { Message::Item(req) => { - match self - .framed - .as_ref() - .unwrap() - .get_codec() - .message_type() - { + match self.framed.get_codec().message_type() { MessageType::Payload => { let (ps, pl) = Payload::new(false); *req.inner.payload.borrow_mut() = Some(pl); @@ -406,52 +404,58 @@ where /// keep-alive timer fn poll_keepalive(&mut self) -> Result<(), DispatchError> { - if let Some(ref mut timer) = self.ka_timer { - match timer.poll() { - Ok(Async::Ready(_)) => { - // if we get timer during shutdown, just drop connection - if self.flags.contains(Flags::SHUTDOWN) { - return Err(DispatchError::DisconnectTimeout); - } else if timer.deadline() >= self.ka_expire { - // check for any outstanding response processing - if self.state.is_empty() && self.flags.contains(Flags::FLUSHED) { - if self.flags.contains(Flags::STARTED) { - trace!("Keep-alive timeout, close connection"); - self.flags.insert(Flags::SHUTDOWN); + if self.ka_timer.is_some() { + return Ok(()); + } + match self.ka_timer.as_mut().unwrap().poll().map_err(|e| { + error!("Timer error {:?}", e); + DispatchError::Unknown + })? { + Async::Ready(_) => { + // if we get timeout during shutdown, drop connection + if self.flags.contains(Flags::SHUTDOWN) { + return Err(DispatchError::DisconnectTimeout); + } else if self.ka_timer.as_mut().unwrap().deadline() >= self.ka_expire { + // check for any outstanding response processing + if self.state.is_empty() && self.flags.contains(Flags::FLUSHED) { + if self.flags.contains(Flags::STARTED) { + trace!("Keep-alive timeout, close connection"); + self.flags.insert(Flags::SHUTDOWN); - // start shutdown timer - if let Some(deadline) = - self.config.client_disconnect_timer() - { + // start shutdown timer + if let Some(deadline) = self.config.client_disconnect_timer() + { + self.ka_timer.as_mut().map(|timer| { timer.reset(deadline); let _ = timer.poll(); - } else { - return Ok(()); - } + }); } else { - // timeout on first request (slow request) return 408 - trace!("Slow request timeout"); - self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); - self.state = State::SendResponse(Some(( - Message::Item(Response::RequestTimeout().finish()), - Body::Empty, - ))); + return Ok(()); } - } else if let Some(deadline) = self.config.keep_alive_expire() { + } else { + // timeout on first request (slow request) return 408 + trace!("Slow request timeout"); + self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); + self.state = self.send_response( + Response::RequestTimeout().finish(), + Body::Empty, + )?; + } + } else if let Some(deadline) = self.config.keep_alive_expire() { + self.ka_timer.as_mut().map(|timer| { timer.reset(deadline); let _ = timer.poll(); - } - } else { - timer.reset(self.ka_expire); - let _ = timer.poll(); + }); } - } - Ok(Async::NotReady) => (), - Err(e) => { - error!("Timer error {:?}", e); - return Err(DispatchError::Unknown); + } else { + let expire = self.ka_expire; + self.ka_timer.as_mut().map(|timer| { + timer.reset(expire); + let _ = timer.poll(); + }); } } + Async::NotReady => (), } Ok(()) @@ -469,43 +473,53 @@ where #[inline] fn poll(&mut self) -> Poll { - if self.flags.contains(Flags::SHUTDOWN) { - self.poll_keepalive()?; - try_ready!(self.poll_flush()); - let io = self.framed.take().unwrap().into_inner(); - Ok(Async::Ready(H1ServiceResult::Shutdown(io))) - } else { - self.poll_keepalive()?; - self.poll_request()?; - self.poll_response()?; - self.poll_flush()?; - - // keep-alive and stream errors - if self.state.is_empty() && self.flags.contains(Flags::FLUSHED) { - if let Some(err) = self.error.take() { - Err(err) - } else if self.flags.contains(Flags::DISCONNECTED) { - Ok(Async::Ready(H1ServiceResult::Disconnected)) - } - // unhandled request (upgrade or connect) - else if self.unhandled.is_some() { - let req = self.unhandled.take().unwrap(); - let framed = self.framed.take().unwrap(); - Ok(Async::Ready(H1ServiceResult::Unhandled(req, framed))) - } - // disconnect if keep-alive is not enabled - else if self.flags.contains(Flags::STARTED) && !self - .flags - .intersects(Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED) - { - let io = self.framed.take().unwrap().into_inner(); - Ok(Async::Ready(H1ServiceResult::Shutdown(io))) - } else { - Ok(Async::NotReady) - } + let shutdown = if let Some(ref mut inner) = self.inner { + if inner.flags.contains(Flags::SHUTDOWN) { + inner.poll_keepalive()?; + try_ready!(inner.poll_flush()); + true } else { - Ok(Async::NotReady) + inner.poll_keepalive()?; + inner.poll_request()?; + inner.poll_response()?; + inner.poll_flush()?; + + // keep-alive and stream errors + if inner.state.is_empty() && inner.flags.contains(Flags::FLUSHED) { + if let Some(err) = inner.error.take() { + return Err(err); + } else if inner.flags.contains(Flags::DISCONNECTED) { + return Ok(Async::Ready(H1ServiceResult::Disconnected)); + } + // unhandled request (upgrade or connect) + else if inner.unhandled.is_some() { + false + } + // disconnect if keep-alive is not enabled + else if inner.flags.contains(Flags::STARTED) && !inner + .flags + .intersects(Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED) + { + true + } else { + return Ok(Async::NotReady); + } + } else { + return Ok(Async::NotReady); + } } + } else { + unreachable!() + }; + + let mut inner = self.inner.take().unwrap(); + if shutdown { + Ok(Async::Ready(H1ServiceResult::Shutdown( + inner.framed.into_inner(), + ))) + } else { + let req = inner.unhandled.take().unwrap(); + Ok(Async::Ready(H1ServiceResult::Unhandled(req, inner.framed))) } } } From 3a4b16a6d57458f9071cb22732d07ab7f2d864e2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 16 Nov 2018 21:30:37 -0800 Subject: [PATCH 070/208] use BodyLength for request and response body --- src/body.rs | 36 +++++++++++++++++++----------------- src/client/pipeline.rs | 12 ++++++------ src/h1/client.rs | 27 ++++++++++++++------------- src/h1/codec.rs | 20 +++++++++----------- src/h1/encoder.rs | 39 ++++++++++++++------------------------- src/ws/client/service.rs | 4 ++-- 6 files changed, 64 insertions(+), 74 deletions(-) diff --git a/src/body.rs b/src/body.rs index 1165909e8..6e6239c3e 100644 --- a/src/body.rs +++ b/src/body.rs @@ -12,24 +12,26 @@ pub type BodyStream = Box>; /// Type represent streaming payload pub type PayloadStream = Box>; -/// Different type of bory -pub enum BodyType { +#[derive(Debug)] +/// Different type of body +pub enum BodyLength { None, Zero, Sized(usize), + Sized64(u64), Unsized, } /// Type that provides this trait can be streamed to a peer. pub trait MessageBody { - fn tp(&self) -> BodyType; + fn length(&self) -> BodyLength; fn poll_next(&mut self) -> Poll, Error>; } impl MessageBody for () { - fn tp(&self) -> BodyType { - BodyType::Zero + fn length(&self) -> BodyLength { + BodyLength::Zero } fn poll_next(&mut self) -> Poll, Error> { @@ -271,8 +273,8 @@ impl AsRef<[u8]> for Binary { } impl MessageBody for Bytes { - fn tp(&self) -> BodyType { - BodyType::Sized(self.len()) + fn length(&self) -> BodyLength { + BodyLength::Sized(self.len()) } fn poll_next(&mut self) -> Poll, Error> { @@ -285,8 +287,8 @@ impl MessageBody for Bytes { } impl MessageBody for &'static str { - fn tp(&self) -> BodyType { - BodyType::Sized(self.len()) + fn length(&self) -> BodyLength { + BodyLength::Sized(self.len()) } fn poll_next(&mut self) -> Poll, Error> { @@ -301,8 +303,8 @@ impl MessageBody for &'static str { } impl MessageBody for &'static [u8] { - fn tp(&self) -> BodyType { - BodyType::Sized(self.len()) + fn length(&self) -> BodyLength { + BodyLength::Sized(self.len()) } fn poll_next(&mut self) -> Poll, Error> { @@ -317,8 +319,8 @@ impl MessageBody for &'static [u8] { } impl MessageBody for Vec { - fn tp(&self) -> BodyType { - BodyType::Sized(self.len()) + fn length(&self) -> BodyLength { + BodyLength::Sized(self.len()) } fn poll_next(&mut self) -> Poll, Error> { @@ -334,8 +336,8 @@ impl MessageBody for Vec { } impl MessageBody for String { - fn tp(&self) -> BodyType { - BodyType::Sized(self.len()) + fn length(&self) -> BodyLength { + BodyLength::Sized(self.len()) } fn poll_next(&mut self) -> Poll, Error> { @@ -367,8 +369,8 @@ impl MessageBody for MessageBodyStream where S: Stream, { - fn tp(&self) -> BodyType { - BodyType::Unsized + fn length(&self) -> BodyLength { + BodyLength::Unsized } fn poll_next(&mut self) -> Poll, Error> { diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 17ba93e7c..93b349e93 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -10,7 +10,7 @@ use tokio_io::{AsyncRead, AsyncWrite}; use super::error::{ConnectorError, SendRequestError}; use super::response::ClientResponse; use super::{Connect, Connection}; -use body::{BodyType, MessageBody, PayloadStream}; +use body::{BodyLength, MessageBody, PayloadStream}; use error::PayloadError; use h1; use message::RequestHead; @@ -25,7 +25,7 @@ where B: MessageBody, I: Connection, { - let tp = body.tp(); + let len = body.length(); connector // connect to the host @@ -33,10 +33,10 @@ where .from_err() // create Framed and send reqest .map(|io| Framed::new(io, h1::ClientCodec::default())) - .and_then(|framed| framed.send((head, tp).into()).from_err()) + .and_then(|framed| framed.send((head, len).into()).from_err()) // send request body - .and_then(move |framed| match body.tp() { - BodyType::None | BodyType::Zero => Either::A(ok(framed)), + .and_then(move |framed| match body.length() { + BodyLength::None | BodyLength::Zero => Either::A(ok(framed)), _ => Either::B(SendBody::new(body, framed)), }) // read response and init read body @@ -64,7 +64,7 @@ where struct SendBody { body: Option, framed: Option>, - write_buf: VecDeque>, + write_buf: VecDeque>, flushed: bool, } diff --git a/src/h1/client.rs b/src/h1/client.rs index f871cb338..8b367acc3 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -5,9 +5,9 @@ use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; -use super::encoder::{RequestEncoder, ResponseLength}; +use super::encoder::RequestEncoder; use super::{Message, MessageType}; -use body::{Binary, Body, BodyType}; +use body::{Binary, Body, BodyLength}; use client::ClientResponse; use config::ServiceConfig; use error::{ParseError, PayloadError}; @@ -104,7 +104,7 @@ impl ClientCodec { } /// prepare transfer encoding - pub fn prepare_te(&mut self, head: &mut RequestHead, btype: BodyType) { + pub fn prepare_te(&mut self, head: &mut RequestHead, length: BodyLength) { self.inner.te.update( head, self.inner.flags.contains(Flags::HEAD), @@ -138,7 +138,7 @@ impl ClientCodecInner { fn encode_response( &mut self, msg: RequestHead, - btype: BodyType, + length: BodyLength, buffer: &mut BytesMut, ) -> io::Result<()> { // render message @@ -157,20 +157,21 @@ impl ClientCodecInner { // content length let mut len_is_set = true; - match btype { - BodyType::Sized(len) => { + match length { + BodyLength::Sized(len) => helpers::write_content_length(len, buffer), + BodyLength::Sized64(len) => { buffer.extend_from_slice(b"\r\ncontent-length: "); write!(buffer.writer(), "{}", len)?; buffer.extend_from_slice(b"\r\n"); } - BodyType::Unsized => { + BodyLength::Unsized => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } - BodyType::Zero => { + BodyLength::Zero => { len_is_set = false; buffer.extend_from_slice(b"\r\n") } - BodyType::None => buffer.extend_from_slice(b"\r\n"), + BodyLength::None => buffer.extend_from_slice(b"\r\n"), } let mut has_date = false; @@ -178,9 +179,9 @@ impl ClientCodecInner { for (key, value) in &msg.headers { match *key { TRANSFER_ENCODING => continue, - CONTENT_LENGTH => match btype { - BodyType::None => (), - BodyType::Zero => len_is_set = true, + CONTENT_LENGTH => match length { + BodyLength::None => (), + BodyLength::Zero => len_is_set = true, _ => continue, }, DATE => has_date = true, @@ -263,7 +264,7 @@ impl Decoder for ClientPayloadCodec { } impl Encoder for ClientCodec { - type Item = Message<(RequestHead, BodyType)>; + type Item = Message<(RequestHead, BodyLength)>; type Error = io::Error; fn encode( diff --git a/src/h1/codec.rs b/src/h1/codec.rs index c1c6091de..a6f39242b 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -6,9 +6,9 @@ use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; -use super::encoder::{ResponseEncoder, ResponseLength}; +use super::encoder::ResponseEncoder; use super::{Message, MessageType}; -use body::{Binary, Body}; +use body::{Binary, Body, BodyLength}; use config::ServiceConfig; use error::ParseError; use helpers; @@ -155,22 +155,20 @@ impl Codec { // content length let mut len_is_set = true; match self.te.length { - ResponseLength::Chunked => { + BodyLength::Unsized => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } - ResponseLength::Zero => { + BodyLength::Zero => { len_is_set = false; buffer.extend_from_slice(b"\r\n") } - ResponseLength::Length(len) => { - helpers::write_content_length(len, buffer) - } - ResponseLength::Length64(len) => { + BodyLength::Sized(len) => helpers::write_content_length(len, buffer), + BodyLength::Sized64(len) => { buffer.extend_from_slice(b"\r\ncontent-length: "); write!(buffer.writer(), "{}", len)?; buffer.extend_from_slice(b"\r\n"); } - ResponseLength::None => buffer.extend_from_slice(b"\r\n"), + BodyLength::None => buffer.extend_from_slice(b"\r\n"), } // write headers @@ -182,8 +180,8 @@ impl Codec { match *key { TRANSFER_ENCODING => continue, CONTENT_LENGTH => match self.te.length { - ResponseLength::None => (), - ResponseLength::Zero => { + BodyLength::None => (), + BodyLength::Zero => { len_is_set = true; } _ => continue, diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index de45351d1..5cfdd01b9 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -8,28 +8,17 @@ use bytes::{Bytes, BytesMut}; use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; -use body::{Binary, Body}; +use body::{Binary, Body, BodyLength}; use header::ContentEncoding; use http::Method; use message::RequestHead; use request::Request; use response::Response; -#[derive(Debug)] -pub(crate) enum ResponseLength { - Chunked, - /// Check if headers contains length or write 0 - Zero, - Length(usize), - Length64(u64), - /// Do no set content-length - None, -} - #[derive(Debug)] pub(crate) struct ResponseEncoder { head: bool, - pub length: ResponseLength, + pub length: BodyLength, pub te: TransferEncoding, } @@ -37,7 +26,7 @@ impl Default for ResponseEncoder { fn default() -> Self { ResponseEncoder { head: false, - length: ResponseLength::None, + length: BodyLength::None, te: TransferEncoding::empty(), } } @@ -80,18 +69,18 @@ impl ResponseEncoder { StatusCode::NO_CONTENT | StatusCode::CONTINUE | StatusCode::SWITCHING_PROTOCOLS - | StatusCode::PROCESSING => ResponseLength::None, - _ => ResponseLength::Zero, + | StatusCode::PROCESSING => BodyLength::None, + _ => BodyLength::Zero, }; TransferEncoding::empty() } Body::Binary(_) => { - self.length = ResponseLength::Length(len); + self.length = BodyLength::Sized(len); TransferEncoding::length(len as u64) } Body::Streaming(_) => { if resp.upgrade() { - self.length = ResponseLength::None; + self.length = BodyLength::None; TransferEncoding::eof() } else { self.streaming_encoding(version, resp) @@ -115,10 +104,10 @@ impl ResponseEncoder { Some(true) => { // Enable transfer encoding if version == Version::HTTP_2 { - self.length = ResponseLength::None; + self.length = BodyLength::None; TransferEncoding::eof() } else { - self.length = ResponseLength::Chunked; + self.length = BodyLength::Unsized; TransferEncoding::chunked() } } @@ -145,7 +134,7 @@ impl ResponseEncoder { if !chunked { if let Some(len) = len { - self.length = ResponseLength::Length64(len); + self.length = BodyLength::Sized64(len); TransferEncoding::length(len) } else { TransferEncoding::eof() @@ -154,11 +143,11 @@ impl ResponseEncoder { // Enable transfer encoding match version { Version::HTTP_11 => { - self.length = ResponseLength::Chunked; + self.length = BodyLength::Unsized; TransferEncoding::chunked() } _ => { - self.length = ResponseLength::None; + self.length = BodyLength::None; TransferEncoding::eof() } } @@ -171,7 +160,7 @@ impl ResponseEncoder { #[derive(Debug)] pub(crate) struct RequestEncoder { head: bool, - pub length: ResponseLength, + pub length: BodyLength, pub te: TransferEncoding, } @@ -179,7 +168,7 @@ impl Default for RequestEncoder { fn default() -> Self { RequestEncoder { head: false, - length: ResponseLength::None, + length: BodyLength::None, te: TransferEncoding::empty(), } } diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 34a151444..94be59f6e 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -13,7 +13,7 @@ use rand; use sha1::Sha1; use tokio_io::{AsyncRead, AsyncWrite}; -use body::BodyType; +use body::BodyLength; use client::ClientResponse; use h1; use ws::Codec; @@ -141,7 +141,7 @@ where // h1 protocol let framed = Framed::new(io, h1::ClientCodec::default()); framed - .send((request.into_parts().0, BodyType::None).into()) + .send((request.into_parts().0, BodyLength::None).into()) .map_err(ClientError::from) .and_then(|framed| { framed From f0bd4d868e12f2f1b227c681e9cadd007937d94a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 17 Nov 2018 08:56:40 -0800 Subject: [PATCH 071/208] simplify server response type --- src/message.rs | 2 + src/response.rs | 119 +++++++++++++++++++++--------------------------- 2 files changed, 53 insertions(+), 68 deletions(-) diff --git a/src/message.rs b/src/message.rs index b38c2f801..f0275093f 100644 --- a/src/message.rs +++ b/src/message.rs @@ -74,6 +74,8 @@ impl Default for ResponseHead { impl Head for ResponseHead { fn clear(&mut self) { + self.reason = None; + self.version = None; self.headers.clear(); self.flags = MessageFlags::empty(); } diff --git a/src/response.rs b/src/response.rs index 1901443fe..0b20f41b8 100644 --- a/src/response.rs +++ b/src/response.rs @@ -12,6 +12,7 @@ use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version}; use serde::Serialize; use serde_json; +use message::{Head, ResponseHead, MessageFlags}; use body::Body; use error::Error; use header::{ContentEncoding, Header, IntoHeaderValue}; @@ -31,7 +32,7 @@ pub enum ConnectionType { } /// An HTTP Response -pub struct Response(Box, &'static ResponsePool); +pub struct Response(Box); impl Response { #[inline] @@ -92,7 +93,6 @@ impl Response { } ResponseBuilder { - pool: self.1, response: Some(self.0), err: None, cookies: jar, @@ -108,33 +108,33 @@ impl Response { /// Get the HTTP version of this response #[inline] pub fn version(&self) -> Option { - self.get_ref().version + self.get_ref().head.version } /// Get the headers from the response #[inline] pub fn headers(&self) -> &HeaderMap { - &self.get_ref().headers + &self.get_ref().head.headers } /// Get a mutable reference to the headers #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.get_mut().headers + &mut self.get_mut().head.headers } /// Get an iterator for the cookies set by this response #[inline] pub fn cookies(&self) -> CookieIter { CookieIter { - iter: self.get_ref().headers.get_all(header::SET_COOKIE).iter(), + iter: self.get_ref().head.headers.get_all(header::SET_COOKIE).iter(), } } /// Add a cookie to this response #[inline] pub fn add_cookie(&mut self, cookie: &Cookie) -> Result<(), HttpError> { - let h = &mut self.get_mut().headers; + let h = &mut self.get_mut().head.headers; HeaderValue::from_str(&cookie.to_string()) .map(|c| { h.append(header::SET_COOKIE, c); @@ -145,7 +145,7 @@ impl Response { /// the number of cookies removed. #[inline] pub fn del_cookie(&mut self, name: &str) -> usize { - let h = &mut self.get_mut().headers; + let h = &mut self.get_mut().head.headers; let vals: Vec = h .get_all(header::SET_COOKIE) .iter() @@ -171,23 +171,23 @@ impl Response { /// Get the response status code #[inline] pub fn status(&self) -> StatusCode { - self.get_ref().status + self.get_ref().head.status } /// Set the `StatusCode` for this response #[inline] pub fn status_mut(&mut self) -> &mut StatusCode { - &mut self.get_mut().status + &mut self.get_mut().head.status } /// Get custom reason for the response #[inline] pub fn reason(&self) -> &str { - if let Some(reason) = self.get_ref().reason { + if let Some(reason) = self.get_ref().head.reason { reason } else { self.get_ref() - .status + .head.status .canonical_reason() .unwrap_or("") } @@ -196,7 +196,7 @@ impl Response { /// Set the custom reason for the response #[inline] pub fn set_reason(&mut self, reason: &'static str) -> &mut Self { - self.get_mut().reason = Some(reason); + self.get_mut().head.reason = Some(reason); self } @@ -279,7 +279,7 @@ impl Response { } pub(crate) fn release(self) { - self.1.release(self.0); + ResponsePool::release(self.0); } pub(crate) fn into_parts(self) -> ResponseParts { @@ -287,10 +287,7 @@ impl Response { } pub(crate) fn from_parts(parts: ResponseParts) -> Response { - Response( - Box::new(InnerResponse::from_parts(parts)), - ResponsePool::get_pool(), - ) + Response(Box::new(InnerResponse::from_parts(parts))) } } @@ -299,13 +296,13 @@ impl fmt::Debug for Response { let res = writeln!( f, "\nResponse {:?} {}{}", - self.get_ref().version, - self.get_ref().status, - self.get_ref().reason.unwrap_or("") + self.get_ref().head.version, + self.get_ref().head.status, + self.get_ref().head.reason.unwrap_or("") ); let _ = writeln!(f, " encoding: {:?}", self.get_ref().encoding); let _ = writeln!(f, " headers:"); - for (key, val) in self.get_ref().headers.iter() { + for (key, val) in self.get_ref().head.headers.iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); } res @@ -335,7 +332,6 @@ impl<'a> Iterator for CookieIter<'a> { /// This type can be used to construct an instance of `Response` through a /// builder-like pattern. pub struct ResponseBuilder { - pool: &'static ResponsePool, response: Option>, err: Option, cookies: Option, @@ -346,7 +342,7 @@ impl ResponseBuilder { #[inline] pub fn status(&mut self, status: StatusCode) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { - parts.status = status; + parts.head.status = status; } self } @@ -357,7 +353,7 @@ impl ResponseBuilder { #[inline] pub fn version(&mut self, version: Version) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { - parts.version = Some(version); + parts.head.version = Some(version); } self } @@ -382,7 +378,7 @@ impl ResponseBuilder { if let Some(parts) = parts(&mut self.response, &self.err) { match hdr.try_into() { Ok(value) => { - parts.headers.append(H::name(), value); + parts.head.headers.append(H::name(), value); } Err(e) => self.err = Some(e.into()), } @@ -413,7 +409,7 @@ impl ResponseBuilder { match HeaderName::try_from(key) { Ok(key) => match value.try_into() { Ok(value) => { - parts.headers.append(key, value); + parts.head.headers.append(key, value); } Err(e) => self.err = Some(e.into()), }, @@ -427,7 +423,7 @@ impl ResponseBuilder { #[inline] pub fn reason(&mut self, reason: &'static str) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { - parts.reason = Some(reason); + parts.head.reason = Some(reason); } self } @@ -496,7 +492,7 @@ impl ResponseBuilder { if let Some(parts) = parts(&mut self.response, &self.err) { match HeaderValue::try_from(value) { Ok(value) => { - parts.headers.insert(header::CONTENT_TYPE, value); + parts.head.headers.insert(header::CONTENT_TYPE, value); } Err(e) => self.err = Some(e.into()), }; @@ -620,13 +616,13 @@ impl ResponseBuilder { if let Some(ref jar) = self.cookies { for cookie in jar.delta() { match HeaderValue::from_str(&cookie.to_string()) { - Ok(val) => response.headers.append(header::SET_COOKIE, val), + Ok(val) => response.head.headers.append(header::SET_COOKIE, val), Err(e) => return Error::from(e).into(), }; } } response.body = body.into(); - Response(response, self.pool) + Response(response) } #[inline] @@ -656,7 +652,7 @@ impl ResponseBuilder { Ok(body) => { let contains = if let Some(parts) = parts(&mut self.response, &self.err) { - parts.headers.contains_key(header::CONTENT_TYPE) + parts.head.headers.contains_key(header::CONTENT_TYPE) } else { true }; @@ -681,7 +677,6 @@ impl ResponseBuilder { /// This method construct new `ResponseBuilder` pub fn take(&mut self) -> ResponseBuilder { ResponseBuilder { - pool: self.pool, response: self.response.take(), err: self.err.take(), cookies: self.cookies.take(), @@ -765,12 +760,8 @@ impl From for Response { } } -#[derive(Debug)] struct InnerResponse { - version: Option, - headers: HeaderMap, - status: StatusCode, - reason: Option<&'static str>, + head: ResponseHead, body: Body, chunked: Option, encoding: Option, @@ -778,13 +769,11 @@ struct InnerResponse { write_capacity: usize, response_size: u64, error: Option, + pool: &'static ResponsePool, } pub(crate) struct ResponseParts { - version: Option, - headers: HeaderMap, - status: StatusCode, - reason: Option<&'static str>, + head: ResponseHead, body: Option, encoding: Option, connection_type: Option, @@ -793,13 +782,17 @@ pub(crate) struct ResponseParts { impl InnerResponse { #[inline] - fn new(status: StatusCode, body: Body) -> InnerResponse { + fn new(status: StatusCode, body: Body, pool: &'static ResponsePool) -> InnerResponse { InnerResponse { - status, + head: ResponseHead { + status, + version: None, + headers: HeaderMap::with_capacity(16), + reason: None, + flags: MessageFlags::empty(), + }, body, - version: None, - headers: HeaderMap::with_capacity(16), - reason: None, + pool, chunked: None, encoding: None, connection_type: None, @@ -822,10 +815,7 @@ impl InnerResponse { ResponseParts { body, - version: self.version, - headers: self.headers, - status: self.status, - reason: self.reason, + head: self.head, encoding: self.encoding, connection_type: self.connection_type, error: self.error, @@ -841,16 +831,14 @@ impl InnerResponse { InnerResponse { body, - status: parts.status, - version: parts.version, - headers: parts.headers, - reason: parts.reason, + head: parts.head, chunked: None, encoding: parts.encoding, connection_type: parts.connection_type, response_size: 0, write_capacity: MAX_WRITE_BUFFER_SIZE, error: parts.error, + pool: ResponsePool::pool(), } } } @@ -876,17 +864,15 @@ impl ResponsePool { status: StatusCode, ) -> ResponseBuilder { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { - msg.status = status; + msg.head.status = status; ResponseBuilder { - pool, response: Some(msg), err: None, cookies: None, } } else { - let msg = Box::new(InnerResponse::new(status, Body::Empty)); + let msg = Box::new(InnerResponse::new(status, Body::Empty, pool)); ResponseBuilder { - pool, response: Some(msg), err: None, cookies: None, @@ -901,12 +887,11 @@ impl ResponsePool { body: Body, ) -> Response { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { - msg.status = status; + msg.head.status = status; msg.body = body; - Response(msg, pool) + Response(msg) } else { - let msg = Box::new(InnerResponse::new(status, body)); - Response(msg, pool) + Response(Box::new(InnerResponse::new(status, body, pool))) } } @@ -921,13 +906,11 @@ impl ResponsePool { } #[inline] - fn release(&self, mut inner: Box) { - let mut p = self.0.borrow_mut(); + fn release(mut inner: Box) { + let mut p = inner.pool.0.borrow_mut(); if p.len() < 128 { - inner.headers.clear(); - inner.version = None; + inner.head.clear(); inner.chunked = None; - inner.reason = None; inner.encoding = None; inner.connection_type = None; inner.response_size = 0; From e73a97884af8d0c738aaf32cb6142b703620d46f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 17 Nov 2018 09:03:35 -0800 Subject: [PATCH 072/208] do not allow to set server response version --- src/client/response.rs | 2 +- src/h1/codec.rs | 7 +++---- src/h1/decoder.rs | 2 +- src/h1/encoder.rs | 2 -- src/message.rs | 5 ++--- src/response.rs | 43 ++++++++++++++++-------------------------- 6 files changed, 23 insertions(+), 38 deletions(-) diff --git a/src/client/response.rs b/src/client/response.rs index 797c88df8..41c18562a 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -57,7 +57,7 @@ impl ClientResponse { /// Read the Request Version. #[inline] pub fn version(&self) -> Version { - self.head().version.clone().unwrap() + self.head().version } /// Get the status from the server. diff --git a/src/h1/codec.rs b/src/h1/codec.rs index a6f39242b..bb8afa710 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -113,7 +113,6 @@ impl Codec { .unwrap_or_else(|| self.flags.contains(Flags::KEEPALIVE)); // Connection upgrade - let version = msg.version().unwrap_or_else(|| self.version); if msg.upgrade() { self.flags.insert(Flags::UPGRADE); self.flags.remove(Flags::KEEPALIVE); @@ -123,11 +122,11 @@ impl Codec { // keep-alive else if ka { self.flags.insert(Flags::KEEPALIVE); - if version < Version::HTTP_11 { + if self.version < Version::HTTP_11 { msg.headers_mut() .insert(CONNECTION, HeaderValue::from_static("keep-alive")); } - } else if version >= Version::HTTP_11 { + } else if self.version >= Version::HTTP_11 { self.flags.remove(Flags::KEEPALIVE); msg.headers_mut() .insert(CONNECTION, HeaderValue::from_static("close")); @@ -149,7 +148,7 @@ impl Codec { } // status line - helpers::write_status_line(version, msg.status().as_u16(), buffer); + helpers::write_status_line(self.version, msg.status().as_u16(), buffer); buffer.extend_from_slice(reason); // content length diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 61cab7ad8..26154ef1e 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -290,7 +290,7 @@ impl MessageTypeDecoder for ClientResponse { }; msg.head.status = status; - msg.head.version = Some(ver); + msg.head.version = ver; Ok(Some((msg, decoder))) } diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 5cfdd01b9..421d0b961 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -45,8 +45,6 @@ impl ResponseEncoder { pub fn update(&mut self, resp: &mut Response, head: bool, version: Version) { self.head = head; - - let version = resp.version().unwrap_or_else(|| version); let mut len = 0; let has_body = match resp.body() { diff --git a/src/message.rs b/src/message.rs index f0275093f..3dcb203de 100644 --- a/src/message.rs +++ b/src/message.rs @@ -53,7 +53,7 @@ impl Head for RequestHead { } pub struct ResponseHead { - pub version: Option, + pub version: Version, pub status: StatusCode, pub headers: HeaderMap, pub reason: Option<&'static str>, @@ -63,7 +63,7 @@ pub struct ResponseHead { impl Default for ResponseHead { fn default() -> ResponseHead { ResponseHead { - version: None, + version: Version::default(), status: StatusCode::OK, headers: HeaderMap::with_capacity(16), reason: None, @@ -75,7 +75,6 @@ impl Default for ResponseHead { impl Head for ResponseHead { fn clear(&mut self) { self.reason = None; - self.version = None; self.headers.clear(); self.flags = MessageFlags::empty(); } diff --git a/src/response.rs b/src/response.rs index 0b20f41b8..f96facbae 100644 --- a/src/response.rs +++ b/src/response.rs @@ -12,10 +12,10 @@ use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version}; use serde::Serialize; use serde_json; -use message::{Head, ResponseHead, MessageFlags}; use body::Body; use error::Error; use header::{ContentEncoding, Header, IntoHeaderValue}; +use message::{Head, MessageFlags, ResponseHead}; /// max write buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; @@ -105,12 +105,6 @@ impl Response { self.get_ref().error.as_ref() } - /// Get the HTTP version of this response - #[inline] - pub fn version(&self) -> Option { - self.get_ref().head.version - } - /// Get the headers from the response #[inline] pub fn headers(&self) -> &HeaderMap { @@ -127,7 +121,12 @@ impl Response { #[inline] pub fn cookies(&self) -> CookieIter { CookieIter { - iter: self.get_ref().head.headers.get_all(header::SET_COOKIE).iter(), + iter: self + .get_ref() + .head + .headers + .get_all(header::SET_COOKIE) + .iter(), } } @@ -187,7 +186,8 @@ impl Response { reason } else { self.get_ref() - .head.status + .head + .status .canonical_reason() .unwrap_or("") } @@ -347,17 +347,6 @@ impl ResponseBuilder { self } - /// Set HTTP version of this response. - /// - /// By default response's http version depends on request's version. - #[inline] - pub fn version(&mut self, version: Version) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.head.version = Some(version); - } - self - } - /// Set a header. /// /// ```rust,ignore @@ -782,11 +771,15 @@ pub(crate) struct ResponseParts { impl InnerResponse { #[inline] - fn new(status: StatusCode, body: Body, pool: &'static ResponsePool) -> InnerResponse { + fn new( + status: StatusCode, + body: Body, + pool: &'static ResponsePool, + ) -> InnerResponse { InnerResponse { head: ResponseHead { status, - version: None, + version: Version::default(), headers: HeaderMap::with_capacity(16), reason: None, flags: MessageFlags::empty(), @@ -999,11 +992,7 @@ mod tests { #[test] fn test_basic_builder() { - let resp = Response::Ok() - .header("X-TEST", "value") - .version(Version::HTTP_10) - .finish(); - assert_eq!(resp.version(), Some(Version::HTTP_10)); + let resp = Response::Ok().header("X-TEST", "value").finish(); assert_eq!(resp.status(), StatusCode::OK); } From 7fed50bcaefde84c6e9424112e220fe789f99a57 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 17 Nov 2018 20:21:28 -0800 Subject: [PATCH 073/208] refactor response body management --- src/body.rs | 118 +++------------- src/client/pipeline.rs | 4 + src/client/request.rs | 6 - src/error.rs | 9 +- src/h1/client.rs | 8 +- src/h1/codec.rs | 26 ++-- src/h1/dispatcher.rs | 80 ++++++----- src/h1/encoder.rs | 122 +++------------- src/h1/service.rs | 54 ++++--- src/lib.rs | 2 +- src/response.rs | 309 ++++++++++++++++------------------------- src/service.rs | 144 +++++++++---------- 12 files changed, 334 insertions(+), 548 deletions(-) diff --git a/src/body.rs b/src/body.rs index 6e6239c3e..3b4e0113d 100644 --- a/src/body.rs +++ b/src/body.rs @@ -1,5 +1,5 @@ +use std::mem; use std::sync::Arc; -use std::{fmt, mem}; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll, Stream}; @@ -19,7 +19,8 @@ pub enum BodyLength { Zero, Sized(usize), Sized64(u64), - Unsized, + Chunked, + Stream, } /// Type that provides this trait can be streamed to a peer. @@ -39,17 +40,6 @@ impl MessageBody for () { } } -/// Represents various types of http message body. -pub enum Body { - /// Empty response. `Content-Length` header is set to `0` - Empty, - /// Specific response body. - Binary(Binary), - /// Unspecified streaming response. Developer is responsible for setting - /// right `Content-Length` or `Transfer-Encoding` headers. - Streaming(BodyStream), -} - /// Represents various types of binary body. /// `Content-Length` header is set to length of the body. #[derive(Debug, PartialEq)] @@ -65,84 +55,6 @@ pub enum Binary { SharedVec(Arc>), } -impl Body { - /// Does this body streaming. - #[inline] - pub fn is_streaming(&self) -> bool { - match *self { - Body::Streaming(_) => true, - _ => false, - } - } - - /// Is this binary body. - #[inline] - pub fn is_binary(&self) -> bool { - match *self { - Body::Binary(_) => true, - _ => false, - } - } - - /// Is this binary empy. - #[inline] - pub fn is_empty(&self) -> bool { - match *self { - Body::Empty => true, - _ => false, - } - } - - /// Create body from slice (copy) - pub fn from_slice(s: &[u8]) -> Body { - Body::Binary(Binary::Bytes(Bytes::from(s))) - } - - /// Is this binary body. - #[inline] - pub(crate) fn into_binary(self) -> Option { - match self { - Body::Binary(b) => Some(b), - _ => None, - } - } -} - -impl PartialEq for Body { - fn eq(&self, other: &Body) -> bool { - match *self { - Body::Empty => match *other { - Body::Empty => true, - _ => false, - }, - Body::Binary(ref b) => match *other { - Body::Binary(ref b2) => b == b2, - _ => false, - }, - Body::Streaming(_) => false, - } - } -} - -impl fmt::Debug for Body { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Body::Empty => write!(f, "Body::Empty"), - Body::Binary(ref b) => write!(f, "Body::Binary({:?})", b), - Body::Streaming(_) => write!(f, "Body::Streaming(_)"), - } - } -} - -impl From for Body -where - T: Into, -{ - fn from(b: T) -> Body { - Body::Binary(b.into()) - } -} - impl Binary { #[inline] /// Returns `true` if body is empty @@ -286,6 +198,22 @@ impl MessageBody for Bytes { } } +impl MessageBody for BytesMut { + fn length(&self) -> BodyLength { + BodyLength::Sized(self.len()) + } + + fn poll_next(&mut self) -> Poll, Error> { + if self.is_empty() { + Ok(Async::Ready(None)) + } else { + Ok(Async::Ready(Some( + mem::replace(self, BytesMut::new()).freeze(), + ))) + } + } +} + impl MessageBody for &'static str { fn length(&self) -> BodyLength { BodyLength::Sized(self.len()) @@ -370,7 +298,7 @@ where S: Stream, { fn length(&self) -> BodyLength { - BodyLength::Unsized + BodyLength::Chunked } fn poll_next(&mut self) -> Poll, Error> { @@ -382,12 +310,6 @@ where mod tests { use super::*; - #[test] - fn test_body_is_streaming() { - assert_eq!(Body::Empty.is_streaming(), false); - assert_eq!(Body::Binary(Binary::from("")).is_streaming(), false); - } - #[test] fn test_is_empty() { assert_eq!(Binary::from("").is_empty(), true); diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 93b349e93..56c22bd2a 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -100,6 +100,10 @@ where { match self.body.as_mut().unwrap().poll_next()? { Async::Ready(item) => { + // check if body is done + if item.is_none() { + let _ = self.body.take(); + } self.flushed = false; self.framed .as_mut() diff --git a/src/client/request.rs b/src/client/request.rs index d3d1544c2..f0b76ed04 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -51,12 +51,6 @@ pub struct ClientRequest { body: B, } -impl RequestHead { - pub fn clear(&mut self) { - self.headers.clear() - } -} - impl ClientRequest<()> { /// Create client request builder pub fn build() -> ClientRequestBuilder { diff --git a/src/error.rs b/src/error.rs index 956ec4eb4..1f70396c3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -100,11 +100,10 @@ impl Error { } /// Converts error to a response instance and set error message as response body - pub fn response_with_message(self) -> Response { + pub fn response_with_message(self) -> Response { let message = format!("{}", self); - let mut resp: Response = self.into(); - resp.set_body(message); - resp + let resp: Response = self.into(); + resp.set_body(message) } } @@ -637,7 +636,7 @@ where InternalErrorType::Status(st) => Response::new(st), InternalErrorType::Response(ref resp) => { if let Some(resp) = resp.lock().unwrap().take() { - Response::from_parts(resp) + Response::<()>::from_parts(resp) } else { Response::new(StatusCode::INTERNAL_SERVER_ERROR) } diff --git a/src/h1/client.rs b/src/h1/client.rs index 8b367acc3..ace504668 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -7,7 +7,7 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; use super::encoder::RequestEncoder; use super::{Message, MessageType}; -use body::{Binary, Body, BodyLength}; +use body::{Binary, BodyLength}; use client::ClientResponse; use config::ServiceConfig; use error::{ParseError, PayloadError}; @@ -164,14 +164,16 @@ impl ClientCodecInner { write!(buffer.writer(), "{}", len)?; buffer.extend_from_slice(b"\r\n"); } - BodyLength::Unsized => { + BodyLength::Chunked => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } BodyLength::Zero => { len_is_set = false; buffer.extend_from_slice(b"\r\n") } - BodyLength::None => buffer.extend_from_slice(b"\r\n"), + BodyLength::None | BodyLength::Stream => { + buffer.extend_from_slice(b"\r\n") + } } let mut has_date = false; diff --git a/src/h1/codec.rs b/src/h1/codec.rs index bb8afa710..6bc20b180 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -8,12 +8,13 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; use super::encoder::ResponseEncoder; use super::{Message, MessageType}; -use body::{Binary, Body, BodyLength}; +use body::{Binary, BodyLength}; use config::ServiceConfig; use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{Method, Version}; +use message::ResponseHead; use request::Request; use response::Response; @@ -98,9 +99,9 @@ impl Codec { } /// prepare transfer encoding - pub fn prepare_te(&mut self, res: &mut Response) { + pub fn prepare_te(&mut self, head: &mut ResponseHead, length: &mut BodyLength) { self.te - .update(res, self.flags.contains(Flags::HEAD), self.version); + .update(head, self.flags.contains(Flags::HEAD), self.version, length); } fn encode_response( @@ -135,17 +136,8 @@ impl Codec { // render message { let reason = msg.reason().as_bytes(); - if let Body::Binary(ref bytes) = msg.body() { - buffer.reserve( - 256 + msg.headers().len() * AVERAGE_HEADER_SIZE - + bytes.len() - + reason.len(), - ); - } else { - buffer.reserve( - 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len(), - ); - } + buffer + .reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len()); // status line helpers::write_status_line(self.version, msg.status().as_u16(), buffer); @@ -154,7 +146,7 @@ impl Codec { // content length let mut len_is_set = true; match self.te.length { - BodyLength::Unsized => { + BodyLength::Chunked => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } BodyLength::Zero => { @@ -167,7 +159,9 @@ impl Codec { write!(buffer.writer(), "{}", len)?; buffer.extend_from_slice(b"\r\n"); } - BodyLength::None => buffer.extend_from_slice(b"\r\n"), + BodyLength::None | BodyLength::Stream => { + buffer.extend_from_slice(b"\r\n") + } } // write headers diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 4a0bce723..508962b43 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -13,7 +13,7 @@ use tokio_timer::Delay; use error::{ParseError, PayloadError}; use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; -use body::{Body, BodyStream}; +use body::{BodyLength, MessageBody}; use config::ServiceConfig; use error::DispatchError; use request::Request; @@ -37,14 +37,14 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher +pub struct Dispatcher where S::Error: Debug, { - inner: Option>, + inner: Option>, } -struct InnerDispatcher +struct InnerDispatcher where S::Error: Debug, { @@ -54,7 +54,7 @@ where error: Option>, config: ServiceConfig, - state: State, + state: State, payload: Option, messages: VecDeque, unhandled: Option, @@ -68,13 +68,13 @@ enum DispatcherMessage { Error(Response), } -enum State { +enum State { None, ServiceCall(S::Future), - SendPayload(BodyStream), + SendPayload(B), } -impl State { +impl State { fn is_empty(&self) -> bool { if let State::None = self { true @@ -84,11 +84,12 @@ impl State { } } -impl Dispatcher +impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service>, S::Error: Debug, + B: MessageBody, { /// Create http/1 dispatcher. pub fn new(stream: T, config: ServiceConfig, service: S) -> Self { @@ -137,11 +138,12 @@ where } } -impl InnerDispatcher +impl InnerDispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service>, S::Error: Debug, + B: MessageBody, { fn can_read(&self) -> bool { if self.flags.contains(Flags::DISCONNECTED) { @@ -186,11 +188,11 @@ where } } - fn send_response( + fn send_response( &mut self, message: Response, - body: Body, - ) -> Result, DispatchError> { + body: B1, + ) -> Result, DispatchError> { self.framed .force_send(Message::Item(message)) .map_err(|err| { @@ -203,15 +205,9 @@ where self.flags .set(Flags::KEEPALIVE, self.framed.get_codec().keepalive()); self.flags.remove(Flags::FLUSHED); - match body { - Body::Empty => Ok(State::None), - Body::Streaming(stream) => Ok(State::SendPayload(stream)), - Body::Binary(mut bin) => { - self.flags.remove(Flags::FLUSHED); - self.framed.force_send(Message::Chunk(Some(bin.take())))?; - self.framed.force_send(Message::Chunk(None))?; - Ok(State::None) - } + match body.length() { + BodyLength::None | BodyLength::Zero => Ok(State::None), + _ => Ok(State::SendPayload(body)), } } @@ -224,15 +220,18 @@ where Some(self.handle_request(req)?) } Some(DispatcherMessage::Error(res)) => { - Some(self.send_response(res, Body::Empty)?) + self.send_response(res, ())?; + None } None => None, }, State::ServiceCall(mut fut) => { match fut.poll().map_err(DispatchError::Service)? { Async::Ready(mut res) => { - self.framed.get_codec_mut().prepare_te(&mut res); - let body = res.replace_body(Body::Empty); + let (mut res, body) = res.replace_body(()); + self.framed + .get_codec_mut() + .prepare_te(res.head_mut(), &mut body.length()); Some(self.send_response(res, body)?) } Async::NotReady => { @@ -244,7 +243,10 @@ where State::SendPayload(mut stream) => { loop { if !self.framed.is_write_buf_full() { - match stream.poll().map_err(|_| DispatchError::Unknown)? { + match stream + .poll_next() + .map_err(|_| DispatchError::Unknown)? + { Async::Ready(Some(item)) => { self.flags.remove(Flags::FLUSHED); self.framed @@ -290,12 +292,14 @@ where fn handle_request( &mut self, req: Request, - ) -> Result, DispatchError> { + ) -> Result, DispatchError> { let mut task = self.service.call(req); match task.poll().map_err(DispatchError::Service)? { - Async::Ready(mut res) => { - self.framed.get_codec_mut().prepare_te(&mut res); - let body = res.replace_body(Body::Empty); + Async::Ready(res) => { + let (mut res, body) = res.replace_body(()); + self.framed + .get_codec_mut() + .prepare_te(res.head_mut(), &mut body.length()); self.send_response(res, body) } Async::NotReady => Ok(State::ServiceCall(task)), @@ -436,10 +440,9 @@ where // timeout on first request (slow request) return 408 trace!("Slow request timeout"); self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); - self.state = self.send_response( - Response::RequestTimeout().finish(), - Body::Empty, - )?; + let _ = self + .send_response(Response::RequestTimeout().finish(), ()); + self.state = State::None; } } else if let Some(deadline) = self.config.keep_alive_expire() { self.ka_timer.as_mut().map(|timer| { @@ -462,11 +465,12 @@ where } } -impl Future for Dispatcher +impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service>, S::Error: Debug, + B: MessageBody, { type Item = H1ServiceResult; type Error = DispatchError; diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 421d0b961..aee17c1f0 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -8,10 +8,10 @@ use bytes::{Bytes, BytesMut}; use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; -use body::{Binary, Body, BodyLength}; +use body::{Binary, BodyLength}; use header::ContentEncoding; use http::Method; -use message::RequestHead; +use message::{RequestHead, ResponseHead}; use request::Request; use response::Response; @@ -43,116 +43,36 @@ impl ResponseEncoder { self.te.encode_eof(buf) } - pub fn update(&mut self, resp: &mut Response, head: bool, version: Version) { + pub fn update( + &mut self, + resp: &mut ResponseHead, + head: bool, + version: Version, + length: &mut BodyLength, + ) { self.head = head; - let mut len = 0; - - let has_body = match resp.body() { - Body::Empty => false, - Body::Binary(ref bin) => { - len = bin.len(); - true - } - _ => true, - }; - - let has_body = match resp.body() { - Body::Empty => false, - _ => true, - }; - - let transfer = match resp.body() { - Body::Empty => { - self.length = match resp.status() { + let transfer = match length { + BodyLength::Zero => { + match resp.status { StatusCode::NO_CONTENT | StatusCode::CONTINUE | StatusCode::SWITCHING_PROTOCOLS - | StatusCode::PROCESSING => BodyLength::None, - _ => BodyLength::Zero, - }; + | StatusCode::PROCESSING => *length = BodyLength::None, + _ => (), + } TransferEncoding::empty() } - Body::Binary(_) => { - self.length = BodyLength::Sized(len); - TransferEncoding::length(len as u64) - } - Body::Streaming(_) => { - if resp.upgrade() { - self.length = BodyLength::None; - TransferEncoding::eof() - } else { - self.streaming_encoding(version, resp) - } - } + BodyLength::Sized(len) => TransferEncoding::length(*len as u64), + BodyLength::Sized64(len) => TransferEncoding::length(*len), + BodyLength::Chunked => TransferEncoding::chunked(), + BodyLength::Stream => TransferEncoding::eof(), + BodyLength::None => TransferEncoding::length(0), }; // check for head response - if self.head { - resp.set_body(Body::Empty); - } else { + if !self.head { self.te = transfer; } } - - fn streaming_encoding( - &mut self, - version: Version, - resp: &mut Response, - ) -> TransferEncoding { - match resp.chunked() { - Some(true) => { - // Enable transfer encoding - if version == Version::HTTP_2 { - self.length = BodyLength::None; - TransferEncoding::eof() - } else { - self.length = BodyLength::Unsized; - TransferEncoding::chunked() - } - } - Some(false) => TransferEncoding::eof(), - None => { - // if Content-Length is specified, then use it as length hint - let (len, chunked) = - if let Some(len) = resp.headers().get(CONTENT_LENGTH) { - // Content-Length - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - (Some(len), false) - } else { - error!("illegal Content-Length: {:?}", len); - (None, false) - } - } else { - error!("illegal Content-Length: {:?}", len); - (None, false) - } - } else { - (None, true) - }; - - if !chunked { - if let Some(len) = len { - self.length = BodyLength::Sized64(len); - TransferEncoding::length(len) - } else { - TransferEncoding::eof() - } - } else { - // Enable transfer encoding - match version { - Version::HTTP_11 => { - self.length = BodyLength::Unsized; - TransferEncoding::chunked() - } - _ => { - self.length = BodyLength::None; - TransferEncoding::eof() - } - } - } - } - } - } } #[derive(Debug)] diff --git a/src/h1/service.rs b/src/h1/service.rs index 7e5e8c5fc..0f0452ee7 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -8,6 +8,7 @@ use futures::future::{ok, FutureResult}; use futures::{Async, Future, Poll, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; +use body::MessageBody; use config::{KeepAlive, ServiceConfig}; use error::{DispatchError, ParseError}; use request::Request; @@ -18,17 +19,18 @@ use super::dispatcher::Dispatcher; use super::{H1ServiceResult, Message}; /// `NewService` implementation for HTTP1 transport -pub struct H1Service { +pub struct H1Service { srv: S, cfg: ServiceConfig, - _t: PhantomData, + _t: PhantomData<(T, B)>, } -impl H1Service +impl H1Service where - S: NewService + Clone, + S: NewService> + Clone, S::Service: Clone, S::Error: Debug, + B: MessageBody, { /// Create new `HttpService` instance. pub fn new>(service: F) -> Self { @@ -47,19 +49,20 @@ where } } -impl NewService for H1Service +impl NewService for H1Service where T: AsyncRead + AsyncWrite, - S: NewService + Clone, + S: NewService> + Clone, S::Service: Clone, S::Error: Debug, + B: MessageBody, { type Request = T; type Response = H1ServiceResult; type Error = DispatchError; type InitError = S::InitError; - type Service = H1ServiceHandler; - type Future = H1ServiceResponse; + type Service = H1ServiceHandler; + type Future = H1ServiceResponse; fn new_service(&self) -> Self::Future { H1ServiceResponse { @@ -180,7 +183,11 @@ where } /// Finish service configuration and create `H1Service` instance. - pub fn finish>(self, service: F) -> H1Service { + pub fn finish(self, service: F) -> H1Service + where + B: MessageBody, + F: IntoNewService, + { let cfg = ServiceConfig::new( self.keep_alive, self.client_timeout, @@ -195,20 +202,21 @@ where } #[doc(hidden)] -pub struct H1ServiceResponse { +pub struct H1ServiceResponse { fut: S::Future, cfg: Option, - _t: PhantomData, + _t: PhantomData<(T, B)>, } -impl Future for H1ServiceResponse +impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService>, S::Service: Clone, S::Error: Debug, + B: MessageBody, { - type Item = H1ServiceHandler; + type Item = H1ServiceHandler; type Error = S::InitError; fn poll(&mut self) -> Poll { @@ -221,18 +229,19 @@ where } /// `Service` implementation for HTTP1 transport -pub struct H1ServiceHandler { +pub struct H1ServiceHandler { srv: S, cfg: ServiceConfig, - _t: PhantomData, + _t: PhantomData<(T, B)>, } -impl H1ServiceHandler +impl H1ServiceHandler where - S: Service + Clone, + S: Service> + Clone, S::Error: Debug, + B: MessageBody, { - fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { + fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { H1ServiceHandler { srv, cfg, @@ -241,16 +250,17 @@ where } } -impl Service for H1ServiceHandler +impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service + Clone, + S: Service> + Clone, S::Error: Debug, + B: MessageBody, { type Request = T; type Response = H1ServiceResult; type Error = DispatchError; - type Future = Dispatcher; + type Future = Dispatcher; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.srv.poll_ready().map_err(DispatchError::Service) diff --git a/src/lib.rs b/src/lib.rs index f64876e31..4b43cae40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -129,7 +129,7 @@ pub mod h1; pub(crate) mod helpers; pub mod test; pub mod ws; -pub use body::{Binary, Body}; +pub use body::{Binary, MessageBody}; pub use error::{Error, ResponseError, Result}; pub use extensions::Extensions; pub use httpmessage::HttpMessage; diff --git a/src/response.rs b/src/response.rs index f96facbae..b3e599826 100644 --- a/src/response.rs +++ b/src/response.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::collections::VecDeque; use std::io::Write; -use std::{fmt, mem, str}; +use std::{fmt, str}; use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; @@ -12,7 +12,7 @@ use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version}; use serde::Serialize; use serde_json; -use body::Body; +use body::{MessageBody, MessageBodyStream}; use error::Error; use header::{ContentEncoding, Header, IntoHeaderValue}; use message::{Head, MessageFlags, ResponseHead}; @@ -32,19 +32,9 @@ pub enum ConnectionType { } /// An HTTP Response -pub struct Response(Box); - -impl Response { - #[inline] - fn get_ref(&self) -> &InnerResponse { - self.0.as_ref() - } - - #[inline] - fn get_mut(&mut self) -> &mut InnerResponse { - self.0.as_mut() - } +pub struct Response(Box, B); +impl Response<()> { /// Create http response builder with specific status. #[inline] pub fn build(status: StatusCode) -> ResponseBuilder { @@ -60,13 +50,7 @@ impl Response { /// Constructs a response #[inline] pub fn new(status: StatusCode) -> Response { - ResponsePool::with_body(status, Body::Empty) - } - - /// Constructs a response with body - #[inline] - pub fn with_body>(status: StatusCode, body: B) -> Response { - ResponsePool::with_body(status, body.into()) + ResponsePool::with_body(status, ()) } /// Constructs an error response @@ -98,6 +82,29 @@ impl Response { cookies: jar, } } +} + +impl Response { + #[inline] + fn get_ref(&self) -> &InnerResponse { + self.0.as_ref() + } + + #[inline] + fn get_mut(&mut self) -> &mut InnerResponse { + self.0.as_mut() + } + + #[inline] + pub(crate) fn head_mut(&mut self) -> &mut ResponseHead { + &mut self.0.as_mut().head + } + + /// Constructs a response with body + #[inline] + pub fn with_body(status: StatusCode, body: B) -> Response { + ResponsePool::with_body(status, body.into()) + } /// The source `error` for this response #[inline] @@ -105,6 +112,39 @@ impl Response { self.get_ref().error.as_ref() } + /// Get the response status code + #[inline] + pub fn status(&self) -> StatusCode { + self.get_ref().head.status + } + + /// Set the `StatusCode` for this response + #[inline] + pub fn status_mut(&mut self) -> &mut StatusCode { + &mut self.get_mut().head.status + } + + /// Get custom reason for the response + #[inline] + pub fn reason(&self) -> &str { + if let Some(reason) = self.get_ref().head.reason { + reason + } else { + self.get_ref() + .head + .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.get_mut().head.reason = Some(reason); + self + } + /// Get the headers from the response #[inline] pub fn headers(&self) -> &HeaderMap { @@ -167,39 +207,6 @@ impl Response { count } - /// Get the response status code - #[inline] - pub fn status(&self) -> StatusCode { - self.get_ref().head.status - } - - /// Set the `StatusCode` for this response - #[inline] - pub fn status_mut(&mut self) -> &mut StatusCode { - &mut self.get_mut().head.status - } - - /// Get custom reason for the response - #[inline] - pub fn reason(&self) -> &str { - if let Some(reason) = self.get_ref().head.reason { - reason - } else { - self.get_ref() - .head - .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.get_mut().head.reason = Some(reason); - self - } - /// Set connection type pub fn set_connection_type(&mut self, conn: ConnectionType) -> &mut Self { self.get_mut().connection_type = Some(conn); @@ -224,38 +231,20 @@ impl Response { } } - /// is chunked encoding enabled - #[inline] - pub fn chunked(&self) -> Option { - self.get_ref().chunked - } - - /// Content encoding - #[inline] - pub fn content_encoding(&self) -> Option { - self.get_ref().encoding - } - - /// Set content encoding - pub fn set_content_encoding(&mut self, enc: ContentEncoding) -> &mut Self { - self.get_mut().encoding = Some(enc); - self - } - /// Get body os this response #[inline] - pub fn body(&self) -> &Body { - &self.get_ref().body + pub fn body(&self) -> &B { + &self.1 } /// Set a body - pub fn set_body>(&mut self, body: B) { - self.get_mut().body = body.into(); + pub fn set_body(self, body: B2) -> Response { + Response(self.0, body) } /// Set a body and return previous body value - pub fn replace_body>(&mut self, body: B) -> Body { - mem::replace(&mut self.get_mut().body, body.into()) + pub fn replace_body(self, body: B2) -> (Response, B) { + (Response(self.0, body), self.1) } /// Size of response in bytes, excluding HTTP headers @@ -268,16 +257,6 @@ impl Response { self.get_mut().response_size = size; } - /// Set write buffer capacity - pub fn write_buffer_capacity(&self) -> usize { - self.get_ref().write_capacity - } - - /// Set write buffer capacity - pub fn set_write_buffer_capacity(&mut self, cap: usize) { - self.get_mut().write_capacity = cap; - } - pub(crate) fn release(self) { ResponsePool::release(self.0); } @@ -287,7 +266,7 @@ impl Response { } pub(crate) fn from_parts(parts: ResponseParts) -> Response { - Response(Box::new(InnerResponse::from_parts(parts))) + Response(Box::new(InnerResponse::from_parts(parts)), ()) } } @@ -454,24 +433,6 @@ impl ResponseBuilder { self.connection_type(ConnectionType::Close) } - /// Enables automatic chunked transfer encoding - #[inline] - pub fn chunked(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.chunked = Some(true); - } - self - } - - /// Force disable chunked encoding - #[inline] - pub fn no_chunking(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.chunked = Some(false); - } - self - } - /// Set response content type #[inline] pub fn content_type(&mut self, value: V) -> &mut Self @@ -580,63 +541,73 @@ impl ResponseBuilder { self } - /// Set write buffer capacity - /// - /// This parameter makes sense only for streaming response - /// or actor. If write buffer reaches specified capacity, stream or actor - /// get paused. - /// - /// Default write buffer capacity is 64kb - pub fn write_buffer_capacity(&mut self, cap: usize) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.write_capacity = cap; - } - self - } + // /// Set write buffer capacity + // /// + // /// This parameter makes sense only for streaming response + // /// or actor. If write buffer reaches specified capacity, stream or actor + // /// get paused. + // /// + // /// Default write buffer capacity is 64kb + // pub fn write_buffer_capacity(&mut self, cap: usize) -> &mut Self { + // if let Some(parts) = parts(&mut self.response, &self.err) { + // parts.write_capacity = cap; + // } + // self + // } /// Set a body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. - pub fn body>(&mut self, body: B) -> Response { - if let Some(e) = self.err.take() { - return Error::from(e).into(); - } + pub fn body(&mut self, body: B) -> Response { + let mut error = if let Some(e) = self.err.take() { + Some(Error::from(e)) + } else { + None + }; + let mut response = self.response.take().expect("cannot reuse response builder"); if let Some(ref jar) = self.cookies { for cookie in jar.delta() { match HeaderValue::from_str(&cookie.to_string()) { - Ok(val) => response.head.headers.append(header::SET_COOKIE, val), - Err(e) => return Error::from(e).into(), + Ok(val) => { + let _ = response.head.headers.append(header::SET_COOKIE, val); + } + Err(e) => if error.is_none() { + error = Some(Error::from(e)); + }, }; } } - response.body = body.into(); - Response(response) + if let Some(error) = error { + response.error = Some(error); + } + + Response(response, body) } #[inline] /// Set a streaming body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. - pub fn streaming(&mut self, stream: S) -> Response + pub fn streaming(&mut self, stream: S) -> Response where S: Stream + 'static, E: Into, { - self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) + self.body(MessageBodyStream::new(stream.map_err(|e| e.into()))) } /// Set a json body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. - pub fn json(&mut self, value: T) -> Response { + pub fn json(&mut self, value: T) -> Response { self.json2(&value) } /// Set a json body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. - pub fn json2(&mut self, value: &T) -> Response { + pub fn json2(&mut self, value: &T) -> Response { match serde_json::to_string(value) { Ok(body) => { let contains = if let Some(parts) = parts(&mut self.response, &self.err) @@ -651,7 +622,10 @@ impl ResponseBuilder { self.body(body) } - Err(e) => Error::from(e).into(), + Err(e) => { + let mut res: Response = Error::from(e).into(); + res.replace_body(String::new()).0 + } } } @@ -659,8 +633,8 @@ impl ResponseBuilder { /// Set an empty body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. - pub fn finish(&mut self) -> Response { - self.body(Body::Empty) + pub fn finish(&mut self) -> Response<()> { + self.body(()) } /// This method construct new `ResponseBuilder` @@ -701,7 +675,7 @@ impl From for Response { } } -impl From<&'static str> for Response { +impl From<&'static str> for Response<&'static str> { fn from(val: &'static str) -> Self { Response::Ok() .content_type("text/plain; charset=utf-8") @@ -709,7 +683,7 @@ impl From<&'static str> for Response { } } -impl From<&'static [u8]> for Response { +impl From<&'static [u8]> for Response<&'static [u8]> { fn from(val: &'static [u8]) -> Self { Response::Ok() .content_type("application/octet-stream") @@ -717,7 +691,7 @@ impl From<&'static [u8]> for Response { } } -impl From for Response { +impl From for Response { fn from(val: String) -> Self { Response::Ok() .content_type("text/plain; charset=utf-8") @@ -725,15 +699,7 @@ impl From for Response { } } -impl<'a> From<&'a String> for Response { - fn from(val: &'a String) -> Self { - Response::build(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(val) - } -} - -impl From for Response { +impl From for Response { fn from(val: Bytes) -> Self { Response::Ok() .content_type("application/octet-stream") @@ -741,7 +707,7 @@ impl From for Response { } } -impl From for Response { +impl From for Response { fn from(val: BytesMut) -> Self { Response::Ok() .content_type("application/octet-stream") @@ -751,8 +717,6 @@ impl From for Response { struct InnerResponse { head: ResponseHead, - body: Body, - chunked: Option, encoding: Option, connection_type: Option, write_capacity: usize, @@ -763,7 +727,6 @@ struct InnerResponse { pub(crate) struct ResponseParts { head: ResponseHead, - body: Option, encoding: Option, connection_type: Option, error: Option, @@ -771,11 +734,7 @@ pub(crate) struct ResponseParts { impl InnerResponse { #[inline] - fn new( - status: StatusCode, - body: Body, - pool: &'static ResponsePool, - ) -> InnerResponse { + fn new(status: StatusCode, pool: &'static ResponsePool) -> InnerResponse { InnerResponse { head: ResponseHead { status, @@ -784,9 +743,7 @@ impl InnerResponse { reason: None, flags: MessageFlags::empty(), }, - body, pool, - chunked: None, encoding: None, connection_type: None, response_size: 0, @@ -796,18 +753,8 @@ impl InnerResponse { } /// This is for failure, we can not have Send + Sync on Streaming and Actor response - fn into_parts(mut self) -> ResponseParts { - let body = match mem::replace(&mut self.body, Body::Empty) { - Body::Empty => None, - Body::Binary(mut bin) => Some(bin.take()), - Body::Streaming(_) => { - error!("Streaming or Actor body is not support by error response"); - None - } - }; - + fn into_parts(self) -> ResponseParts { ResponseParts { - body, head: self.head, encoding: self.encoding, connection_type: self.connection_type, @@ -816,16 +763,8 @@ impl InnerResponse { } fn from_parts(parts: ResponseParts) -> InnerResponse { - let body = if let Some(ref body) = parts.body { - Body::Binary(body.clone().into()) - } else { - Body::Empty - }; - InnerResponse { - body, head: parts.head, - chunked: None, encoding: parts.encoding, connection_type: parts.connection_type, response_size: 0, @@ -864,7 +803,7 @@ impl ResponsePool { cookies: None, } } else { - let msg = Box::new(InnerResponse::new(status, Body::Empty, pool)); + let msg = Box::new(InnerResponse::new(status, pool)); ResponseBuilder { response: Some(msg), err: None, @@ -874,17 +813,16 @@ impl ResponsePool { } #[inline] - pub fn get_response( + pub fn get_response( pool: &'static ResponsePool, status: StatusCode, - body: Body, - ) -> Response { + body: B, + ) -> Response { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { msg.head.status = status; - msg.body = body; - Response(msg) + Response(msg, body) } else { - Response(Box::new(InnerResponse::new(status, body, pool))) + Response(Box::new(InnerResponse::new(status, pool)), body) } } @@ -894,7 +832,7 @@ impl ResponsePool { } #[inline] - fn with_body(status: StatusCode, body: Body) -> Response { + fn with_body(status: StatusCode, body: B) -> Response { POOL.with(|pool| ResponsePool::get_response(pool, status, body)) } @@ -903,7 +841,6 @@ impl ResponsePool { let mut p = inner.pool.0.borrow_mut(); if p.len() < 128 { inner.head.clear(); - inner.chunked = None; inner.encoding = None; inner.connection_type = None; inner.response_size = 0; diff --git a/src/service.rs b/src/service.rs index 3aa5d1e41..ac92a0f76 100644 --- a/src/service.rs +++ b/src/service.rs @@ -6,7 +6,7 @@ use futures::future::{ok, Either, FutureResult}; use futures::{Async, AsyncSink, Future, Poll, Sink}; use tokio_io::{AsyncRead, AsyncWrite}; -use body::Body; +use body::MessageBody; use error::{Error, ResponseError}; use h1::{Codec, Message}; use response::Response; @@ -58,11 +58,11 @@ where match req { Ok(r) => Either::A(ok(r)), Err((e, framed)) => { - let mut resp = e.error_response(); - resp.set_body(format!("{}", e)); + let mut res = e.error_response().set_body(format!("{}", e)); + let (res, _body) = res.replace_body(()); Either::B(SendErrorFut { framed: Some(framed), - res: Some(resp.into()), + res: Some(res.into()), err: Some(e), _t: PhantomData, }) @@ -109,30 +109,30 @@ where } } -pub struct SendResponse(PhantomData<(T,)>); +pub struct SendResponse(PhantomData<(T, B)>); -impl Default for SendResponse -where - T: AsyncRead + AsyncWrite, -{ +impl Default for SendResponse { fn default() -> Self { SendResponse(PhantomData) } } -impl SendResponse +impl SendResponse where T: AsyncRead + AsyncWrite, + B: MessageBody, { pub fn send( mut framed: Framed, - mut res: Response, + res: Response, ) -> impl Future, Error = Error> { - // init codec - framed.get_codec_mut().prepare_te(&mut res); - // extract body from response - let body = res.replace_body(Body::Empty); + let (mut res, body) = res.replace_body(()); + + // init codec + framed + .get_codec_mut() + .prepare_te(&mut res.head_mut(), &mut body.length()); // write response SendResponseFut { @@ -143,15 +143,16 @@ where } } -impl NewService for SendResponse +impl NewService for SendResponse where T: AsyncRead + AsyncWrite, + B: MessageBody, { - type Request = (Response, Framed); + type Request = (Response, Framed); type Response = Framed; type Error = Error; type InitError = (); - type Service = SendResponse; + type Service = SendResponse; type Future = FutureResult; fn new_service(&self) -> Self::Future { @@ -159,22 +160,25 @@ where } } -impl Service for SendResponse +impl Service for SendResponse where T: AsyncRead + AsyncWrite, + B: MessageBody, { - type Request = (Response, Framed); + type Request = (Response, Framed); type Response = Framed; type Error = Error; - type Future = SendResponseFut; + type Future = SendResponseFut; fn poll_ready(&mut self) -> Poll<(), Self::Error> { Ok(Async::Ready(())) } - fn call(&mut self, (mut res, mut framed): Self::Request) -> Self::Future { - framed.get_codec_mut().prepare_te(&mut res); - let body = res.replace_body(Body::Empty); + fn call(&mut self, (res, mut framed): Self::Request) -> Self::Future { + let (mut res, body) = res.replace_body(()); + framed + .get_codec_mut() + .prepare_te(res.head_mut(), &mut body.length()); SendResponseFut { res: Some(Message::Item(res)), body: Some(body), @@ -183,73 +187,69 @@ where } } -pub struct SendResponseFut { +pub struct SendResponseFut { res: Option>, - body: Option, + body: Option, framed: Option>, } -impl Future for SendResponseFut +impl Future for SendResponseFut where T: AsyncRead + AsyncWrite, + B: MessageBody, { type Item = Framed; type Error = Error; fn poll(&mut self) -> Poll { - // send response - if self.res.is_some() { + loop { + let mut body_ready = self.body.is_some(); let framed = self.framed.as_mut().unwrap(); - if !framed.is_write_buf_full() { - if let Some(res) = self.res.take() { - framed.force_send(res)?; - } - } - } - // send body - if self.res.is_none() && self.body.is_some() { - let framed = self.framed.as_mut().unwrap(); - if !framed.is_write_buf_full() { - let body = self.body.take().unwrap(); - match body { - Body::Empty => (), - Body::Streaming(mut stream) => loop { - match stream.poll()? { - Async::Ready(item) => { - let done = item.is_none(); - framed.force_send(Message::Chunk(item.into()))?; - if !done { - if !framed.is_write_buf_full() { - continue; - } else { - self.body = Some(Body::Streaming(stream)); - break; - } - } - } - Async::NotReady => { - self.body = Some(Body::Streaming(stream)); - break; + // send body + if self.res.is_none() && self.body.is_some() { + while body_ready && self.body.is_some() && !framed.is_write_buf_full() { + match self.body.as_mut().unwrap().poll_next()? { + Async::Ready(item) => { + // body is done + if item.is_none() { + let _ = self.body.take(); } + framed.force_send(Message::Chunk(item))?; } - }, - Body::Binary(mut bin) => { - framed.force_send(Message::Chunk(Some(bin.take())))?; - framed.force_send(Message::Chunk(None))?; + Async::NotReady => body_ready = false, } } } - } - // flush - match self.framed.as_mut().unwrap().poll_complete()? { - Async::Ready(_) => if self.res.is_some() || self.body.is_some() { - return self.poll(); - }, - Async::NotReady => return Ok(Async::NotReady), - } + // flush write buffer + if !framed.is_write_buf_empty() { + match framed.poll_complete()? { + Async::Ready(_) => if body_ready { + continue; + } else { + return Ok(Async::NotReady); + }, + Async::NotReady => return Ok(Async::NotReady), + } + } - Ok(Async::Ready(self.framed.take().unwrap())) + // send response + if let Some(res) = self.res.take() { + framed.force_send(res)?; + continue; + } + + if self.body.is_some() { + if body_ready { + continue; + } else { + return Ok(Async::NotReady); + } + } else { + break; + } + } + return Ok(Async::Ready(self.framed.take().unwrap())); } } From 8fea1367c739224f6365185a6ec6b1c4efdd5a8f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 18 Nov 2018 13:48:42 -0800 Subject: [PATCH 074/208] re-introduce Body type, use Body as default body type for Response --- src/body.rs | 371 ++++++++++++++++++++--------------------- src/client/pipeline.rs | 4 +- src/client/request.rs | 9 +- src/error.rs | 5 +- src/h1/client.rs | 6 +- src/h1/codec.rs | 10 +- src/h1/dispatcher.rs | 18 +- src/h1/encoder.rs | 4 +- src/lib.rs | 5 +- src/response.rs | 149 ++++++++--------- src/service.rs | 4 +- src/test.rs | 7 +- src/ws/codec.rs | 5 +- src/ws/frame.rs | 5 +- tests/test_server.rs | 22 +-- tests/test_ws.rs | 20 +-- 16 files changed, 309 insertions(+), 335 deletions(-) diff --git a/src/body.rs b/src/body.rs index 3b4e0113d..a44bc6038 100644 --- a/src/body.rs +++ b/src/body.rs @@ -1,22 +1,19 @@ -use std::mem; -use std::sync::Arc; +use std::marker::PhantomData; +use std::{fmt, mem}; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll, Stream}; use error::{Error, PayloadError}; -/// Type represent streaming body -pub type BodyStream = Box>; - /// Type represent streaming payload pub type PayloadStream = Box>; -#[derive(Debug)] +#[derive(Debug, PartialEq)] /// Different type of body pub enum BodyLength { None, - Zero, + Empty, Sized(usize), Sized64(u64), Chunked, @@ -32,7 +29,7 @@ pub trait MessageBody { impl MessageBody for () { fn length(&self) -> BodyLength { - BodyLength::Zero + BodyLength::Empty } fn poll_next(&mut self) -> Poll, Error> { @@ -40,150 +37,129 @@ impl MessageBody for () { } } -/// Represents various types of binary body. -/// `Content-Length` header is set to length of the body. -#[derive(Debug, PartialEq)] -pub enum Binary { - /// Bytes body +/// Represents various types of http message body. +pub enum Body { + /// Empty response. `Content-Length` header is not set. + None, + /// Zero sized response body. `Content-Length` header is set to `0`. + Empty, + /// Specific response body. Bytes(Bytes), - /// Static slice - Slice(&'static [u8]), - /// Shared string body - #[doc(hidden)] - SharedString(Arc), - /// Shared vec body - SharedVec(Arc>), + /// Generic message body. + Message(Box), } -impl Binary { - #[inline] - /// Returns `true` if body is empty - pub fn is_empty(&self) -> bool { - self.len() == 0 +impl Body { + /// Create body from slice (copy) + pub fn from_slice(s: &[u8]) -> Body { + Body::Bytes(Bytes::from(s)) } - #[inline] - /// Length of body in bytes - pub fn len(&self) -> usize { - match *self { - Binary::Bytes(ref bytes) => bytes.len(), - Binary::Slice(slice) => slice.len(), - Binary::SharedString(ref s) => s.len(), - Binary::SharedVec(ref s) => s.len(), - } - } - - /// Create binary body from slice - pub fn from_slice(s: &[u8]) -> Binary { - Binary::Bytes(Bytes::from(s)) - } - - /// Convert Binary to a Bytes instance - pub fn take(&mut self) -> Bytes { - mem::replace(self, Binary::Slice(b"")).into() + /// Create body from generic message body. + pub fn from_message(body: B) -> Body { + Body::Message(Box::new(body)) } } -impl Clone for Binary { - fn clone(&self) -> Binary { - match *self { - Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()), - Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)), - Binary::SharedString(ref s) => Binary::SharedString(s.clone()), - Binary::SharedVec(ref s) => Binary::SharedVec(s.clone()), - } - } -} - -impl Into for Binary { - fn into(self) -> Bytes { +impl MessageBody for Body { + fn length(&self) -> BodyLength { match self { - Binary::Bytes(bytes) => bytes, - Binary::Slice(slice) => Bytes::from(slice), - Binary::SharedString(s) => Bytes::from(s.as_str()), - Binary::SharedVec(s) => Bytes::from(AsRef::<[u8]>::as_ref(s.as_ref())), + Body::None => BodyLength::None, + Body::Empty => BodyLength::Empty, + Body::Bytes(ref bin) => BodyLength::Sized(bin.len()), + Body::Message(ref body) => body.length(), + } + } + + fn poll_next(&mut self) -> Poll, Error> { + match self { + Body::None => Ok(Async::Ready(None)), + Body::Empty => Ok(Async::Ready(None)), + Body::Bytes(ref mut bin) => { + if bin.len() == 0 { + Ok(Async::Ready(None)) + } else { + Ok(Async::Ready(Some(bin.slice_to(bin.len())))) + } + } + Body::Message(ref mut body) => body.poll_next(), } } } -impl From<&'static str> for Binary { - fn from(s: &'static str) -> Binary { - Binary::Slice(s.as_ref()) - } -} - -impl From<&'static [u8]> for Binary { - fn from(s: &'static [u8]) -> Binary { - Binary::Slice(s) - } -} - -impl From> for Binary { - fn from(vec: Vec) -> Binary { - Binary::Bytes(Bytes::from(vec)) - } -} - -impl From for Binary { - fn from(s: String) -> Binary { - Binary::Bytes(Bytes::from(s)) - } -} - -impl<'a> From<&'a String> for Binary { - fn from(s: &'a String) -> Binary { - Binary::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s))) - } -} - -impl From for Binary { - fn from(s: Bytes) -> Binary { - Binary::Bytes(s) - } -} - -impl From for Binary { - fn from(s: BytesMut) -> Binary { - Binary::Bytes(s.freeze()) - } -} - -impl From> for Binary { - fn from(body: Arc) -> Binary { - Binary::SharedString(body) - } -} - -impl<'a> From<&'a Arc> for Binary { - fn from(body: &'a Arc) -> Binary { - Binary::SharedString(Arc::clone(body)) - } -} - -impl From>> for Binary { - fn from(body: Arc>) -> Binary { - Binary::SharedVec(body) - } -} - -impl<'a> From<&'a Arc>> for Binary { - fn from(body: &'a Arc>) -> Binary { - Binary::SharedVec(Arc::clone(body)) - } -} - -impl AsRef<[u8]> for Binary { - #[inline] - fn as_ref(&self) -> &[u8] { +impl PartialEq for Body { + fn eq(&self, other: &Body) -> bool { match *self { - Binary::Bytes(ref bytes) => bytes.as_ref(), - Binary::Slice(slice) => slice, - Binary::SharedString(ref s) => s.as_bytes(), - Binary::SharedVec(ref s) => s.as_ref().as_ref(), + Body::None => match *other { + Body::None => true, + _ => false, + }, + Body::Empty => match *other { + Body::Empty => true, + _ => false, + }, + Body::Bytes(ref b) => match *other { + Body::Bytes(ref b2) => b == b2, + _ => false, + }, + Body::Message(_) => false, } } } +impl fmt::Debug for Body { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Body::None => write!(f, "Body::None"), + Body::Empty => write!(f, "Body::Zero"), + Body::Bytes(ref b) => write!(f, "Body::Bytes({:?})", b), + Body::Message(_) => write!(f, "Body::Message(_)"), + } + } +} + +impl From<&'static str> for Body { + fn from(s: &'static str) -> Body { + Body::Bytes(Bytes::from_static(s.as_ref())) + } +} + +impl From<&'static [u8]> for Body { + fn from(s: &'static [u8]) -> Body { + Body::Bytes(Bytes::from_static(s.as_ref())) + } +} + +impl From> for Body { + fn from(vec: Vec) -> Body { + Body::Bytes(Bytes::from(vec)) + } +} + +impl From for Body { + fn from(s: String) -> Body { + s.into_bytes().into() + } +} + +impl<'a> From<&'a String> for Body { + fn from(s: &'a String) -> Body { + Body::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s))) + } +} + +impl From for Body { + fn from(s: Bytes) -> Body { + Body::Bytes(s) + } +} + +impl From for Body { + fn from(s: BytesMut) -> Body { + Body::Bytes(s.freeze()) + } +} + impl MessageBody for Bytes { fn length(&self) -> BodyLength { BodyLength::Sized(self.len()) @@ -279,26 +255,62 @@ impl MessageBody for String { } } -#[doc(hidden)] -pub struct MessageBodyStream { +/// Type represent streaming body. +/// Response does not contain `content-length` header and appropriate transfer encoding is used. +pub struct BodyStream { stream: S, + _t: PhantomData, } -impl MessageBodyStream +impl BodyStream where - S: Stream, + S: Stream, + E: Into, { pub fn new(stream: S) -> Self { - MessageBodyStream { stream } + BodyStream { + stream, + _t: PhantomData, + } } } -impl MessageBody for MessageBodyStream +impl MessageBody for BodyStream +where + S: Stream, + E: Into, +{ + fn length(&self) -> BodyLength { + BodyLength::Chunked + } + + fn poll_next(&mut self) -> Poll, Error> { + self.stream.poll().map_err(|e| e.into()) + } +} + +/// Type represent streaming body. This body implementation should be used +/// if total size of stream is known. Data get sent as is without using transfer encoding. +pub struct SizedStream { + size: usize, + stream: S, +} + +impl SizedStream +where + S: Stream, +{ + pub fn new(size: usize, stream: S) -> Self { + SizedStream { size, stream } + } +} + +impl MessageBody for SizedStream where S: Stream, { fn length(&self) -> BodyLength { - BodyLength::Chunked + BodyLength::Sized(self.size) } fn poll_next(&mut self) -> Poll, Error> { @@ -310,78 +322,61 @@ where mod tests { use super::*; - #[test] - fn test_is_empty() { - assert_eq!(Binary::from("").is_empty(), true); - assert_eq!(Binary::from("test").is_empty(), false); + impl Body { + pub(crate) fn get_ref(&self) -> &[u8] { + match *self { + Body::Bytes(ref bin) => &bin, + _ => panic!(), + } + } } #[test] fn test_static_str() { - assert_eq!(Binary::from("test").len(), 4); - assert_eq!(Binary::from("test").as_ref(), b"test"); + assert_eq!(Body::from("").length(), BodyLength::Sized(0)); + assert_eq!(Body::from("test").length(), BodyLength::Sized(4)); + assert_eq!(Body::from("test").get_ref(), b"test"); } #[test] fn test_static_bytes() { - assert_eq!(Binary::from(b"test".as_ref()).len(), 4); - assert_eq!(Binary::from(b"test".as_ref()).as_ref(), b"test"); - assert_eq!(Binary::from_slice(b"test".as_ref()).len(), 4); - assert_eq!(Binary::from_slice(b"test".as_ref()).as_ref(), b"test"); + assert_eq!(Body::from(b"test".as_ref()).length(), BodyLength::Sized(4)); + assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test"); + assert_eq!( + Body::from_slice(b"test".as_ref()).length(), + BodyLength::Sized(4) + ); + assert_eq!(Body::from_slice(b"test".as_ref()).get_ref(), b"test"); } #[test] fn test_vec() { - assert_eq!(Binary::from(Vec::from("test")).len(), 4); - assert_eq!(Binary::from(Vec::from("test")).as_ref(), b"test"); + assert_eq!(Body::from(Vec::from("test")).length(), BodyLength::Sized(4)); + assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test"); } #[test] fn test_bytes() { - assert_eq!(Binary::from(Bytes::from("test")).len(), 4); - assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test"); - } - - #[test] - fn test_arc_string() { - let b = Arc::new("test".to_owned()); - assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b.clone()).as_ref(), b"test"); - assert_eq!(Binary::from(&b).len(), 4); - assert_eq!(Binary::from(&b).as_ref(), b"test"); + assert_eq!( + Body::from(Bytes::from("test")).length(), + BodyLength::Sized(4) + ); + assert_eq!(Body::from(Bytes::from("test")).get_ref(), b"test"); } #[test] fn test_string() { let b = "test".to_owned(); - assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b.clone()).as_ref(), b"test"); - assert_eq!(Binary::from(&b).len(), 4); - assert_eq!(Binary::from(&b).as_ref(), b"test"); - } - - #[test] - fn test_shared_vec() { - let b = Arc::new(Vec::from(&b"test"[..])); - assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b.clone()).as_ref(), &b"test"[..]); - assert_eq!(Binary::from(&b).len(), 4); - assert_eq!(Binary::from(&b).as_ref(), &b"test"[..]); + assert_eq!(Body::from(b.clone()).length(), BodyLength::Sized(4)); + assert_eq!(Body::from(b.clone()).get_ref(), b"test"); + assert_eq!(Body::from(&b).length(), BodyLength::Sized(4)); + assert_eq!(Body::from(&b).get_ref(), b"test"); } #[test] fn test_bytes_mut() { let b = BytesMut::from("test"); - assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b).as_ref(), b"test"); - } - - #[test] - fn test_binary_into() { - let bytes = Bytes::from_static(b"test"); - let b: Bytes = Binary::from("test").into(); - assert_eq!(b, bytes); - let b: Bytes = Binary::from(bytes.clone()).into(); - assert_eq!(b, bytes); + assert_eq!(Body::from(b.clone()).length(), BodyLength::Sized(4)); + assert_eq!(Body::from(b).get_ref(), b"test"); } } diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 56c22bd2a..8be860ae9 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -36,7 +36,9 @@ where .and_then(|framed| framed.send((head, len).into()).from_err()) // send request body .and_then(move |framed| match body.length() { - BodyLength::None | BodyLength::Zero => Either::A(ok(framed)), + BodyLength::None | BodyLength::Empty | BodyLength::Sized(0) => { + Either::A(ok(framed)) + } _ => Either::B(SendBody::new(body, framed)), }) // read response and init read body diff --git a/src/client/request.rs b/src/client/request.rs index f0b76ed04..dd418a6fe 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -9,7 +9,7 @@ use futures::{Future, Stream}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use urlcrate::Url; -use body::{MessageBody, MessageBodyStream}; +use body::{BodyStream, MessageBody}; use error::Error; use header::{self, Header, IntoHeaderValue}; use http::{ @@ -534,14 +534,15 @@ impl ClientRequestBuilder { /// Set an streaming body and generate `ClientRequest`. /// /// `ClientRequestBuilder` can not be used after this call. - pub fn stream( + pub fn stream( &mut self, stream: S, ) -> Result, HttpError> where - S: Stream, + S: Stream, + E: Into + 'static, { - self.body(MessageBodyStream::new(stream)) + self.body(BodyStream::new(stream)) } /// Set an empty body and generate `ClientRequest`. diff --git a/src/error.rs b/src/error.rs index 1f70396c3..2e0c2382a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,6 +20,7 @@ use tokio_timer::Error as TimerError; // re-exports pub use cookie::ParseError as CookieParseError; +use body::Body; use response::{Response, ResponseParts}; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) @@ -100,10 +101,10 @@ impl Error { } /// Converts error to a response instance and set error message as response body - pub fn response_with_message(self) -> Response { + pub fn response_with_message(self) -> Response { let message = format!("{}", self); let resp: Response = self.into(); - resp.set_body(message) + resp.set_body(Body::from(message)) } } diff --git a/src/h1/client.rs b/src/h1/client.rs index ace504668..2cb2fb2e0 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -7,7 +7,7 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; use super::encoder::RequestEncoder; use super::{Message, MessageType}; -use body::{Binary, BodyLength}; +use body::BodyLength; use client::ClientResponse; use config::ServiceConfig; use error::{ParseError, PayloadError}; @@ -167,7 +167,7 @@ impl ClientCodecInner { BodyLength::Chunked => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } - BodyLength::Zero => { + BodyLength::Empty => { len_is_set = false; buffer.extend_from_slice(b"\r\n") } @@ -183,7 +183,7 @@ impl ClientCodecInner { TRANSFER_ENCODING => continue, CONTENT_LENGTH => match length { BodyLength::None => (), - BodyLength::Zero => len_is_set = true, + BodyLength::Empty => len_is_set = true, _ => continue, }, DATE => has_date = true, diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 6bc20b180..b4a62a50e 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -8,7 +8,7 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; use super::encoder::ResponseEncoder; use super::{Message, MessageType}; -use body::{Binary, BodyLength}; +use body::BodyLength; use config::ServiceConfig; use error::ParseError; use helpers; @@ -106,7 +106,7 @@ impl Codec { fn encode_response( &mut self, - mut msg: Response, + mut msg: Response<()>, buffer: &mut BytesMut, ) -> io::Result<()> { let ka = self.flags.contains(Flags::KEEPALIVE_ENABLED) && msg @@ -149,7 +149,7 @@ impl Codec { BodyLength::Chunked => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } - BodyLength::Zero => { + BodyLength::Empty => { len_is_set = false; buffer.extend_from_slice(b"\r\n") } @@ -174,7 +174,7 @@ impl Codec { TRANSFER_ENCODING => continue, CONTENT_LENGTH => match self.te.length { BodyLength::None => (), - BodyLength::Zero => { + BodyLength::Empty => { len_is_set = true; } _ => continue, @@ -268,7 +268,7 @@ impl Decoder for Codec { } impl Encoder for Codec { - type Item = Message; + type Item = Message>; type Error = io::Error; fn encode( diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 508962b43..ff9d54e66 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -65,7 +65,7 @@ where enum DispatcherMessage { Item(Request), - Error(Response), + Error(Response<()>), } enum State { @@ -190,7 +190,7 @@ where fn send_response( &mut self, - message: Response, + message: Response<()>, body: B1, ) -> Result, DispatchError> { self.framed @@ -206,7 +206,7 @@ where .set(Flags::KEEPALIVE, self.framed.get_codec().keepalive()); self.flags.remove(Flags::FLUSHED); match body.length() { - BodyLength::None | BodyLength::Zero => Ok(State::None), + BodyLength::None | BodyLength::Empty => Ok(State::None), _ => Ok(State::SendPayload(body)), } } @@ -351,7 +351,7 @@ where ); self.flags.insert(Flags::DISCONNECTED); self.messages.push_back(DispatcherMessage::Error( - Response::InternalServerError().finish(), + Response::InternalServerError().finish().drop_body(), )); self.error = Some(DispatchError::InternalError); break; @@ -364,7 +364,7 @@ where error!("Internal server error: unexpected eof"); self.flags.insert(Flags::DISCONNECTED); self.messages.push_back(DispatcherMessage::Error( - Response::InternalServerError().finish(), + Response::InternalServerError().finish().drop_body(), )); self.error = Some(DispatchError::InternalError); break; @@ -389,7 +389,7 @@ where // Malformed requests should be responded with 400 self.messages.push_back(DispatcherMessage::Error( - Response::BadRequest().finish(), + Response::BadRequest().finish().drop_body(), )); self.flags.insert(Flags::DISCONNECTED); self.error = Some(e.into()); @@ -440,8 +440,10 @@ where // timeout on first request (slow request) return 408 trace!("Slow request timeout"); self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); - let _ = self - .send_response(Response::RequestTimeout().finish(), ()); + let _ = self.send_response( + Response::RequestTimeout().finish().drop_body(), + (), + ); self.state = State::None; } } else if let Some(deadline) = self.config.keep_alive_expire() { diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index aee17c1f0..fd52d7316 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -8,7 +8,7 @@ use bytes::{Bytes, BytesMut}; use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; -use body::{Binary, BodyLength}; +use body::BodyLength; use header::ContentEncoding; use http::Method; use message::{RequestHead, ResponseHead}; @@ -52,7 +52,7 @@ impl ResponseEncoder { ) { self.head = head; let transfer = match length { - BodyLength::Zero => { + BodyLength::Empty => { match resp.status { StatusCode::NO_CONTENT | StatusCode::CONTINUE diff --git a/src/lib.rs b/src/lib.rs index 4b43cae40..615f04010 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,7 +109,7 @@ extern crate serde_derive; #[cfg(feature = "ssl")] extern crate openssl; -mod body; +pub mod body; pub mod client; mod config; mod extensions; @@ -129,7 +129,7 @@ pub mod h1; pub(crate) mod helpers; pub mod test; pub mod ws; -pub use body::{Binary, MessageBody}; +pub use body::{Body, MessageBody}; pub use error::{Error, ResponseError, Result}; pub use extensions::Extensions; pub use httpmessage::HttpMessage; @@ -150,7 +150,6 @@ pub mod dev { //! use actix_http::dev::*; //! ``` - pub use body::BodyStream; pub use httpmessage::{MessageBody, Readlines, UrlEncoded}; pub use json::JsonBody; pub use payload::{Payload, PayloadBuffer}; diff --git a/src/response.rs b/src/response.rs index b3e599826..a6f7c81c2 100644 --- a/src/response.rs +++ b/src/response.rs @@ -12,9 +12,9 @@ use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version}; use serde::Serialize; use serde_json; -use body::{MessageBody, MessageBodyStream}; +use body::{Body, BodyStream, MessageBody}; use error::Error; -use header::{ContentEncoding, Header, IntoHeaderValue}; +use header::{Header, IntoHeaderValue}; use message::{Head, MessageFlags, ResponseHead}; /// max write buffer size 64k @@ -32,9 +32,9 @@ pub enum ConnectionType { } /// An HTTP Response -pub struct Response(Box, B); +pub struct Response(Box, B); -impl Response<()> { +impl Response { /// Create http response builder with specific status. #[inline] pub fn build(status: StatusCode) -> ResponseBuilder { @@ -50,7 +50,7 @@ impl Response<()> { /// Constructs a response #[inline] pub fn new(status: StatusCode) -> Response { - ResponsePool::with_body(status, ()) + ResponsePool::with_body(status, Body::Empty) } /// Constructs an error response @@ -242,6 +242,11 @@ impl Response { Response(self.0, body) } + /// Drop request's body + pub fn drop_body(self) -> Response<()> { + Response(self.0, ()) + } + /// Set a body and return previous body value pub fn replace_body(self, body: B2) -> (Response, B) { (Response(self.0, body), self.1) @@ -252,7 +257,7 @@ impl Response { self.get_ref().response_size } - /// Set content encoding + /// Set response size pub(crate) fn set_response_size(&mut self, size: u64) { self.get_mut().response_size = size; } @@ -266,7 +271,7 @@ impl Response { } pub(crate) fn from_parts(parts: ResponseParts) -> Response { - Response(Box::new(InnerResponse::from_parts(parts)), ()) + Response(Box::new(InnerResponse::from_parts(parts)), Body::Empty) } } @@ -279,7 +284,6 @@ impl fmt::Debug for Response { self.get_ref().head.status, self.get_ref().head.reason.unwrap_or("") ); - let _ = writeln!(f, " encoding: {:?}", self.get_ref().encoding); let _ = writeln!(f, " headers:"); for (key, val) in self.get_ref().head.headers.iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); @@ -396,20 +400,6 @@ impl ResponseBuilder { self } - /// Set content encoding. - /// - /// By default `ContentEncoding::Auto` is used, which automatically - /// negotiates content encoding based on request's `Accept-Encoding` - /// headers. To enforce specific encoding, use specific - /// ContentEncoding` value. - #[inline] - pub fn content_encoding(&mut self, enc: ContentEncoding) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.encoding = Some(enc); - } - self - } - /// Set connection type #[inline] #[doc(hidden)] @@ -558,7 +548,14 @@ impl ResponseBuilder { /// Set a body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. - pub fn body(&mut self, body: B) -> Response { + pub fn body>(&mut self, body: B) -> Response { + self.message_body(body.into()) + } + + /// Set a body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + pub fn message_body(&mut self, body: B) -> Response { let mut error = if let Some(e) = self.err.take() { Some(Error::from(e)) } else { @@ -589,25 +586,25 @@ impl ResponseBuilder { /// Set a streaming body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. - pub fn streaming(&mut self, stream: S) -> Response + pub fn streaming(&mut self, stream: S) -> Response where S: Stream + 'static, - E: Into, + E: Into + 'static, { - self.body(MessageBodyStream::new(stream.map_err(|e| e.into()))) + self.body(Body::from_message(BodyStream::new(stream))) } /// Set a json body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. - pub fn json(&mut self, value: T) -> Response { + pub fn json(&mut self, value: T) -> Response { self.json2(&value) } /// Set a json body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. - pub fn json2(&mut self, value: &T) -> Response { + pub fn json2(&mut self, value: &T) -> Response { match serde_json::to_string(value) { Ok(body) => { let contains = if let Some(parts) = parts(&mut self.response, &self.err) @@ -620,12 +617,9 @@ impl ResponseBuilder { self.header(header::CONTENT_TYPE, "application/json"); } - self.body(body) - } - Err(e) => { - let mut res: Response = Error::from(e).into(); - res.replace_body(String::new()).0 + self.body(Body::from(body)) } + Err(e) => Error::from(e).into(), } } @@ -633,8 +627,8 @@ impl ResponseBuilder { /// Set an empty body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. - pub fn finish(&mut self) -> Response<()> { - self.body(()) + pub fn finish(&mut self) -> Response { + self.body(Body::Empty) } /// This method construct new `ResponseBuilder` @@ -675,7 +669,7 @@ impl From for Response { } } -impl From<&'static str> for Response<&'static str> { +impl From<&'static str> for Response { fn from(val: &'static str) -> Self { Response::Ok() .content_type("text/plain; charset=utf-8") @@ -683,7 +677,7 @@ impl From<&'static str> for Response<&'static str> { } } -impl From<&'static [u8]> for Response<&'static [u8]> { +impl From<&'static [u8]> for Response { fn from(val: &'static [u8]) -> Self { Response::Ok() .content_type("application/octet-stream") @@ -691,7 +685,7 @@ impl From<&'static [u8]> for Response<&'static [u8]> { } } -impl From for Response { +impl From for Response { fn from(val: String) -> Self { Response::Ok() .content_type("text/plain; charset=utf-8") @@ -699,7 +693,15 @@ impl From for Response { } } -impl From for Response { +impl<'a> From<&'a String> for Response { + fn from(val: &'a String) -> Self { + Response::Ok() + .content_type("text/plain; charset=utf-8") + .body(val) + } +} + +impl From for Response { fn from(val: Bytes) -> Self { Response::Ok() .content_type("application/octet-stream") @@ -707,7 +709,7 @@ impl From for Response { } } -impl From for Response { +impl From for Response { fn from(val: BytesMut) -> Self { Response::Ok() .content_type("application/octet-stream") @@ -717,7 +719,6 @@ impl From for Response { struct InnerResponse { head: ResponseHead, - encoding: Option, connection_type: Option, write_capacity: usize, response_size: u64, @@ -727,7 +728,6 @@ struct InnerResponse { pub(crate) struct ResponseParts { head: ResponseHead, - encoding: Option, connection_type: Option, error: Option, } @@ -744,7 +744,6 @@ impl InnerResponse { flags: MessageFlags::empty(), }, pool, - encoding: None, connection_type: None, response_size: 0, write_capacity: MAX_WRITE_BUFFER_SIZE, @@ -756,7 +755,6 @@ impl InnerResponse { fn into_parts(self) -> ResponseParts { ResponseParts { head: self.head, - encoding: self.encoding, connection_type: self.connection_type, error: self.error, } @@ -765,7 +763,6 @@ impl InnerResponse { fn from_parts(parts: ResponseParts) -> InnerResponse { InnerResponse { head: parts.head, - encoding: parts.encoding, connection_type: parts.connection_type, response_size: 0, write_capacity: MAX_WRITE_BUFFER_SIZE, @@ -841,7 +838,6 @@ impl ResponsePool { let mut p = inner.pool.0.borrow_mut(); if p.len() < 128 { inner.head.clear(); - inner.encoding = None; inner.connection_type = None; inner.response_size = 0; inner.error = None; @@ -854,11 +850,10 @@ impl ResponsePool { #[cfg(test)] mod tests { use super::*; - use body::Binary; + use body::Body; use http; use http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; - use header::ContentEncoding; // use test::TestRequest; #[test] @@ -953,24 +948,24 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } - #[test] - fn test_content_encoding() { - let resp = Response::build(StatusCode::OK).finish(); - assert_eq!(resp.content_encoding(), None); + // #[test] + // fn test_content_encoding() { + // let resp = Response::build(StatusCode::OK).finish(); + // assert_eq!(resp.content_encoding(), None); - #[cfg(feature = "brotli")] - { - let resp = Response::build(StatusCode::OK) - .content_encoding(ContentEncoding::Br) - .finish(); - assert_eq!(resp.content_encoding(), Some(ContentEncoding::Br)); - } + // #[cfg(feature = "brotli")] + // { + // let resp = Response::build(StatusCode::OK) + // .content_encoding(ContentEncoding::Br) + // .finish(); + // assert_eq!(resp.content_encoding(), Some(ContentEncoding::Br)); + // } - let resp = Response::build(StatusCode::OK) - .content_encoding(ContentEncoding::Gzip) - .finish(); - assert_eq!(resp.content_encoding(), Some(ContentEncoding::Gzip)); - } + // let resp = Response::build(StatusCode::OK) + // .content_encoding(ContentEncoding::Gzip) + // .finish(); + // assert_eq!(resp.content_encoding(), Some(ContentEncoding::Gzip)); + // } #[test] fn test_json() { @@ -1020,15 +1015,6 @@ mod tests { ); } - impl Body { - pub(crate) fn bin_ref(&self) -> &Binary { - match *self { - Body::Binary(ref bin) => bin, - _ => panic!(), - } - } - } - #[test] fn test_into_response() { let resp: Response = "test".into(); @@ -1038,7 +1024,7 @@ mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from("test")); + assert_eq!(resp.body().get_ref(), b"test"); let resp: Response = b"test".as_ref().into(); assert_eq!(resp.status(), StatusCode::OK); @@ -1047,7 +1033,7 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref())); + assert_eq!(resp.body().get_ref(), b"test"); let resp: Response = "test".to_owned().into(); assert_eq!(resp.status(), StatusCode::OK); @@ -1056,7 +1042,7 @@ mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned())); + assert_eq!(resp.body().get_ref(), b"test"); let resp: Response = (&"test".to_owned()).into(); assert_eq!(resp.status(), StatusCode::OK); @@ -1065,7 +1051,7 @@ mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned())); + assert_eq!(resp.body().get_ref(), b"test"); let b = Bytes::from_static(b"test"); let resp: Response = b.into(); @@ -1075,10 +1061,7 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body().bin_ref(), - &Binary::from(Bytes::from_static(b"test")) - ); + assert_eq!(resp.body().get_ref(), b"test"); let b = Bytes::from_static(b"test"); let resp: Response = b.into(); @@ -1088,7 +1071,7 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test"))); + assert_eq!(resp.body().get_ref(), b"test"); let b = BytesMut::from("test"); let resp: Response = b.into(); @@ -1098,7 +1081,7 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test"))); + assert_eq!(resp.body().get_ref(), b"test"); } #[test] diff --git a/src/service.rs b/src/service.rs index ac92a0f76..51a16b48b 100644 --- a/src/service.rs +++ b/src/service.rs @@ -72,7 +72,7 @@ where } pub struct SendErrorFut { - res: Option>, + res: Option>>, framed: Option>, err: Option, _t: PhantomData, @@ -188,7 +188,7 @@ where } pub struct SendResponseFut { - res: Option>, + res: Option>>, body: Option, framed: Option>, } diff --git a/src/test.rs b/src/test.rs index 439749343..c6f258a77 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,6 +4,7 @@ use std::str::FromStr; use actix::System; +use bytes::Bytes; use cookie::Cookie; use futures::Future; use http::header::HeaderName; @@ -11,7 +12,6 @@ use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; use net2::TcpBuilder; use tokio::runtime::current_thread::Runtime; -use body::Binary; use header::{Header, IntoHeaderValue}; use payload::Payload; use request::Request; @@ -362,10 +362,9 @@ impl TestRequest { } /// Set request payload - pub fn set_payload>(mut self, data: B) -> Self { - let mut data = data.into(); + pub fn set_payload>(mut self, data: B) -> Self { let mut payload = Payload::empty(); - payload.unread_data(data.take()); + payload.unread_data(data.into()); self.payload = Some(payload); self } diff --git a/src/ws/codec.rs b/src/ws/codec.rs index 5bdc58199..10c505287 100644 --- a/src/ws/codec.rs +++ b/src/ws/codec.rs @@ -1,10 +1,9 @@ -use bytes::BytesMut; +use bytes::{Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; use super::frame::Parser; use super::proto::{CloseReason, OpCode}; use super::ProtocolError; -use body::Binary; /// `WebSocket` Message #[derive(Debug, PartialEq)] @@ -12,7 +11,7 @@ pub enum Message { /// Text message Text(String), /// Binary message - Binary(Binary), + Binary(Bytes), /// Ping message Ping(String), /// Pong message diff --git a/src/ws/frame.rs b/src/ws/frame.rs index ca5f0e892..56d282964 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -1,8 +1,7 @@ use byteorder::{ByteOrder, LittleEndian, NetworkEndian}; -use bytes::{BufMut, BytesMut}; +use bytes::{BufMut, Bytes, BytesMut}; use rand; -use body::Binary; use ws::mask::apply_mask; use ws::proto::{CloseCode, CloseReason, OpCode}; use ws::ProtocolError; @@ -151,7 +150,7 @@ impl Parser { } /// Generate binary representation - pub fn write_message>( + pub fn write_message>( dst: &mut BytesMut, pl: B, op: OpCode, diff --git a/tests/test_server.rs b/tests/test_server.rs index c8de0290d..c499cf635 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -15,7 +15,7 @@ use bytes::Bytes; use futures::future::{self, ok}; use futures::stream::once; -use actix_http::{h1, http, Body, KeepAlive, Request, Response}; +use actix_http::{body, h1, http, Body, Error, KeepAlive, Request, Response}; #[test] fn test_h1_v2() { @@ -349,11 +349,9 @@ fn test_body_length() { .bind("test", addr, move || { h1::H1Service::new(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .content_length(STR.len() as u64) - .body(Body::Streaming(Box::new(body))), - ) + ok::<_, ()>(Response::Ok().body(Body::from_message( + body::SizedStream::new(STR.len(), body), + ))) }).map(|_| ()) }).unwrap() .run() @@ -379,12 +377,8 @@ fn test_body_chunked_explicit() { Server::new() .bind("test", addr, move || { h1::H1Service::new(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .chunked() - .body(Body::Streaming(Box::new(body))), - ) + let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>(Response::Ok().streaming(body)) }).map(|_| ()) }).unwrap() .run() @@ -412,8 +406,8 @@ fn test_body_chunked_implicit() { Server::new() .bind("test", addr, move || { h1::H1Service::new(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::Ok().body(Body::Streaming(Box::new(body)))) + let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>(Response::Ok().streaming(body)) }).map(|_| ()) }).unwrap() .run() diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 85770fa8e..c246d5e47 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -18,7 +18,7 @@ use bytes::{Bytes, BytesMut}; use futures::future::{lazy, ok, Either}; use futures::{Future, IntoFuture, Sink, Stream}; -use actix_http::{h1, ws, ResponseError, ServiceConfig}; +use actix_http::{h1, ws, ResponseError, SendResponse, ServiceConfig}; fn ws_service(req: ws::Frame) -> impl Future { match req { @@ -55,20 +55,19 @@ fn test_simple() { match ws::verify_handshake(&req) { Err(e) => { // validation failed - let resp = e.error_response(); Either::A( - framed - .send(h1::Message::Item(resp)) + SendResponse::send(framed, e.error_response()) .map_err(|_| ()) .map(|_| ()), ) } - Ok(_) => Either::B( - // send response - framed - .send(h1::Message::Item( + Ok(_) => { + Either::B( + // send handshake response + SendResponse::send( + framed, ws::handshake_response(&req).finish(), - )).map_err(|_| ()) + ).map_err(|_| ()) .and_then(|framed| { // start websocket service let framed = @@ -76,7 +75,8 @@ fn test_simple() { ws::Transport::with(framed, ws_service) .map_err(|_| ()) }), - ), + ) + } } } else { panic!() From adad2033141499ee071e75cc3a74eba67b2bcd6a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 18 Nov 2018 17:52:56 -0800 Subject: [PATCH 075/208] refactor encoder/decoder impl --- Cargo.toml | 4 +- src/body.rs | 7 ++-- src/client/pipeline.rs | 2 +- src/client/request.rs | 17 ++++++++- src/client/response.rs | 4 +- src/h1/client.rs | 45 ++++++++++------------ src/h1/codec.rs | 87 +++++++++++++++++++----------------------- src/h1/decoder.rs | 18 +++++++-- src/h1/dispatcher.rs | 35 +++++++---------- src/h1/encoder.rs | 17 ++------- src/lib.rs | 3 +- src/message.rs | 85 ++++++++++++++++++++++++++++++++++++++++- src/request.rs | 4 +- src/response.rs | 86 +++++++++-------------------------------- src/service.rs | 49 +++++++++--------------- src/ws/mod.rs | 4 +- tests/test_server.rs | 22 +++++++---- 17 files changed, 255 insertions(+), 234 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 80d245951..2eef1c50f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,9 +91,11 @@ native-tls = { version="0.2", optional = true } # openssl openssl = { version="0.10", optional = true } -#rustls +# rustls rustls = { version = "^0.14", optional = true } +backtrace="*" + [dev-dependencies] actix-web = "0.7" env_logger = "0.5" diff --git a/src/body.rs b/src/body.rs index a44bc6038..3449448e3 100644 --- a/src/body.rs +++ b/src/body.rs @@ -9,7 +9,7 @@ use error::{Error, PayloadError}; /// Type represent streaming payload pub type PayloadStream = Box>; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Copy, Clone)] /// Different type of body pub enum BodyLength { None, @@ -76,10 +76,11 @@ impl MessageBody for Body { Body::None => Ok(Async::Ready(None)), Body::Empty => Ok(Async::Ready(None)), Body::Bytes(ref mut bin) => { - if bin.len() == 0 { + let len = bin.len(); + if len == 0 { Ok(Async::Ready(None)) } else { - Ok(Async::Ready(Some(bin.slice_to(bin.len())))) + Ok(Async::Ready(Some(bin.split_to(len)))) } } Body::Message(ref mut body) => body.poll_next(), diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 8be860ae9..63551cffa 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -33,7 +33,7 @@ where .from_err() // create Framed and send reqest .map(|io| Framed::new(io, h1::ClientCodec::default())) - .and_then(|framed| framed.send((head, len).into()).from_err()) + .and_then(move |framed| framed.send((head, len).into()).from_err()) // send request body .and_then(move |framed| match body.length() { BodyLength::None | BodyLength::Empty | BodyLength::Sized(0) => { diff --git a/src/client/request.rs b/src/client/request.rs index dd418a6fe..cc65b9db1 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -16,7 +16,7 @@ use http::{ uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, Uri, Version, }; -use message::RequestHead; +use message::{Head, RequestHead}; use super::response::ClientResponse; use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; @@ -365,8 +365,21 @@ impl ClientRequestBuilder { where V: IntoHeaderValue, { + { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_upgrade(); + } + } self.set_header(header::UPGRADE, value) - .set_header(header::CONNECTION, "upgrade") + } + + /// Close connection + #[inline] + pub fn close(&mut self) -> &mut Self { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.force_close(); + } + self } /// Set request's content type diff --git a/src/client/response.rs b/src/client/response.rs index 41c18562a..dc7b13c18 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -8,7 +8,7 @@ use http::{HeaderMap, StatusCode, Version}; use body::PayloadStream; use error::PayloadError; use httpmessage::HttpMessage; -use message::{MessageFlags, ResponseHead}; +use message::{Head, ResponseHead}; use super::pipeline::Payload; @@ -81,7 +81,7 @@ impl ClientResponse { /// Checks if a connection should be kept alive. #[inline] pub fn keep_alive(&self) -> bool { - self.head().flags.contains(MessageFlags::KEEPALIVE) + self.head().keep_alive() } } diff --git a/src/h1/client.rs b/src/h1/client.rs index 2cb2fb2e0..e2d1eefe6 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -16,7 +16,7 @@ use http::header::{ HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, }; use http::{Method, Version}; -use message::{MessagePool, RequestHead}; +use message::{Head, MessagePool, RequestHead}; bitflags! { struct Flags: u8 { @@ -135,7 +135,7 @@ fn prn_version(ver: Version) -> &'static str { } impl ClientCodecInner { - fn encode_response( + fn encode_request( &mut self, msg: RequestHead, length: BodyLength, @@ -146,7 +146,7 @@ impl ClientCodecInner { // status line write!( Writer(buffer), - "{} {} {}", + "{} {} {}\r\n", msg.method, msg.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), prn_version(msg.version) @@ -156,38 +156,26 @@ impl ClientCodecInner { buffer.reserve(msg.headers.len() * AVERAGE_HEADER_SIZE); // content length - let mut len_is_set = true; match length { BodyLength::Sized(len) => helpers::write_content_length(len, buffer), BodyLength::Sized64(len) => { - buffer.extend_from_slice(b"\r\ncontent-length: "); + buffer.extend_from_slice(b"content-length: "); write!(buffer.writer(), "{}", len)?; buffer.extend_from_slice(b"\r\n"); } BodyLength::Chunked => { - buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") - } - BodyLength::Empty => { - len_is_set = false; - buffer.extend_from_slice(b"\r\n") - } - BodyLength::None | BodyLength::Stream => { - buffer.extend_from_slice(b"\r\n") + buffer.extend_from_slice(b"transfer-encoding: chunked\r\n") } + BodyLength::Empty => buffer.extend_from_slice(b"content-length: 0\r\n"), + BodyLength::None | BodyLength::Stream => (), } let mut has_date = false; for (key, value) in &msg.headers { match *key { - TRANSFER_ENCODING => continue, - CONTENT_LENGTH => match length { - BodyLength::None => (), - BodyLength::Empty => len_is_set = true, - _ => continue, - }, + TRANSFER_ENCODING | CONNECTION | CONTENT_LENGTH => continue, DATE => has_date = true, - UPGRADE => self.flags.insert(Flags::UPGRADE), _ => (), } @@ -197,12 +185,19 @@ impl ClientCodecInner { buffer.put_slice(b"\r\n"); } - // set content length - if !len_is_set { - buffer.extend_from_slice(b"content-length: 0\r\n") + // Connection header + if msg.upgrade() { + self.flags.set(Flags::UPGRADE, msg.upgrade()); + buffer.extend_from_slice(b"connection: upgrade\r\n"); + } else if msg.keep_alive() { + if self.version < Version::HTTP_11 { + buffer.extend_from_slice(b"connection: keep-alive\r\n"); + } + } else if self.version >= Version::HTTP_11 { + buffer.extend_from_slice(b"connection: close\r\n"); } - // set date header + // Date header if !has_date { self.config.set_date(buffer); } else { @@ -276,7 +271,7 @@ impl Encoder for ClientCodec { ) -> Result<(), Self::Error> { match item { Message::Item((msg, btype)) => { - self.inner.encode_response(msg, btype, dst)?; + self.inner.encode_request(msg, btype, dst)?; } Message::Chunk(Some(bytes)) => { self.inner.te.encode(bytes.as_ref(), dst)?; diff --git a/src/h1/codec.rs b/src/h1/codec.rs index b4a62a50e..117c8cde1 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -13,8 +13,8 @@ use config::ServiceConfig; use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; -use http::{Method, Version}; -use message::ResponseHead; +use http::{Method, StatusCode, Version}; +use message::{Head, ResponseHead}; use request::Request; use response::Response; @@ -99,69 +99,71 @@ impl Codec { } /// prepare transfer encoding - pub fn prepare_te(&mut self, head: &mut ResponseHead, length: &mut BodyLength) { + fn prepare_te(&mut self, head: &mut ResponseHead, length: BodyLength) { self.te .update(head, self.flags.contains(Flags::HEAD), self.version, length); } fn encode_response( &mut self, - mut msg: Response<()>, + msg: &mut ResponseHead, + length: BodyLength, buffer: &mut BytesMut, ) -> io::Result<()> { - let ka = self.flags.contains(Flags::KEEPALIVE_ENABLED) && msg - .keep_alive() - .unwrap_or_else(|| self.flags.contains(Flags::KEEPALIVE)); + msg.version = self.version; // Connection upgrade if msg.upgrade() { self.flags.insert(Flags::UPGRADE); self.flags.remove(Flags::KEEPALIVE); - msg.headers_mut() + msg.headers .insert(CONNECTION, HeaderValue::from_static("upgrade")); } // keep-alive - else if ka { + else if self.flags.contains(Flags::KEEPALIVE_ENABLED) && msg.keep_alive() { self.flags.insert(Flags::KEEPALIVE); if self.version < Version::HTTP_11 { - msg.headers_mut() + msg.headers .insert(CONNECTION, HeaderValue::from_static("keep-alive")); } } else if self.version >= Version::HTTP_11 { self.flags.remove(Flags::KEEPALIVE); - msg.headers_mut() + msg.headers .insert(CONNECTION, HeaderValue::from_static("close")); } // render message { let reason = msg.reason().as_bytes(); - buffer - .reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len()); + buffer.reserve(256 + msg.headers.len() * AVERAGE_HEADER_SIZE + reason.len()); // status line - helpers::write_status_line(self.version, msg.status().as_u16(), buffer); + helpers::write_status_line(self.version, msg.status.as_u16(), buffer); buffer.extend_from_slice(reason); // content length - let mut len_is_set = true; - match self.te.length { - BodyLength::Chunked => { - buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") - } - BodyLength::Empty => { - len_is_set = false; - buffer.extend_from_slice(b"\r\n") - } - BodyLength::Sized(len) => helpers::write_content_length(len, buffer), - BodyLength::Sized64(len) => { - buffer.extend_from_slice(b"\r\ncontent-length: "); - write!(buffer.writer(), "{}", len)?; - buffer.extend_from_slice(b"\r\n"); - } - BodyLength::None | BodyLength::Stream => { - buffer.extend_from_slice(b"\r\n") - } + match msg.status { + StatusCode::NO_CONTENT + | StatusCode::CONTINUE + | StatusCode::SWITCHING_PROTOCOLS + | StatusCode::PROCESSING => buffer.extend_from_slice(b"\r\n"), + _ => match length { + BodyLength::Chunked => { + buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") + } + BodyLength::Empty => { + buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n"); + } + BodyLength::Sized(len) => helpers::write_content_length(len, buffer), + BodyLength::Sized64(len) => { + buffer.extend_from_slice(b"\r\ncontent-length: "); + write!(buffer.writer(), "{}", len)?; + buffer.extend_from_slice(b"\r\n"); + } + BodyLength::None | BodyLength::Stream => { + buffer.extend_from_slice(b"\r\n") + } + }, } // write headers @@ -169,16 +171,9 @@ impl Codec { let mut has_date = false; let mut remaining = buffer.remaining_mut(); let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) }; - for (key, value) in msg.headers() { + for (key, value) in &msg.headers { match *key { - TRANSFER_ENCODING => continue, - CONTENT_LENGTH => match self.te.length { - BodyLength::None => (), - BodyLength::Empty => { - len_is_set = true; - } - _ => continue, - }, + TRANSFER_ENCODING | CONTENT_LENGTH => continue, DATE => { has_date = true; } @@ -213,9 +208,6 @@ impl Codec { unsafe { buffer.advance_mut(pos); } - if !len_is_set { - buffer.extend_from_slice(b"content-length: 0\r\n") - } // optimized date header, set_date writes \r\n if !has_date { @@ -268,7 +260,7 @@ impl Decoder for Codec { } impl Encoder for Codec { - type Item = Message>; + type Item = Message<(Response<()>, BodyLength)>; type Error = io::Error; fn encode( @@ -277,8 +269,9 @@ impl Encoder for Codec { dst: &mut BytesMut, ) -> Result<(), Self::Error> { match item { - Message::Item(res) => { - self.encode_response(res, dst)?; + Message::Item((mut res, length)) => { + self.prepare_te(res.head_mut(), length); + self.encode_response(res.head_mut(), length, dst)?; } Message::Chunk(Some(bytes)) => { self.te.encode(bytes.as_ref(), dst)?; diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 26154ef1e..12008a777 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -10,7 +10,7 @@ use client::ClientResponse; use error::ParseError; use http::header::{HeaderName, HeaderValue}; use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version}; -use message::MessageFlags; +use message::Head; use request::Request; const MAX_BUFFER_SIZE: usize = 131_072; @@ -50,6 +50,8 @@ pub(crate) enum PayloadLength { pub(crate) trait MessageTypeDecoder: Sized { fn keep_alive(&mut self); + fn force_close(&mut self); + fn headers_mut(&mut self) -> &mut HeaderMap; fn decode(src: &mut BytesMut) -> Result, ParseError>; @@ -137,6 +139,8 @@ pub(crate) trait MessageTypeDecoder: Sized { if ka { self.keep_alive(); + } else { + self.force_close(); } // https://tools.ietf.org/html/rfc7230#section-3.3.3 @@ -160,7 +164,11 @@ pub(crate) trait MessageTypeDecoder: Sized { impl MessageTypeDecoder for Request { fn keep_alive(&mut self) { - self.inner_mut().flags.set(MessageFlags::KEEPALIVE); + self.inner_mut().head.set_keep_alive() + } + + fn force_close(&mut self) { + self.inner_mut().head.force_close() } fn headers_mut(&mut self) -> &mut HeaderMap { @@ -234,7 +242,11 @@ impl MessageTypeDecoder for Request { impl MessageTypeDecoder for ClientResponse { fn keep_alive(&mut self) { - self.head.flags.insert(MessageFlags::KEEPALIVE); + self.head.set_keep_alive(); + } + + fn force_close(&mut self) { + self.head.force_close(); } fn headers_mut(&mut self) -> &mut HeaderMap { diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index ff9d54e66..bf0abb046 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -30,7 +30,6 @@ bitflags! { const KEEPALIVE_ENABLED = 0b0000_0010; const KEEPALIVE = 0b0000_0100; const POLLED = 0b0000_1000; - const FLUSHED = 0b0001_0000; const SHUTDOWN = 0b0010_0000; const DISCONNECTED = 0b0100_0000; } @@ -105,9 +104,9 @@ where ) -> Self { let keepalive = config.keep_alive_enabled(); let flags = if keepalive { - Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED | Flags::FLUSHED + Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED } else { - Flags::FLUSHED + Flags::empty() }; let framed = Framed::new(stream, Codec::new(config.clone())); @@ -167,7 +166,7 @@ where /// Flush stream fn poll_flush(&mut self) -> Poll<(), DispatchError> { - if !self.flags.contains(Flags::FLUSHED) { + if !self.framed.is_write_buf_empty() { match self.framed.poll_complete() { Ok(Async::NotReady) => Ok(Async::NotReady), Err(err) => { @@ -179,7 +178,6 @@ where if self.payload.is_some() && self.state.is_empty() { return Err(DispatchError::PayloadIsNotConsumed); } - self.flags.insert(Flags::FLUSHED); Ok(Async::Ready(())) } } @@ -194,7 +192,7 @@ where body: B1, ) -> Result, DispatchError> { self.framed - .force_send(Message::Item(message)) + .force_send(Message::Item((message, body.length()))) .map_err(|err| { if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete(None)); @@ -204,7 +202,6 @@ where self.flags .set(Flags::KEEPALIVE, self.framed.get_codec().keepalive()); - self.flags.remove(Flags::FLUSHED); match body.length() { BodyLength::None | BodyLength::Empty => Ok(State::None), _ => Ok(State::SendPayload(body)), @@ -228,10 +225,7 @@ where State::ServiceCall(mut fut) => { match fut.poll().map_err(DispatchError::Service)? { Async::Ready(mut res) => { - let (mut res, body) = res.replace_body(()); - self.framed - .get_codec_mut() - .prepare_te(res.head_mut(), &mut body.length()); + let (res, body) = res.replace_body(()); Some(self.send_response(res, body)?) } Async::NotReady => { @@ -248,13 +242,11 @@ where .map_err(|_| DispatchError::Unknown)? { Async::Ready(Some(item)) => { - self.flags.remove(Flags::FLUSHED); self.framed .force_send(Message::Chunk(Some(item)))?; continue; } Async::Ready(None) => { - self.flags.remove(Flags::FLUSHED); self.framed.force_send(Message::Chunk(None))?; } Async::NotReady => { @@ -296,10 +288,7 @@ where let mut task = self.service.call(req); match task.poll().map_err(DispatchError::Service)? { Async::Ready(res) => { - let (mut res, body) = res.replace_body(()); - self.framed - .get_codec_mut() - .prepare_te(res.head_mut(), &mut body.length()); + let (res, body) = res.replace_body(()); self.send_response(res, body) } Async::NotReady => Ok(State::ServiceCall(task)), @@ -408,7 +397,7 @@ where /// keep-alive timer fn poll_keepalive(&mut self) -> Result<(), DispatchError> { - if self.ka_timer.is_some() { + if self.ka_timer.is_none() { return Ok(()); } match self.ka_timer.as_mut().unwrap().poll().map_err(|e| { @@ -421,7 +410,7 @@ where return Err(DispatchError::DisconnectTimeout); } else if self.ka_timer.as_mut().unwrap().deadline() >= self.ka_expire { // check for any outstanding response processing - if self.state.is_empty() && self.flags.contains(Flags::FLUSHED) { + if self.state.is_empty() && self.framed.is_write_buf_empty() { if self.flags.contains(Flags::STARTED) { trace!("Keep-alive timeout, close connection"); self.flags.insert(Flags::SHUTDOWN); @@ -490,12 +479,14 @@ where inner.poll_response()?; inner.poll_flush()?; + if inner.flags.contains(Flags::DISCONNECTED) { + return Ok(Async::Ready(H1ServiceResult::Disconnected)); + } + // keep-alive and stream errors - if inner.state.is_empty() && inner.flags.contains(Flags::FLUSHED) { + if inner.state.is_empty() && inner.framed.is_write_buf_empty() { if let Some(err) = inner.error.take() { return Err(err); - } else if inner.flags.contains(Flags::DISCONNECTED) { - return Ok(Async::Ready(H1ServiceResult::Disconnected)); } // unhandled request (upgrade or connect) else if inner.unhandled.is_some() { diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index fd52d7316..1664af162 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -48,22 +48,13 @@ impl ResponseEncoder { resp: &mut ResponseHead, head: bool, version: Version, - length: &mut BodyLength, + length: BodyLength, ) { self.head = head; let transfer = match length { - BodyLength::Empty => { - match resp.status { - StatusCode::NO_CONTENT - | StatusCode::CONTINUE - | StatusCode::SWITCHING_PROTOCOLS - | StatusCode::PROCESSING => *length = BodyLength::None, - _ => (), - } - TransferEncoding::empty() - } - BodyLength::Sized(len) => TransferEncoding::length(*len as u64), - BodyLength::Sized64(len) => TransferEncoding::length(*len), + BodyLength::Empty => TransferEncoding::empty(), + BodyLength::Sized(len) => TransferEncoding::length(len as u64), + BodyLength::Sized64(len) => TransferEncoding::length(len), BodyLength::Chunked => TransferEncoding::chunked(), BodyLength::Stream => TransferEncoding::eof(), BodyLength::None => TransferEncoding::length(0), diff --git a/src/lib.rs b/src/lib.rs index 615f04010..57ff5df78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,6 +109,8 @@ extern crate serde_derive; #[cfg(feature = "ssl")] extern crate openssl; +extern crate backtrace; + pub mod body; pub mod client; mod config; @@ -173,5 +175,4 @@ pub mod http { pub use header::*; } pub use header::ContentEncoding; - pub use response::ConnectionType; } diff --git a/src/message.rs b/src/message.rs index 3dcb203de..e7ce22fcd 100644 --- a/src/message.rs +++ b/src/message.rs @@ -12,12 +12,41 @@ use uri::Url; pub trait Head: Default + 'static { fn clear(&mut self); + fn flags(&self) -> MessageFlags; + + fn flags_mut(&mut self) -> &mut MessageFlags; + fn pool() -> &'static MessagePool; + + /// Set upgrade + fn set_upgrade(&mut self) { + *self.flags_mut() = MessageFlags::UPGRADE; + } + + /// Check if request is upgrade request + fn upgrade(&self) -> bool { + self.flags().contains(MessageFlags::UPGRADE) + } + + /// Set keep-alive + fn set_keep_alive(&mut self) { + *self.flags_mut() = MessageFlags::KEEP_ALIVE; + } + + /// Check if request is keep-alive + fn keep_alive(&self) -> bool; + + /// Set force-close connection + fn force_close(&mut self) { + *self.flags_mut() = MessageFlags::FORCE_CLOSE; + } } bitflags! { - pub(crate) struct MessageFlags: u8 { - const KEEPALIVE = 0b0000_0001; + pub struct MessageFlags: u8 { + const KEEP_ALIVE = 0b0000_0001; + const FORCE_CLOSE = 0b0000_0010; + const UPGRADE = 0b0000_0100; } } @@ -47,6 +76,25 @@ impl Head for RequestHead { self.flags = MessageFlags::empty(); } + fn flags(&self) -> MessageFlags { + self.flags + } + + fn flags_mut(&mut self) -> &mut MessageFlags { + &mut self.flags + } + + /// Check if request is keep-alive + fn keep_alive(&self) -> bool { + if self.flags().contains(MessageFlags::FORCE_CLOSE) { + false + } else if self.flags().contains(MessageFlags::KEEP_ALIVE) { + true + } else { + self.version <= Version::HTTP_11 + } + } + fn pool() -> &'static MessagePool { REQUEST_POOL.with(|p| *p) } @@ -79,11 +127,44 @@ impl Head for ResponseHead { self.flags = MessageFlags::empty(); } + fn flags(&self) -> MessageFlags { + self.flags + } + + fn flags_mut(&mut self) -> &mut MessageFlags { + &mut self.flags + } + + /// Check if response is keep-alive + fn keep_alive(&self) -> bool { + if self.flags().contains(MessageFlags::FORCE_CLOSE) { + false + } else if self.flags().contains(MessageFlags::KEEP_ALIVE) { + true + } else { + self.version <= Version::HTTP_11 + } + } + fn pool() -> &'static MessagePool { RESPONSE_POOL.with(|p| *p) } } +impl ResponseHead { + /// Get custom reason for the response + #[inline] + pub fn reason(&self) -> &str { + if let Some(reason) = self.reason { + reason + } else { + self.status + .canonical_reason() + .unwrap_or("") + } + } +} + pub struct Message { pub head: T, pub url: Url, diff --git a/src/request.rs b/src/request.rs index 1e191047a..1ee47edb4 100644 --- a/src/request.rs +++ b/src/request.rs @@ -8,7 +8,7 @@ use extensions::Extensions; use httpmessage::HttpMessage; use payload::Payload; -use message::{Message, MessageFlags, MessagePool, RequestHead}; +use message::{Head, Message, MessagePool, RequestHead}; /// Request pub struct Request { @@ -116,7 +116,7 @@ impl Request { /// Checks if a connection should be kept alive. #[inline] pub fn keep_alive(&self) -> bool { - self.inner().flags.get().contains(MessageFlags::KEEPALIVE) + self.inner().head.keep_alive() } /// Request extensions diff --git a/src/response.rs b/src/response.rs index a6f7c81c2..542d4963e 100644 --- a/src/response.rs +++ b/src/response.rs @@ -20,17 +20,6 @@ use message::{Head, MessageFlags, ResponseHead}; /// max write buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; -/// Represents various types of connection -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum ConnectionType { - /// Close connection after response - Close, - /// Keep connection alive after response - KeepAlive, - /// Connection is upgraded to different type - Upgrade, -} - /// An HTTP Response pub struct Response(Box, B); @@ -124,27 +113,6 @@ impl Response { &mut self.get_mut().head.status } - /// Get custom reason for the response - #[inline] - pub fn reason(&self) -> &str { - if let Some(reason) = self.get_ref().head.reason { - reason - } else { - self.get_ref() - .head - .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.get_mut().head.reason = Some(reason); - self - } - /// Get the headers from the response #[inline] pub fn headers(&self) -> &HeaderMap { @@ -207,28 +175,15 @@ impl Response { count } - /// Set connection type - pub fn set_connection_type(&mut self, conn: ConnectionType) -> &mut Self { - self.get_mut().connection_type = Some(conn); - self - } - /// Connection upgrade status #[inline] pub fn upgrade(&self) -> bool { - self.get_ref().connection_type == Some(ConnectionType::Upgrade) + self.get_ref().head.upgrade() } /// Keep-alive status for this connection - pub fn keep_alive(&self) -> Option { - if let Some(ct) = self.get_ref().connection_type { - match ct { - ConnectionType::KeepAlive => Some(true), - ConnectionType::Close | ConnectionType::Upgrade => Some(false), - } - } else { - None - } + pub fn keep_alive(&self) -> bool { + self.get_ref().head.keep_alive() } /// Get body os this response @@ -275,19 +230,20 @@ impl Response { } } -impl fmt::Debug for Response { +impl fmt::Debug for Response { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let res = writeln!( f, "\nResponse {:?} {}{}", self.get_ref().head.version, self.get_ref().head.status, - self.get_ref().head.reason.unwrap_or("") + self.get_ref().head.reason.unwrap_or(""), ); let _ = writeln!(f, " headers:"); for (key, val) in self.get_ref().head.headers.iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); } + let _ = writeln!(f, " body: {:?}", self.body().length()); res } } @@ -400,27 +356,31 @@ impl ResponseBuilder { self } - /// Set connection type + /// Set connection type to KeepAlive #[inline] - #[doc(hidden)] - pub fn connection_type(&mut self, conn: ConnectionType) -> &mut Self { + pub fn keep_alive(&mut self) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { - parts.connection_type = Some(conn); + parts.head.set_keep_alive(); } self } /// Set connection type to Upgrade #[inline] - #[doc(hidden)] pub fn upgrade(&mut self) -> &mut Self { - self.connection_type(ConnectionType::Upgrade) + if let Some(parts) = parts(&mut self.response, &self.err) { + parts.head.set_upgrade(); + } + self } /// Force close connection, even if it is marked as keep-alive #[inline] pub fn force_close(&mut self) -> &mut Self { - self.connection_type(ConnectionType::Close) + if let Some(parts) = parts(&mut self.response, &self.err) { + parts.head.force_close(); + } + self } /// Set response content type @@ -719,8 +679,6 @@ impl From for Response { struct InnerResponse { head: ResponseHead, - connection_type: Option, - write_capacity: usize, response_size: u64, error: Option, pool: &'static ResponsePool, @@ -728,7 +686,6 @@ struct InnerResponse { pub(crate) struct ResponseParts { head: ResponseHead, - connection_type: Option, error: Option, } @@ -744,9 +701,7 @@ impl InnerResponse { flags: MessageFlags::empty(), }, pool, - connection_type: None, response_size: 0, - write_capacity: MAX_WRITE_BUFFER_SIZE, error: None, } } @@ -755,7 +710,6 @@ impl InnerResponse { fn into_parts(self) -> ResponseParts { ResponseParts { head: self.head, - connection_type: self.connection_type, error: self.error, } } @@ -763,9 +717,7 @@ impl InnerResponse { fn from_parts(parts: ResponseParts) -> InnerResponse { InnerResponse { head: parts.head, - connection_type: parts.connection_type, response_size: 0, - write_capacity: MAX_WRITE_BUFFER_SIZE, error: parts.error, pool: ResponsePool::pool(), } @@ -838,10 +790,8 @@ impl ResponsePool { let mut p = inner.pool.0.borrow_mut(); if p.len() < 128 { inner.head.clear(); - inner.connection_type = None; inner.response_size = 0; inner.error = None; - inner.write_capacity = MAX_WRITE_BUFFER_SIZE; p.push_front(inner); } } @@ -937,7 +887,7 @@ mod tests { #[test] fn test_force_close() { let resp = Response::build(StatusCode::OK).force_close().finish(); - assert!(!resp.keep_alive().unwrap()) + assert!(!resp.keep_alive()) } #[test] diff --git a/src/service.rs b/src/service.rs index 51a16b48b..f934305ce 100644 --- a/src/service.rs +++ b/src/service.rs @@ -3,10 +3,10 @@ use std::marker::PhantomData; use actix_net::codec::Framed; use actix_net::service::{NewService, Service}; use futures::future::{ok, Either, FutureResult}; -use futures::{Async, AsyncSink, Future, Poll, Sink}; +use futures::{Async, Future, Poll, Sink}; use tokio_io::{AsyncRead, AsyncWrite}; -use body::MessageBody; +use body::{BodyLength, MessageBody}; use error::{Error, ResponseError}; use h1::{Codec, Message}; use response::Response; @@ -15,7 +15,7 @@ pub struct SendError(PhantomData<(T, R, E)>); impl Default for SendError where - T: AsyncWrite, + T: AsyncRead + AsyncWrite, E: ResponseError, { fn default() -> Self { @@ -25,7 +25,7 @@ where impl NewService for SendError where - T: AsyncWrite, + T: AsyncRead + AsyncWrite, E: ResponseError, { type Request = Result)>; @@ -42,7 +42,7 @@ where impl Service for SendError where - T: AsyncWrite, + T: AsyncRead + AsyncWrite, E: ResponseError, { type Request = Result)>; @@ -62,7 +62,7 @@ where let (res, _body) = res.replace_body(()); Either::B(SendErrorFut { framed: Some(framed), - res: Some(res.into()), + res: Some((res, BodyLength::Empty).into()), err: Some(e), _t: PhantomData, }) @@ -72,7 +72,7 @@ where } pub struct SendErrorFut { - res: Option>>, + res: Option, BodyLength)>>, framed: Option>, err: Option, _t: PhantomData, @@ -81,22 +81,15 @@ pub struct SendErrorFut { impl Future for SendErrorFut where E: ResponseError, - T: AsyncWrite, + T: AsyncRead + AsyncWrite, { type Item = R; type Error = (E, Framed); fn poll(&mut self) -> Poll { if let Some(res) = self.res.take() { - match self.framed.as_mut().unwrap().start_send(res) { - Ok(AsyncSink::Ready) => (), - Ok(AsyncSink::NotReady(res)) => { - self.res = Some(res); - return Ok(Async::NotReady); - } - Err(_) => { - return Err((self.err.take().unwrap(), self.framed.take().unwrap())) - } + if let Err(_) = self.framed.as_mut().unwrap().force_send(res) { + return Err((self.err.take().unwrap(), self.framed.take().unwrap())); } } match self.framed.as_mut().unwrap().poll_complete() { @@ -123,20 +116,15 @@ where B: MessageBody, { pub fn send( - mut framed: Framed, + framed: Framed, res: Response, ) -> impl Future, Error = Error> { // extract body from response - let (mut res, body) = res.replace_body(()); - - // init codec - framed - .get_codec_mut() - .prepare_te(&mut res.head_mut(), &mut body.length()); + let (res, body) = res.replace_body(()); // write response SendResponseFut { - res: Some(Message::Item(res)), + res: Some(Message::Item((res, body.length()))), body: Some(body), framed: Some(framed), } @@ -174,13 +162,10 @@ where Ok(Async::Ready(())) } - fn call(&mut self, (res, mut framed): Self::Request) -> Self::Future { - let (mut res, body) = res.replace_body(()); - framed - .get_codec_mut() - .prepare_te(res.head_mut(), &mut body.length()); + fn call(&mut self, (res, framed): Self::Request) -> Self::Future { + let (res, body) = res.replace_body(()); SendResponseFut { - res: Some(Message::Item(res)), + res: Some(Message::Item((res, body.length()))), body: Some(body), framed: Some(framed), } @@ -188,7 +173,7 @@ where } pub struct SendResponseFut { - res: Option>>, + res: Option, BodyLength)>>, body: Option, framed: Option>, } diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 1fbbd03dd..5c86d8c49 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -8,7 +8,7 @@ use std::io; use error::ResponseError; use http::{header, Method, StatusCode}; use request::Request; -use response::{ConnectionType, Response, ResponseBuilder}; +use response::{Response, ResponseBuilder}; mod client; mod codec; @@ -183,7 +183,7 @@ pub fn handshake_response(req: &Request) -> ResponseBuilder { }; Response::build(StatusCode::SWITCHING_PROTOCOLS) - .connection_type(ConnectionType::Upgrade) + .upgrade() .header(header::UPGRADE, "websocket") .header(header::TRANSFER_ENCODING, "chunked") .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) diff --git a/tests/test_server.rs b/tests/test_server.rs index c499cf635..9d16e92e3 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -12,10 +12,13 @@ use actix_net::server::Server; use actix_net::service::NewServiceExt; use actix_web::{client, test, HttpMessage}; use bytes::Bytes; -use futures::future::{self, ok}; +use futures::future::{self, lazy, ok}; use futures::stream::once; -use actix_http::{body, h1, http, Body, Error, KeepAlive, Request, Response}; +use actix_http::{ + body, client as client2, h1, http, Body, Error, HttpMessage as HttpMessage2, + KeepAlive, Request, Response, +}; #[test] fn test_h1_v2() { @@ -181,14 +184,19 @@ fn test_headers() { .unwrap() .run() }); - thread::sleep(time::Duration::from_millis(400)); + thread::sleep(time::Duration::from_millis(200)); let mut sys = System::new("test"); - let req = client::ClientRequest::get(format!("http://{}/", addr)) + let mut connector = sys + .block_on(lazy(|| { + Ok::<_, ()>(client2::Connector::default().service()) + })).unwrap(); + + let req = client2::ClientRequest::get(format!("http://{}/", addr)) .finish() .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let response = sys.block_on(req.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response @@ -249,9 +257,7 @@ fn test_head_empty() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - h1::H1Service::new(|_| { - ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).finish()) - }).map(|_| ()) + h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }).unwrap() .run() }); From 7d3adaa6a8f47ef4101ba1a16e9e6360453237d4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 18 Nov 2018 18:17:38 -0800 Subject: [PATCH 076/208] replace message flags with ConnectionType --- src/client/request.rs | 6 +-- src/h1/decoder.rs | 14 +++-- src/lib.rs | 1 + src/message.rs | 115 +++++++++++++++++------------------------- src/response.rs | 10 ++-- 5 files changed, 64 insertions(+), 82 deletions(-) diff --git a/src/client/request.rs b/src/client/request.rs index cc65b9db1..735ce4937 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -16,7 +16,7 @@ use http::{ uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, Uri, Version, }; -use message::{Head, RequestHead}; +use message::{ConnectionType, Head, RequestHead}; use super::response::ClientResponse; use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; @@ -367,7 +367,7 @@ impl ClientRequestBuilder { { { if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_upgrade(); + parts.set_connection_type(ConnectionType::Upgrade); } } self.set_header(header::UPGRADE, value) @@ -377,7 +377,7 @@ impl ClientRequestBuilder { #[inline] pub fn close(&mut self) -> &mut Self { if let Some(parts) = parts(&mut self.head, &self.err) { - parts.force_close(); + parts.set_connection_type(ConnectionType::Close); } self } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 12008a777..12dfe1657 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -10,7 +10,7 @@ use client::ClientResponse; use error::ParseError; use http::header::{HeaderName, HeaderValue}; use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version}; -use message::Head; +use message::{ConnectionType, Head}; use request::Request; const MAX_BUFFER_SIZE: usize = 131_072; @@ -164,11 +164,15 @@ pub(crate) trait MessageTypeDecoder: Sized { impl MessageTypeDecoder for Request { fn keep_alive(&mut self) { - self.inner_mut().head.set_keep_alive() + self.inner_mut() + .head + .set_connection_type(ConnectionType::KeepAlive) } fn force_close(&mut self) { - self.inner_mut().head.force_close() + self.inner_mut() + .head + .set_connection_type(ConnectionType::Close) } fn headers_mut(&mut self) -> &mut HeaderMap { @@ -242,11 +246,11 @@ impl MessageTypeDecoder for Request { impl MessageTypeDecoder for ClientResponse { fn keep_alive(&mut self) { - self.head.set_keep_alive(); + self.head.set_connection_type(ConnectionType::KeepAlive); } fn force_close(&mut self) { - self.head.force_close(); + self.head.set_connection_type(ConnectionType::Close); } fn headers_mut(&mut self) -> &mut HeaderMap { diff --git a/src/lib.rs b/src/lib.rs index 57ff5df78..3024b753f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -175,4 +175,5 @@ pub mod http { pub use header::*; } pub use header::ContentEncoding; + pub use message::ConnectionType; } diff --git a/src/message.rs b/src/message.rs index e7ce22fcd..03e18f082 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,4 +1,4 @@ -use std::cell::{Cell, RefCell}; +use std::cell::RefCell; use std::collections::VecDeque; use std::rc::Rc; @@ -8,46 +8,36 @@ use extensions::Extensions; use payload::Payload; use uri::Url; +/// Represents various types of connection +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum ConnectionType { + /// Close connection after response + Close, + /// Keep connection alive after response + KeepAlive, + /// Connection is upgraded to different type + Upgrade, +} + #[doc(hidden)] pub trait Head: Default + 'static { fn clear(&mut self); - fn flags(&self) -> MessageFlags; + /// Connection type + fn connection_type(&self) -> ConnectionType; - fn flags_mut(&mut self) -> &mut MessageFlags; + /// Set connection type of the message + fn set_connection_type(&mut self, ctype: ConnectionType); + + fn upgrade(&self) -> bool { + self.connection_type() == ConnectionType::Upgrade + } + + fn keep_alive(&self) -> bool { + self.connection_type() == ConnectionType::KeepAlive + } fn pool() -> &'static MessagePool; - - /// Set upgrade - fn set_upgrade(&mut self) { - *self.flags_mut() = MessageFlags::UPGRADE; - } - - /// Check if request is upgrade request - fn upgrade(&self) -> bool { - self.flags().contains(MessageFlags::UPGRADE) - } - - /// Set keep-alive - fn set_keep_alive(&mut self) { - *self.flags_mut() = MessageFlags::KEEP_ALIVE; - } - - /// Check if request is keep-alive - fn keep_alive(&self) -> bool; - - /// Set force-close connection - fn force_close(&mut self) { - *self.flags_mut() = MessageFlags::FORCE_CLOSE; - } -} - -bitflags! { - pub struct MessageFlags: u8 { - const KEEP_ALIVE = 0b0000_0001; - const FORCE_CLOSE = 0b0000_0010; - const UPGRADE = 0b0000_0100; - } } pub struct RequestHead { @@ -55,7 +45,7 @@ pub struct RequestHead { pub method: Method, pub version: Version, pub headers: HeaderMap, - pub(crate) flags: MessageFlags, + ctype: Option, } impl Default for RequestHead { @@ -65,33 +55,28 @@ impl Default for RequestHead { method: Method::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), - flags: MessageFlags::empty(), + ctype: None, } } } impl Head for RequestHead { fn clear(&mut self) { + self.ctype = None; self.headers.clear(); - self.flags = MessageFlags::empty(); } - fn flags(&self) -> MessageFlags { - self.flags + fn set_connection_type(&mut self, ctype: ConnectionType) { + self.ctype = Some(ctype) } - fn flags_mut(&mut self) -> &mut MessageFlags { - &mut self.flags - } - - /// Check if request is keep-alive - fn keep_alive(&self) -> bool { - if self.flags().contains(MessageFlags::FORCE_CLOSE) { - false - } else if self.flags().contains(MessageFlags::KEEP_ALIVE) { - true + fn connection_type(&self) -> ConnectionType { + if let Some(ct) = self.ctype { + ct + } else if self.version <= Version::HTTP_11 { + ConnectionType::Close } else { - self.version <= Version::HTTP_11 + ConnectionType::KeepAlive } } @@ -105,7 +90,7 @@ pub struct ResponseHead { pub status: StatusCode, pub headers: HeaderMap, pub reason: Option<&'static str>, - pub(crate) flags: MessageFlags, + pub(crate) ctype: Option, } impl Default for ResponseHead { @@ -115,34 +100,29 @@ impl Default for ResponseHead { status: StatusCode::OK, headers: HeaderMap::with_capacity(16), reason: None, - flags: MessageFlags::empty(), + ctype: None, } } } impl Head for ResponseHead { fn clear(&mut self) { + self.ctype = None; self.reason = None; self.headers.clear(); - self.flags = MessageFlags::empty(); } - fn flags(&self) -> MessageFlags { - self.flags + fn set_connection_type(&mut self, ctype: ConnectionType) { + self.ctype = Some(ctype) } - fn flags_mut(&mut self) -> &mut MessageFlags { - &mut self.flags - } - - /// Check if response is keep-alive - fn keep_alive(&self) -> bool { - if self.flags().contains(MessageFlags::FORCE_CLOSE) { - false - } else if self.flags().contains(MessageFlags::KEEP_ALIVE) { - true + fn connection_type(&self) -> ConnectionType { + if let Some(ct) = self.ctype { + ct + } else if self.version <= Version::HTTP_11 { + ConnectionType::Close } else { - self.version <= Version::HTTP_11 + ConnectionType::KeepAlive } } @@ -172,7 +152,6 @@ pub struct Message { pub extensions: RefCell, pub payload: RefCell>, pub(crate) pool: &'static MessagePool, - pub(crate) flags: Cell, } impl Message { @@ -181,7 +160,6 @@ impl Message { pub fn reset(&mut self) { self.head.clear(); self.extensions.borrow_mut().clear(); - self.flags.set(MessageFlags::empty()); *self.payload.borrow_mut() = None; } } @@ -193,7 +171,6 @@ impl Default for Message { url: Url::default(), head: T::default(), status: StatusCode::OK, - flags: Cell::new(MessageFlags::empty()), payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), } diff --git a/src/response.rs b/src/response.rs index 542d4963e..f2ed72925 100644 --- a/src/response.rs +++ b/src/response.rs @@ -15,7 +15,7 @@ use serde_json; use body::{Body, BodyStream, MessageBody}; use error::Error; use header::{Header, IntoHeaderValue}; -use message::{Head, MessageFlags, ResponseHead}; +use message::{ConnectionType, Head, ResponseHead}; /// max write buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; @@ -360,7 +360,7 @@ impl ResponseBuilder { #[inline] pub fn keep_alive(&mut self) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { - parts.head.set_keep_alive(); + parts.head.set_connection_type(ConnectionType::KeepAlive); } self } @@ -369,7 +369,7 @@ impl ResponseBuilder { #[inline] pub fn upgrade(&mut self) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { - parts.head.set_upgrade(); + parts.head.set_connection_type(ConnectionType::Upgrade); } self } @@ -378,7 +378,7 @@ impl ResponseBuilder { #[inline] pub fn force_close(&mut self) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { - parts.head.force_close(); + parts.head.set_connection_type(ConnectionType::Close); } self } @@ -698,7 +698,7 @@ impl InnerResponse { version: Version::default(), headers: HeaderMap::with_capacity(16), reason: None, - flags: MessageFlags::empty(), + ctype: None, }, pool, response_size: 0, From 22d4523c93db4f750ea2866a1e34ad3142773554 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 18 Nov 2018 18:31:44 -0800 Subject: [PATCH 077/208] update actix-net --- Cargo.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2eef1c50f..50bacddbd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,9 +45,8 @@ rust-tls = ["rustls", "actix-net/rust-tls"] [dependencies] actix = "0.7.5" -#actix-net = "0.2.2" -actix-net = { git="https://github.com/actix/actix-net.git" } -#actix-net = { path="../actix-net" } +actix-net = "0.2.3" +#actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" From 7d6643032408720bbcb4355cf3bc453e8724c272 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 18 Nov 2018 20:08:43 -0800 Subject: [PATCH 078/208] move url module to different crate --- Cargo.toml | 5 +- src/h1/decoder.rs | 1 - src/lib.rs | 3 - src/message.rs | 5 -- src/request.rs | 6 +- src/test.rs | 3 +- src/uri.rs | 169 ---------------------------------------------- 7 files changed, 3 insertions(+), 189 deletions(-) delete mode 100644 src/uri.rs diff --git a/Cargo.toml b/Cargo.toml index 50bacddbd..b2c7c0848 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ base64 = "0.9" bitflags = "1.0" http = "0.1.8" httparse = "1.3" -failure = "0.1.2" +failure = "0.1.3" indexmap = "1.0" log = "0.4" mime = "0.3" @@ -62,7 +62,6 @@ serde_json = "1.0" sha1 = "0.6" time = "0.1" encoding = "0.2" -lazy_static = "1.0" serde_urlencoded = "0.5.3" cookie = { version="0.11", features=["percent-encode"] } @@ -93,8 +92,6 @@ openssl = { version="0.10", optional = true } # rustls rustls = { version = "^0.14", optional = true } -backtrace="*" - [dev-dependencies] actix-web = "0.7" env_logger = "0.5" diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 12dfe1657..de0df367f 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -234,7 +234,6 @@ impl MessageTypeDecoder for Request { { let inner = msg.inner_mut(); - inner.url.update(&uri); inner.head.uri = uri; inner.head.method = method; inner.head.version = ver; diff --git a/src/lib.rs b/src/lib.rs index 3024b753f..bee172f0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,8 +76,6 @@ extern crate bitflags; #[macro_use] extern crate failure; #[macro_use] -extern crate lazy_static; -#[macro_use] extern crate futures; extern crate cookie; extern crate encoding; @@ -124,7 +122,6 @@ mod payload; mod request; mod response; mod service; -pub mod uri; pub mod error; pub mod h1; diff --git a/src/message.rs b/src/message.rs index 03e18f082..e1059fa48 100644 --- a/src/message.rs +++ b/src/message.rs @@ -6,7 +6,6 @@ use http::{HeaderMap, Method, StatusCode, Uri, Version}; use extensions::Extensions; use payload::Payload; -use uri::Url; /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] @@ -147,8 +146,6 @@ impl ResponseHead { pub struct Message { pub head: T, - pub url: Url, - pub status: StatusCode, pub extensions: RefCell, pub payload: RefCell>, pub(crate) pool: &'static MessagePool, @@ -168,9 +165,7 @@ impl Default for Message { fn default() -> Self { Message { pool: T::pool(), - url: Url::default(), head: T::default(), - status: StatusCode::OK, payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), } diff --git a/src/request.rs b/src/request.rs index 1ee47edb4..d529c09f9 100644 --- a/src/request.rs +++ b/src/request.rs @@ -94,11 +94,7 @@ impl Request { /// The target path of this Request. #[inline] pub fn path(&self) -> &str { - if let Some(path) = self.inner().url.path() { - path - } else { - self.inner().head.uri.path() - } + self.inner().head.uri.path() } #[inline] diff --git a/src/test.rs b/src/test.rs index c6f258a77..5442a4149 100644 --- a/src/test.rs +++ b/src/test.rs @@ -15,7 +15,6 @@ use tokio::runtime::current_thread::Runtime; use header::{Header, IntoHeaderValue}; use payload::Payload; use request::Request; -use uri::Url as InnerUrl; // use ws; /// The `TestServer` type. @@ -390,8 +389,8 @@ impl TestRequest { let mut req = Request::new(); { let inner = req.inner_mut(); + inner.head.uri = uri; inner.head.method = method; - inner.url = InnerUrl::new(&uri); inner.head.version = version; inner.head.headers = headers; *inner.payload.borrow_mut() = payload; diff --git a/src/uri.rs b/src/uri.rs deleted file mode 100644 index 89f6d3b1e..000000000 --- a/src/uri.rs +++ /dev/null @@ -1,169 +0,0 @@ -use http::Uri; -use std::rc::Rc; - -#[allow(dead_code)] -const GEN_DELIMS: &[u8] = b":/?#[]@"; -#[allow(dead_code)] -const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,"; -#[allow(dead_code)] -const SUB_DELIMS: &[u8] = b"!$'()*,+?=;"; -#[allow(dead_code)] -const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;"; -#[allow(dead_code)] -const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ - 1234567890 - -._~"; -const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ - 1234567890 - -._~ - !$'()*,"; -const QS: &[u8] = b"+&=;b"; - -#[inline] -fn bit_at(array: &[u8], ch: u8) -> bool { - array[(ch >> 3) as usize] & (1 << (ch & 7)) != 0 -} - -#[inline] -fn set_bit(array: &mut [u8], ch: u8) { - array[(ch >> 3) as usize] |= 1 << (ch & 7) -} - -lazy_static! { - pub static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") }; -} - -#[derive(Default, Clone, Debug)] -pub struct Url { - path: Option>, -} - -impl Url { - pub fn new(uri: &Uri) -> Url { - let path = DEFAULT_QUOTER.requote(uri.path().as_bytes()); - - Url { path } - } - - pub(crate) fn update(&mut self, uri: &Uri) { - self.path = DEFAULT_QUOTER.requote(uri.path().as_bytes()); - } - - pub fn path(&self) -> Option<&str> { - self.path.as_ref().map(|s| s.as_str()) - } -} - -pub struct Quoter { - safe_table: [u8; 16], - protected_table: [u8; 16], -} - -impl Quoter { - pub fn new(safe: &[u8], protected: &[u8]) -> Quoter { - let mut q = Quoter { - safe_table: [0; 16], - protected_table: [0; 16], - }; - - // prepare safe table - for i in 0..128 { - if ALLOWED.contains(&i) { - set_bit(&mut q.safe_table, i); - } - if QS.contains(&i) { - set_bit(&mut q.safe_table, i); - } - } - - for ch in safe { - set_bit(&mut q.safe_table, *ch) - } - - // prepare protected table - for ch in protected { - set_bit(&mut q.safe_table, *ch); - set_bit(&mut q.protected_table, *ch); - } - - q - } - - pub fn requote(&self, val: &[u8]) -> Option> { - let mut has_pct = 0; - let mut pct = [b'%', 0, 0]; - let mut idx = 0; - let mut cloned: Option> = None; - - let len = val.len(); - while idx < len { - let ch = val[idx]; - - if has_pct != 0 { - pct[has_pct] = val[idx]; - has_pct += 1; - if has_pct == 3 { - has_pct = 0; - let buf = cloned.as_mut().unwrap(); - - if let Some(ch) = restore_ch(pct[1], pct[2]) { - if ch < 128 { - if bit_at(&self.protected_table, ch) { - buf.extend_from_slice(&pct); - idx += 1; - continue; - } - - if bit_at(&self.safe_table, ch) { - buf.push(ch); - idx += 1; - continue; - } - } - buf.push(ch); - } else { - buf.extend_from_slice(&pct[..]); - } - } - } else if ch == b'%' { - has_pct = 1; - if cloned.is_none() { - let mut c = Vec::with_capacity(len); - c.extend_from_slice(&val[..idx]); - cloned = Some(c); - } - } else if let Some(ref mut cloned) = cloned { - cloned.push(ch) - } - idx += 1; - } - - if let Some(data) = cloned { - // Unsafe: we get data from http::Uri, which does utf-8 checks already - // this code only decodes valid pct encoded values - Some(Rc::new(unsafe { String::from_utf8_unchecked(data) })) - } else { - None - } - } -} - -#[inline] -fn from_hex(v: u8) -> Option { - if v >= b'0' && v <= b'9' { - Some(v - 0x30) // ord('0') == 0x30 - } else if v >= b'A' && v <= b'F' { - Some(v - 0x41 + 10) // ord('A') == 0x41 - } else if v > b'a' && v <= b'f' { - Some(v - 0x61 + 10) // ord('a') == 0x61 - } else { - None - } -} - -#[inline] -fn restore_ch(d1: u8, d2: u8) -> Option { - from_hex(d1).and_then(|d1| from_hex(d2).and_then(move |d2| Some(d1 << 4 | d2))) -} From 18fcddfd637b77f11bea8d17826ff31bd019f277 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 18 Nov 2018 20:25:59 -0800 Subject: [PATCH 079/208] remove backtrace dep --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bee172f0c..e5f50011b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,8 +107,6 @@ extern crate serde_derive; #[cfg(feature = "ssl")] extern crate openssl; -extern crate backtrace; - pub mod body; pub mod client; mod config; @@ -173,4 +171,4 @@ pub mod http { } pub use header::ContentEncoding; pub use message::ConnectionType; -} +} \ No newline at end of file From 1ca6b44bae381f9042795fea63665aa9d480ccf2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 18 Nov 2018 21:48:20 -0800 Subject: [PATCH 080/208] add TestServer --- src/lib.rs | 2 +- src/test.rs | 447 +++++++++++++++++++++---------------------- tests/test_client.rs | 113 ++++------- tests/test_server.rs | 354 ++++++++++++---------------------- tests/test_ws.rs | 128 ++++++------- 5 files changed, 425 insertions(+), 619 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e5f50011b..5256dd190 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,4 +171,4 @@ pub mod http { } pub use header::ContentEncoding; pub use message::ConnectionType; -} \ No newline at end of file +} diff --git a/src/test.rs b/src/test.rs index 5442a4149..9e14129b6 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,251 +1,31 @@ //! Various helpers for Actix applications to use during testing. -use std::net; use std::str::FromStr; +use std::sync::mpsc; +use std::{net, thread}; use actix::System; +use actix_net::codec::Framed; +use actix_net::server::{Server, StreamServiceFactory}; +use actix_net::service::Service; use bytes::Bytes; use cookie::Cookie; -use futures::Future; +use futures::future::{lazy, Future}; use http::header::HeaderName; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; use net2::TcpBuilder; use tokio::runtime::current_thread::Runtime; +use tokio_io::{AsyncRead, AsyncWrite}; +use body::MessageBody; +use client::{ + ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, + ConnectorError, SendRequestError, +}; use header::{Header, IntoHeaderValue}; use payload::Payload; use request::Request; -// use ws; - -/// The `TestServer` type. -/// -/// `TestServer` is very simple test server that simplify process of writing -/// integration tests cases for actix web applications. -/// -/// # Examples -/// -/// ```rust,ignore -/// # extern crate actix_web; -/// # use actix_web::*; -/// # -/// # fn my_handler(req: &HttpRequest) -> Response { -/// # Response::Ok().into() -/// # } -/// # -/// # fn main() { -/// use actix_web::test::TestServer; -/// -/// let mut srv = TestServer::new(|app| app.handler(my_handler)); -/// -/// let req = srv.get().finish().unwrap(); -/// let response = srv.execute(req.send()).unwrap(); -/// assert!(response.status().is_success()); -/// # } -/// ``` -pub struct TestServer { - addr: net::SocketAddr, - rt: Runtime, - ssl: bool, -} - -impl TestServer { - /// Start new test server - /// - /// This method accepts configuration method. You can add - /// middlewares or set handlers for test application. - pub fn new(_config: F) -> Self - where - F: Fn() + Clone + Send + 'static, - { - unimplemented!() - } - - /// Get firat available unused address - pub fn unused_addr() -> net::SocketAddr { - let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = TcpBuilder::new_v4().unwrap(); - socket.bind(&addr).unwrap(); - socket.reuse_address(true).unwrap(); - let tcp = socket.to_tcp_listener().unwrap(); - tcp.local_addr().unwrap() - } - - /// Construct test server url - pub fn addr(&self) -> net::SocketAddr { - self.addr - } - - /// Construct test server url - pub fn url(&self, uri: &str) -> String { - if uri.starts_with('/') { - format!( - "{}://localhost:{}{}", - if self.ssl { "https" } else { "http" }, - self.addr.port(), - uri - ) - } else { - format!( - "{}://localhost:{}/{}", - if self.ssl { "https" } else { "http" }, - self.addr.port(), - uri - ) - } - } - - /// Stop http server - fn stop(&mut self) { - System::current().stop(); - } - - /// Execute future on current core - pub fn execute(&mut self, fut: F) -> Result - where - F: Future, - { - self.rt.block_on(fut) - } - - // /// Connect to websocket server at a given path - // pub fn ws_at( - // &mut self, path: &str, - // ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { - // let url = self.url(path); - // self.rt - // .block_on(ws::Client::with_connector(url, self.conn.clone()).connect()) - // } - - // /// Connect to a websocket server - // pub fn ws( - // &mut self, - // ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { - // self.ws_at("/") - // } - - // /// Create `GET` request - // pub fn get(&self) -> ClientRequestBuilder { - // ClientRequest::get(self.url("/").as_str()) - // } - - // /// Create `POST` request - // pub fn post(&self) -> ClientRequestBuilder { - // ClientRequest::post(self.url("/").as_str()) - // } - - // /// Create `HEAD` request - // pub fn head(&self) -> ClientRequestBuilder { - // ClientRequest::head(self.url("/").as_str()) - // } - - // /// Connect to test http server - // pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder { - // ClientRequest::build() - // .method(meth) - // .uri(self.url(path).as_str()) - // .with_connector(self.conn.clone()) - // .take() - // } -} - -impl Drop for TestServer { - fn drop(&mut self) { - self.stop() - } -} - -// /// An `TestServer` builder -// /// -// /// This type can be used to construct an instance of `TestServer` through a -// /// builder-like pattern. -// pub struct TestServerBuilder -// where -// F: Fn() -> S + Send + Clone + 'static, -// { -// state: F, -// } - -// impl TestServerBuilder -// where -// F: Fn() -> S + Send + Clone + 'static, -// { -// /// Create a new test server -// pub fn new(state: F) -> TestServerBuilder { -// TestServerBuilder { state } -// } - -// #[allow(unused_mut)] -// /// Configure test application and run test server -// pub fn start(mut self, config: C) -> TestServer -// where -// C: Fn(&mut TestApp) + Clone + Send + 'static, -// { -// let (tx, rx) = mpsc::channel(); - -// let mut has_ssl = false; - -// #[cfg(any(feature = "alpn", feature = "ssl"))] -// { -// has_ssl = has_ssl || self.ssl.is_some(); -// } - -// #[cfg(feature = "rust-tls")] -// { -// has_ssl = has_ssl || self.rust_ssl.is_some(); -// } - -// // run server in separate thread -// thread::spawn(move || { -// let addr = TestServer::unused_addr(); - -// let sys = System::new("actix-test-server"); -// let state = self.state; -// let mut srv = HttpServer::new(move || { -// let mut app = TestApp::new(state()); -// config(&mut app); -// app -// }).workers(1) -// .keep_alive(5) -// .disable_signals(); - -// tx.send((System::current(), addr, TestServer::get_conn())) -// .unwrap(); - -// #[cfg(any(feature = "alpn", feature = "ssl"))] -// { -// let ssl = self.ssl.take(); -// if let Some(ssl) = ssl { -// let tcp = net::TcpListener::bind(addr).unwrap(); -// srv = srv.listen_ssl(tcp, ssl).unwrap(); -// } -// } -// #[cfg(feature = "rust-tls")] -// { -// let ssl = self.rust_ssl.take(); -// if let Some(ssl) = ssl { -// let tcp = net::TcpListener::bind(addr).unwrap(); -// srv = srv.listen_rustls(tcp, ssl); -// } -// } -// if !has_ssl { -// let tcp = net::TcpListener::bind(addr).unwrap(); -// srv = srv.listen(tcp); -// } -// srv.start(); - -// sys.run(); -// }); - -// let (system, addr, conn) = rx.recv().unwrap(); -// System::set_current(system); -// TestServer { -// addr, -// conn, -// ssl: has_ssl, -// rt: Runtime::new().unwrap(), -// } -// } -// } +use ws; /// Test `Request` builder /// @@ -486,3 +266,204 @@ impl TestRequest { // } // } } + +/// The `TestServer` type. +/// +/// `TestServer` is very simple test server that simplify process of writing +/// integration tests cases for actix web applications. +/// +/// # Examples +/// +/// ```rust +/// # extern crate actix_web; +/// # use actix_web::*; +/// # +/// # fn my_handler(req: &HttpRequest) -> HttpResponse { +/// # HttpResponse::Ok().into() +/// # } +/// # +/// # fn main() { +/// use actix_web::test::TestServer; +/// +/// let mut srv = TestServer::new(|app| app.handler(my_handler)); +/// +/// let req = srv.get().finish().unwrap(); +/// let response = srv.execute(req.send()).unwrap(); +/// assert!(response.status().is_success()); +/// # } +/// ``` +pub struct TestServer; + +/// +pub struct TestServerRuntime { + addr: net::SocketAddr, + conn: T, + rt: Runtime, +} + +impl TestServer { + /// Start new test server with application factory + pub fn with_factory( + factory: F, + ) -> TestServerRuntime< + impl Service + + Clone, + > { + let (tx, rx) = mpsc::channel(); + + // run server in separate thread + thread::spawn(move || { + let sys = System::new("actix-test-server"); + let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); + let local_addr = tcp.local_addr().unwrap(); + + Server::default() + .listen("test", tcp, factory) + .workers(1) + .disable_signals() + .start(); + + tx.send((System::current(), local_addr)).unwrap(); + sys.run(); + }); + + let (system, addr) = rx.recv().unwrap(); + System::set_current(system); + + let mut rt = Runtime::new().unwrap(); + let conn = rt + .block_on(lazy(|| Ok::<_, ()>(TestServer::new_connector()))) + .unwrap(); + + TestServerRuntime { addr, conn, rt } + } + + fn new_connector( +) -> impl Service + + Clone { + #[cfg(feature = "ssl")] + { + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + Connector::default().ssl(builder.build()).service() + } + #[cfg(not(feature = "ssl"))] + { + Connector::default().service() + } + } + + /// Get firat available unused address + pub fn unused_addr() -> net::SocketAddr { + let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); + let socket = TcpBuilder::new_v4().unwrap(); + socket.bind(&addr).unwrap(); + socket.reuse_address(true).unwrap(); + let tcp = socket.to_tcp_listener().unwrap(); + tcp.local_addr().unwrap() + } +} + +impl TestServerRuntime { + /// Execute future on current core + pub fn block_on(&mut self, fut: F) -> Result + where + F: Future, + { + self.rt.block_on(fut) + } + + /// Construct test server url + pub fn addr(&self) -> net::SocketAddr { + self.addr + } + + /// Construct test server url + pub fn url(&self, uri: &str) -> String { + if uri.starts_with('/') { + format!("http://localhost:{}{}", self.addr.port(), uri) + } else { + format!("http://localhost:{}/{}", self.addr.port(), uri) + } + } + + /// Create `GET` request + pub fn get(&self) -> ClientRequestBuilder { + ClientRequest::get(self.url("/").as_str()) + } + + /// Create `POST` request + pub fn post(&self) -> ClientRequestBuilder { + ClientRequest::post(self.url("/").as_str()) + } + + /// Create `HEAD` request + pub fn head(&self) -> ClientRequestBuilder { + ClientRequest::head(self.url("/").as_str()) + } + + /// Connect to test http server + pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder { + ClientRequest::build() + .method(meth) + .uri(self.url(path).as_str()) + .take() + } + + /// Http connector + pub fn connector(&mut self) -> &mut T { + &mut self.conn + } + + /// Http connector + pub fn new_connector(&mut self) -> T + where + T: Clone, + { + self.conn.clone() + } + + /// Stop http server + fn stop(&mut self) { + System::current().stop(); + } +} + +impl TestServerRuntime +where + T: Service + Clone, + T::Response: Connection, +{ + /// Connect to websocket server at a given path + pub fn ws_at( + &mut self, + path: &str, + ) -> Result, ws::ClientError> { + let url = self.url(path); + self.rt + .block_on(ws::Client::default().call(ws::Connect::new(url))) + } + + /// Connect to a websocket server + pub fn ws( + &mut self, + ) -> Result, ws::ClientError> { + self.ws_at("/") + } + + /// Send request and read response message + pub fn send_request( + &mut self, + req: ClientRequest, + ) -> Result { + self.rt.block_on(req.send(&mut self.conn)) + } +} + +impl Drop for TestServerRuntime { + fn drop(&mut self) { + self.stop() + } +} diff --git a/tests/test_client.rs b/tests/test_client.rs index 40920d1b8..4a4ccb7dd 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -4,16 +4,12 @@ extern crate actix_net; extern crate bytes; extern crate futures; -use std::{thread, time}; - -use actix::System; -use actix_net::server::Server; use actix_net::service::NewServiceExt; use bytes::Bytes; -use futures::future::{self, lazy, ok}; +use futures::future::{self, ok}; use actix_http::HttpMessage; -use actix_http::{client, h1, test, Request, Response}; +use actix_http::{client, h1, test::TestServer, Request, Response}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -39,111 +35,70 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_h1_v2() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::build() - .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) - .map(|_| ()) - }).unwrap() - .run(); + let mut srv = TestServer::with_factory(move || { + h1::H1Service::build() + .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); + let mut connector = srv.new_connector(); - let mut sys = System::new("test"); - let mut connector = sys - .block_on(lazy(|| Ok::<_, ()>(client::Connector::default().service()))) - .unwrap(); - - let req = client::ClientRequest::get(format!("http://{}/", addr)) - .finish() - .unwrap(); - - let response = sys.block_on(req.send(&mut connector)).unwrap(); + let request = srv.get().finish().unwrap(); + let response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); - let request = client::ClientRequest::get(format!("http://{}/", addr)) - .header("x-test", "111") - .finish() - .unwrap(); + let request = srv.get().header("x-test", "111").finish().unwrap(); let repr = format!("{:?}", request); assert!(repr.contains("ClientRequest")); assert!(repr.contains("x-test")); - let response = sys.block_on(request.send(&mut connector)).unwrap(); + let response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - let request = client::ClientRequest::post(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(request.send(&mut connector)).unwrap(); + let request = srv.post().finish().unwrap(); + let response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } #[test] fn test_connection_close() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map(|_| ()) - }).unwrap() - .run(); + let mut srv = TestServer::with_factory(move || { + h1::H1Service::build() + .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); + let mut connector = srv.new_connector(); - let mut sys = System::new("test"); - let mut connector = sys - .block_on(lazy(|| Ok::<_, ()>(client::Connector::default().service()))) - .unwrap(); - - let request = client::ClientRequest::get(format!("http://{}/", addr)) - .header("Connection", "close") - .finish() - .unwrap(); - let response = sys.block_on(request.send(&mut connector)).unwrap(); + let request = srv.get().close().finish().unwrap(); + let response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); } #[test] fn test_with_query_parameter() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::build() - .finish(|req: Request| { - if req.uri().query().unwrap().contains("qp=") { - ok::<_, ()>(Response::Ok().finish()) - } else { - ok::<_, ()>(Response::BadRequest().finish()) - } - }).map(|_| ()) - }).unwrap() - .run(); + let mut srv = TestServer::with_factory(move || { + h1::H1Service::build() + .finish(|req: Request| { + if req.uri().query().unwrap().contains("qp=") { + ok::<_, ()>(Response::Ok().finish()) + } else { + ok::<_, ()>(Response::BadRequest().finish()) + } + }).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); + let mut connector = srv.new_connector(); - let mut sys = System::new("test"); - let mut connector = sys - .block_on(lazy(|| Ok::<_, ()>(client::Connector::default().service()))) - .unwrap(); - - let request = client::ClientRequest::get(format!("http://{}/?qp=5", addr)) + let request = client::ClientRequest::get(srv.url("/?qp=5")) .finish() .unwrap(); - let response = sys.block_on(request.send(&mut connector)).unwrap(); + let response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); } diff --git a/tests/test_server.rs b/tests/test_server.rs index 9d16e92e3..a01af4f0d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,70 +1,48 @@ extern crate actix; extern crate actix_http; extern crate actix_net; -extern crate actix_web; extern crate bytes; extern crate futures; -use std::{io::Read, io::Write, net, thread, time}; +use std::{io::Read, io::Write, net}; -use actix::System; -use actix_net::server::Server; use actix_net::service::NewServiceExt; -use actix_web::{client, test, HttpMessage}; use bytes::Bytes; -use futures::future::{self, lazy, ok}; +use futures::future::{self, ok}; use futures::stream::once; use actix_http::{ - body, client as client2, h1, http, Body, Error, HttpMessage as HttpMessage2, - KeepAlive, Request, Response, + body, client, h1, http, test, Body, Error, HttpMessage as HttpMessage2, KeepAlive, + Request, Response, }; #[test] fn test_h1_v2() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::build() - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_disconnect(1000) - .server_hostname("localhost") - .server_address(addr) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) - }).unwrap() - .run(); + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::build() + .keep_alive(KeepAlive::Disabled) + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut sys = System::new("test"); - { - let req = client::ClientRequest::get(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } + let req = client::ClientRequest::get(srv.url("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); } #[test] fn test_slow_request() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::build() - .client_timeout(100) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) - }).unwrap() - .run(); + let srv = test::TestServer::with_factory(|| { + h1::H1Service::build() + .client_timeout(100) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut stream = net::TcpStream::connect(addr).unwrap(); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); let mut data = String::new(); let _ = stream.read_to_string(&mut data); @@ -73,18 +51,11 @@ fn test_slow_request() { #[test] fn test_malformed_request() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) - }).unwrap() - .run(); + let srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().finish())).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut stream = net::TcpStream::connect(addr).unwrap(); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let _ = stream.write_all(b"GET /test/tests/test HTTP1.1\r\n"); let mut data = String::new(); let _ = stream.read_to_string(&mut data); @@ -98,51 +69,42 @@ fn test_content_length() { StatusCode, }; - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|req: Request| { - let indx: usize = req.uri().path()[1..].parse().unwrap(); - let statuses = [ - StatusCode::NO_CONTENT, - StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, - StatusCode::OK, - StatusCode::NOT_FOUND, - ]; - future::ok::<_, ()>(Response::new(statuses[indx])) - }).map(|_| ()) - }).unwrap() - .run(); + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|req: Request| { + let indx: usize = req.uri().path()[1..].parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + future::ok::<_, ()>(Response::new(statuses[indx])) + }).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); let header = HeaderName::from_static("content-length"); let value = HeaderValue::from_static("0"); - let mut sys = System::new("test"); { for i in 0..4 { - let req = client::ClientRequest::get(format!("http://{}/{}", addr, i)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let req = client::ClientRequest::get(srv.url("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert_eq!(response.headers().get(&header), None); - let req = client::ClientRequest::head(format!("http://{}/{}", addr, i)) + let req = client::ClientRequest::head(srv.url(&format!("/{}", i))) .finish() .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let response = srv.send_request(req).unwrap(); assert_eq!(response.headers().get(&header), None); } for i in 4..6 { - let req = client::ClientRequest::get(format!("http://{}/{}", addr, i)) + let req = client::ClientRequest::get(srv.url(&format!("/{}", i))) .finish() .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let response = srv.send_request(req).unwrap(); assert_eq!(response.headers().get(&header), Some(&value)); } } @@ -153,54 +115,41 @@ fn test_headers() { let data = STR.repeat(10); let data2 = data.clone(); - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - let data = data.clone(); - h1::H1Service::new(move |_| { - let mut builder = Response::Ok(); - for idx in 0..90 { - builder.header( - format!("X-TEST-{}", idx).as_str(), - "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); - } - future::ok::<_, ()>(builder.body(data.clone())) - }).map(|_| ()) - }) - .unwrap() - .run() + let mut srv = test::TestServer::with_factory(move || { + let data = data.clone(); + h1::H1Service::new(move |_| { + let mut builder = Response::Ok(); + for idx in 0..90 { + builder.header( + format!("X-TEST-{}", idx).as_str(), + "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", + ); + } + future::ok::<_, ()>(builder.body(data.clone())) + }).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(200)); - let mut sys = System::new("test"); - let mut connector = sys - .block_on(lazy(|| { - Ok::<_, ()>(client2::Connector::default().service()) - })).unwrap(); + let mut connector = srv.new_connector(); - let req = client2::ClientRequest::get(format!("http://{}/", addr)) - .finish() - .unwrap(); + let req = srv.get().finish().unwrap(); - let response = sys.block_on(req.send(&mut connector)).unwrap(); + let response = srv.block_on(req.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from(data2)); } @@ -228,46 +177,27 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_body() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().body(STR))) - .map(|_| ()) - }).unwrap() - .run(); + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut sys = System::new("test"); - let req = client::ClientRequest::get(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let req = srv.get().finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } #[test] fn test_head_empty() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) - }).unwrap() - .run() + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut sys = System::new("test"); - let req = client::ClientRequest::head(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -279,31 +209,20 @@ fn test_head_empty() { } // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert!(bytes.is_empty()); } #[test] fn test_head_binary() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|_| { - ok::<_, ()>( - Response::Ok().content_length(STR.len() as u64).body(STR), - ) - }).map(|_| ()) - }).unwrap() - .run() + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| { + ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) + }).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut sys = System::new("test"); - let req = client::ClientRequest::head(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -315,27 +234,18 @@ fn test_head_binary() { } // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert!(bytes.is_empty()); } #[test] fn test_head_binary2() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) - }).unwrap() - .run() + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut sys = System::new("test"); - let req = client::ClientRequest::head(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -349,57 +259,40 @@ fn test_head_binary2() { #[test] fn test_body_length() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::Ok().body(Body::from_message( - body::SizedStream::new(STR.len(), body), - ))) - }).map(|_| ()) - }).unwrap() - .run() + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| { + let body = once(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .body(Body::from_message(body::SizedStream::new(STR.len(), body))), + ) + }).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut sys = System::new("test"); - let req = client::ClientRequest::get(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let req = srv.get().finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } #[test] fn test_body_chunked_explicit() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|_| { - let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::Ok().streaming(body)) - }).map(|_| ()) - }).unwrap() - .run() + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| { + let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>(Response::Ok().streaming(body)) + }).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut sys = System::new("test"); - let req = client::ClientRequest::get(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let req = srv.get().finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode assert_eq!(bytes, Bytes::from_static(STR.as_ref())); @@ -407,27 +300,18 @@ fn test_body_chunked_explicit() { #[test] fn test_body_chunked_implicit() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|_| { - let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::Ok().streaming(body)) - }).map(|_| ()) - }).unwrap() - .run() + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| { + let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>(Response::Ok().streaming(body)) + }).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut sys = System::new("test"); - let req = client::ClientRequest::get(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let req = srv.get().finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } diff --git a/tests/test_ws.rs b/tests/test_ws.rs index c246d5e47..21f635129 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -5,20 +5,18 @@ extern crate actix_web; extern crate bytes; extern crate futures; -use std::{io, thread}; +use std::io; -use actix::System; use actix_net::codec::Framed; use actix_net::framed::IntoFramed; -use actix_net::server::Server; -use actix_net::service::{NewServiceExt, Service}; +use actix_net::service::NewServiceExt; use actix_net::stream::TakeItem; -use actix_web::{test, ws as web_ws}; +use actix_web::ws as web_ws; use bytes::{Bytes, BytesMut}; -use futures::future::{lazy, ok, Either}; -use futures::{Future, IntoFuture, Sink, Stream}; +use futures::future::{ok, Either}; +use futures::{Future, Sink, Stream}; -use actix_http::{h1, ws, ResponseError, SendResponse, ServiceConfig}; +use actix_http::{h1, test, ws, ResponseError, SendResponse, ServiceConfig}; fn ws_service(req: ws::Frame) -> impl Future { match req { @@ -43,72 +41,66 @@ fn ws_service(req: ws::Frame) -> impl Future)| { - // validate request - if let Some(h1::Message::Item(req)) = req { - match ws::verify_handshake(&req) { - Err(e) => { - // validation failed - Either::A( - SendResponse::send(framed, e.error_response()) - .map_err(|_| ()) - .map(|_| ()), - ) - } - Ok(_) => { - Either::B( - // send handshake response - SendResponse::send( - framed, - ws::handshake_response(&req).finish(), - ).map_err(|_| ()) - .and_then(|framed| { - // start websocket service - let framed = - framed.into_framed(ws::Codec::new()); - ws::Transport::with(framed, ws_service) - .map_err(|_| ()) - }), - ) - } - } - } else { - panic!() + let mut srv = test::TestServer::with_factory(|| { + IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) + .and_then(TakeItem::new().map_err(|_| ())) + .and_then(|(req, framed): (_, Framed<_, _>)| { + // validate request + if let Some(h1::Message::Item(req)) = req { + match ws::verify_handshake(&req) { + Err(e) => { + // validation failed + Either::A( + SendResponse::send(framed, e.error_response()) + .map_err(|_| ()) + .map(|_| ()), + ) } - }) - }).unwrap() - .run(); + Ok(_) => { + Either::B( + // send handshake response + SendResponse::send( + framed, + ws::handshake_response(&req).finish(), + ).map_err(|_| ()) + .and_then(|framed| { + // start websocket service + let framed = framed.into_framed(ws::Codec::new()); + ws::Transport::with(framed, ws_service) + .map_err(|_| ()) + }), + ) + } + } + } else { + panic!() + } + }) }); - let mut sys = System::new("test"); { - let (reader, mut writer) = sys - .block_on(web_ws::Client::new(format!("http://{}/", addr)).connect()) - .unwrap(); + let url = srv.url("/"); + + let (reader, mut writer) = + srv.block_on(web_ws::Client::new(url).connect()).unwrap(); writer.text("text"); - let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + let (item, reader) = srv.block_on(reader.into_future()).unwrap(); assert_eq!(item, Some(web_ws::Message::Text("text".to_owned()))); writer.binary(b"text".as_ref()); - let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + let (item, reader) = srv.block_on(reader.into_future()).unwrap(); assert_eq!( item, Some(web_ws::Message::Binary(Bytes::from_static(b"text").into())) ); writer.ping("ping"); - let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + let (item, reader) = srv.block_on(reader.into_future()).unwrap(); assert_eq!(item, Some(web_ws::Message::Pong("ping".to_owned()))); writer.close(Some(web_ws::CloseCode::Normal.into())); - let (item, _) = sys.block_on(reader.into_future()).unwrap(); + let (item, _) = srv.block_on(reader.into_future()).unwrap(); assert_eq!( item, Some(web_ws::Message::Close(Some( @@ -118,39 +110,33 @@ fn test_simple() { } // client service - let mut client = sys - .block_on(lazy(|| Ok::<_, ()>(ws::Client::default()).into_future())) - .unwrap(); - let framed = sys - .block_on(client.call(ws::Connect::new(format!("http://{}/", addr)))) - .unwrap(); - - let framed = sys + let framed = srv.ws().unwrap(); + let framed = srv .block_on(framed.send(ws::Message::Text("text".to_string()))) .unwrap(); - let (item, framed) = sys.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text"))))); - let framed = sys + let framed = srv .block_on(framed.send(ws::Message::Binary("text".into()))) .unwrap(); - let (item, framed) = sys.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); assert_eq!( item, Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))) ); - let framed = sys + let framed = srv .block_on(framed.send(ws::Message::Ping("text".into()))) .unwrap(); - let (item, framed) = sys.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into()))); - let framed = sys + let framed = srv .block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))) .unwrap(); - let (item, _framed) = sys.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); assert_eq!( item, Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) From 3901239128ac9d076cdaaeb46220117a8043f7ad Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 19 Nov 2018 14:57:12 -0800 Subject: [PATCH 081/208] unify requedt/response encoder --- src/client/pipeline.rs | 13 +- src/h1/client.rs | 185 ++++++++++------------------ src/h1/codec.rs | 184 +++++++--------------------- src/h1/decoder.rs | 97 +++++++-------- src/h1/encoder.rs | 268 ++++++++++++++++++++++++++++++++--------- src/message.rs | 8 +- src/request.rs | 21 ++-- src/response.rs | 61 +++++++++- src/test.rs | 2 +- src/ws/mod.rs | 3 +- tests/test_server.rs | 4 +- tests/test_ws.rs | 7 +- 12 files changed, 448 insertions(+), 405 deletions(-) diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 63551cffa..e1d8421e9 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -49,7 +49,10 @@ where .and_then(|(item, framed)| { if let Some(res) = item { match framed.get_codec().message_type() { - h1::MessageType::None => release_connection(framed), + h1::MessageType::None => { + let force_close = !framed.get_codec().keepalive(); + release_connection(framed, force_close) + } _ => { *res.payload.borrow_mut() = Some(Payload::stream(framed)) } @@ -174,7 +177,9 @@ impl Stream for Payload { Async::Ready(Some(chunk)) => if let Some(chunk) = chunk { Ok(Async::Ready(Some(chunk))) } else { - release_connection(self.framed.take().unwrap()); + let framed = self.framed.take().unwrap(); + let force_close = framed.get_codec().keepalive(); + release_connection(framed, force_close); Ok(Async::Ready(None)) }, Async::Ready(None) => Ok(Async::Ready(None)), @@ -182,12 +187,12 @@ impl Stream for Payload { } } -fn release_connection(framed: Framed) +fn release_connection(framed: Framed, force_close: bool) where T: Connection, { let mut parts = framed.into_parts(); - if parts.read_buf.is_empty() && parts.write_buf.is_empty() { + if !force_close && parts.read_buf.is_empty() && parts.write_buf.is_empty() { parts.io.release() } else { parts.io.close() diff --git a/src/h1/client.rs b/src/h1/client.rs index e2d1eefe6..7704ba97a 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -4,8 +4,8 @@ use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; -use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; -use super::encoder::RequestEncoder; +use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; +use super::{decoder, encoder}; use super::{Message, MessageType}; use body::BodyLength; use client::ClientResponse; @@ -16,13 +16,11 @@ use http::header::{ HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, }; use http::{Method, Version}; -use message::{Head, MessagePool, RequestHead}; +use message::{ConnectionType, Head, MessagePool, RequestHead}; bitflags! { struct Flags: u8 { const HEAD = 0b0000_0001; - const UPGRADE = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; const KEEPALIVE_ENABLED = 0b0000_1000; const STREAM = 0b0001_0000; } @@ -42,14 +40,15 @@ pub struct ClientPayloadCodec { struct ClientCodecInner { config: ServiceConfig, - decoder: MessageDecoder, + decoder: decoder::MessageDecoder, payload: Option, version: Version, + ctype: ConnectionType, // encoder part flags: Flags, headers_size: u32, - te: RequestEncoder, + encoder: encoder::MessageEncoder, } impl Default for ClientCodec { @@ -71,25 +70,26 @@ impl ClientCodec { ClientCodec { inner: ClientCodecInner { config, - decoder: MessageDecoder::default(), + decoder: decoder::MessageDecoder::default(), payload: None, version: Version::HTTP_11, + ctype: ConnectionType::Close, flags, headers_size: 0, - te: RequestEncoder::default(), + encoder: encoder::MessageEncoder::default(), }, } } /// Check if request is upgrade pub fn upgrade(&self) -> bool { - self.inner.flags.contains(Flags::UPGRADE) + self.inner.ctype == ConnectionType::Upgrade } /// Check if last response is keep-alive pub fn keepalive(&self) -> bool { - self.inner.flags.contains(Flags::KEEPALIVE) + self.inner.ctype == ConnectionType::KeepAlive } /// Check last request's message type @@ -103,15 +103,6 @@ impl ClientCodec { } } - /// prepare transfer encoding - pub fn prepare_te(&mut self, head: &mut RequestHead, length: BodyLength) { - self.inner.te.update( - head, - self.inner.flags.contains(Flags::HEAD), - self.inner.version, - ); - } - /// Convert message codec to a payload codec pub fn into_payload_codec(self) -> ClientPayloadCodec { ClientPayloadCodec { inner: self.inner } @@ -119,96 +110,17 @@ impl ClientCodec { } impl ClientPayloadCodec { + /// Check if last response is keep-alive + pub fn keepalive(&self) -> bool { + self.inner.ctype == ConnectionType::KeepAlive + } + /// Transform payload codec to a message codec pub fn into_message_codec(self) -> ClientCodec { ClientCodec { inner: self.inner } } } -fn prn_version(ver: Version) -> &'static str { - match ver { - Version::HTTP_09 => "HTTP/0.9", - Version::HTTP_10 => "HTTP/1.0", - Version::HTTP_11 => "HTTP/1.1", - Version::HTTP_2 => "HTTP/2.0", - } -} - -impl ClientCodecInner { - fn encode_request( - &mut self, - msg: RequestHead, - length: BodyLength, - buffer: &mut BytesMut, - ) -> io::Result<()> { - // render message - { - // status line - write!( - Writer(buffer), - "{} {} {}\r\n", - msg.method, - msg.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), - prn_version(msg.version) - ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - - // write headers - buffer.reserve(msg.headers.len() * AVERAGE_HEADER_SIZE); - - // content length - match length { - BodyLength::Sized(len) => helpers::write_content_length(len, buffer), - BodyLength::Sized64(len) => { - buffer.extend_from_slice(b"content-length: "); - write!(buffer.writer(), "{}", len)?; - buffer.extend_from_slice(b"\r\n"); - } - BodyLength::Chunked => { - buffer.extend_from_slice(b"transfer-encoding: chunked\r\n") - } - BodyLength::Empty => buffer.extend_from_slice(b"content-length: 0\r\n"), - BodyLength::None | BodyLength::Stream => (), - } - - let mut has_date = false; - - for (key, value) in &msg.headers { - match *key { - TRANSFER_ENCODING | CONNECTION | CONTENT_LENGTH => continue, - DATE => has_date = true, - _ => (), - } - - buffer.put_slice(key.as_ref()); - buffer.put_slice(b": "); - buffer.put_slice(value.as_ref()); - buffer.put_slice(b"\r\n"); - } - - // Connection header - if msg.upgrade() { - self.flags.set(Flags::UPGRADE, msg.upgrade()); - buffer.extend_from_slice(b"connection: upgrade\r\n"); - } else if msg.keep_alive() { - if self.version < Version::HTTP_11 { - buffer.extend_from_slice(b"connection: keep-alive\r\n"); - } - } else if self.version >= Version::HTTP_11 { - buffer.extend_from_slice(b"connection: close\r\n"); - } - - // Date header - if !has_date { - self.config.set_date(buffer); - } else { - buffer.extend_from_slice(b"\r\n"); - } - } - - Ok(()) - } -} - impl Decoder for ClientCodec { type Item = ClientResponse; type Error = ParseError; @@ -217,21 +129,27 @@ impl Decoder for ClientCodec { debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set"); if let Some((req, payload)) = self.inner.decoder.decode(src)? { - // self.inner - // .flags - // .set(Flags::HEAD, req.head.method == Method::HEAD); - // self.inner.version = req.head.version; - if self.inner.flags.contains(Flags::KEEPALIVE_ENABLED) { - self.inner.flags.set(Flags::KEEPALIVE, req.keep_alive()); + if let Some(ctype) = req.head().ctype { + // do not use peer's keep-alive + self.inner.ctype = if ctype == ConnectionType::KeepAlive { + self.inner.ctype + } else { + ctype + }; } - match payload { - PayloadType::None => self.inner.payload = None, - PayloadType::Payload(pl) => self.inner.payload = Some(pl), - PayloadType::Stream(pl) => { - self.inner.payload = Some(pl); - self.inner.flags.insert(Flags::STREAM); + + if !self.inner.flags.contains(Flags::HEAD) { + match payload { + PayloadType::None => self.inner.payload = None, + PayloadType::Payload(pl) => self.inner.payload = Some(pl), + PayloadType::Stream(pl) => { + self.inner.payload = Some(pl); + self.inner.flags.insert(Flags::STREAM); + } } - }; + } else { + self.inner.payload = None; + } Ok(Some(req)) } else { Ok(None) @@ -270,14 +188,39 @@ impl Encoder for ClientCodec { dst: &mut BytesMut, ) -> Result<(), Self::Error> { match item { - Message::Item((msg, btype)) => { - self.inner.encode_request(msg, btype, dst)?; + Message::Item((mut msg, length)) => { + let inner = &mut self.inner; + inner.version = msg.version; + inner.flags.set(Flags::HEAD, msg.method == Method::HEAD); + + // connection status + inner.ctype = match msg.connection_type() { + ConnectionType::KeepAlive => { + if inner.flags.contains(Flags::KEEPALIVE_ENABLED) { + ConnectionType::KeepAlive + } else { + ConnectionType::Close + } + } + ConnectionType::Upgrade => ConnectionType::Upgrade, + ConnectionType::Close => ConnectionType::Close, + }; + + inner.encoder.encode( + dst, + &mut msg, + false, + inner.version, + length, + inner.ctype, + &inner.config, + )?; } Message::Chunk(Some(bytes)) => { - self.inner.te.encode(bytes.as_ref(), dst)?; + self.inner.encoder.encode_chunk(bytes.as_ref(), dst)?; } Message::Chunk(None) => { - self.inner.te.encode_eof(dst)?; + self.inner.encoder.encode_eof(dst)?; } } Ok(()) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 117c8cde1..f9f455e5d 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -5,8 +5,8 @@ use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; -use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; -use super::encoder::ResponseEncoder; +use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; +use super::{decoder, encoder}; use super::{Message, MessageType}; use body::BodyLength; use config::ServiceConfig; @@ -14,15 +14,13 @@ use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{Method, StatusCode, Version}; -use message::{Head, ResponseHead}; +use message::{ConnectionType, Head, ResponseHead}; use request::Request; use response::Response; bitflags! { struct Flags: u8 { const HEAD = 0b0000_0001; - const UPGRADE = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; const KEEPALIVE_ENABLED = 0b0000_1000; const STREAM = 0b0001_0000; } @@ -33,14 +31,15 @@ const AVERAGE_HEADER_SIZE: usize = 30; /// HTTP/1 Codec pub struct Codec { config: ServiceConfig, - decoder: MessageDecoder, + decoder: decoder::MessageDecoder, payload: Option, version: Version, + ctype: ConnectionType, // encoder part flags: Flags, headers_size: u32, - te: ResponseEncoder, + encoder: encoder::MessageEncoder>, } impl Default for Codec { @@ -67,24 +66,25 @@ impl Codec { }; Codec { config, - decoder: MessageDecoder::default(), + decoder: decoder::MessageDecoder::default(), payload: None, version: Version::HTTP_11, + ctype: ConnectionType::Close, flags, headers_size: 0, - te: ResponseEncoder::default(), + encoder: encoder::MessageEncoder::default(), } } /// Check if request is upgrade pub fn upgrade(&self) -> bool { - self.flags.contains(Flags::UPGRADE) + self.ctype == ConnectionType::Upgrade } /// Check if last response is keep-alive pub fn keepalive(&self) -> bool { - self.flags.contains(Flags::KEEPALIVE) + self.ctype == ConnectionType::KeepAlive } /// Check last request's message type @@ -97,130 +97,6 @@ impl Codec { MessageType::Payload } } - - /// prepare transfer encoding - fn prepare_te(&mut self, head: &mut ResponseHead, length: BodyLength) { - self.te - .update(head, self.flags.contains(Flags::HEAD), self.version, length); - } - - fn encode_response( - &mut self, - msg: &mut ResponseHead, - length: BodyLength, - buffer: &mut BytesMut, - ) -> io::Result<()> { - msg.version = self.version; - - // Connection upgrade - if msg.upgrade() { - self.flags.insert(Flags::UPGRADE); - self.flags.remove(Flags::KEEPALIVE); - msg.headers - .insert(CONNECTION, HeaderValue::from_static("upgrade")); - } - // keep-alive - else if self.flags.contains(Flags::KEEPALIVE_ENABLED) && msg.keep_alive() { - self.flags.insert(Flags::KEEPALIVE); - if self.version < Version::HTTP_11 { - msg.headers - .insert(CONNECTION, HeaderValue::from_static("keep-alive")); - } - } else if self.version >= Version::HTTP_11 { - self.flags.remove(Flags::KEEPALIVE); - msg.headers - .insert(CONNECTION, HeaderValue::from_static("close")); - } - - // render message - { - let reason = msg.reason().as_bytes(); - buffer.reserve(256 + msg.headers.len() * AVERAGE_HEADER_SIZE + reason.len()); - - // status line - helpers::write_status_line(self.version, msg.status.as_u16(), buffer); - buffer.extend_from_slice(reason); - - // content length - match msg.status { - StatusCode::NO_CONTENT - | StatusCode::CONTINUE - | StatusCode::SWITCHING_PROTOCOLS - | StatusCode::PROCESSING => buffer.extend_from_slice(b"\r\n"), - _ => match length { - BodyLength::Chunked => { - buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") - } - BodyLength::Empty => { - buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n"); - } - BodyLength::Sized(len) => helpers::write_content_length(len, buffer), - BodyLength::Sized64(len) => { - buffer.extend_from_slice(b"\r\ncontent-length: "); - write!(buffer.writer(), "{}", len)?; - buffer.extend_from_slice(b"\r\n"); - } - BodyLength::None | BodyLength::Stream => { - buffer.extend_from_slice(b"\r\n") - } - }, - } - - // write headers - let mut pos = 0; - let mut has_date = false; - let mut remaining = buffer.remaining_mut(); - let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) }; - for (key, value) in &msg.headers { - match *key { - TRANSFER_ENCODING | CONTENT_LENGTH => continue, - DATE => { - has_date = true; - } - _ => (), - } - - let v = value.as_ref(); - let k = key.as_str().as_bytes(); - let len = k.len() + v.len() + 4; - if len > remaining { - unsafe { - buffer.advance_mut(pos); - } - pos = 0; - buffer.reserve(len); - remaining = buffer.remaining_mut(); - unsafe { - buf = &mut *(buffer.bytes_mut() as *mut _); - } - } - - buf[pos..pos + k.len()].copy_from_slice(k); - pos += k.len(); - buf[pos..pos + 2].copy_from_slice(b": "); - pos += 2; - buf[pos..pos + v.len()].copy_from_slice(v); - pos += v.len(); - buf[pos..pos + 2].copy_from_slice(b"\r\n"); - pos += 2; - remaining -= len; - } - unsafe { - buffer.advance_mut(pos); - } - - // optimized date header, set_date writes \r\n - if !has_date { - self.config.set_date(buffer); - } else { - // msg eof - buffer.extend_from_slice(b"\r\n"); - } - self.headers_size = buffer.len() as u32; - } - - Ok(()) - } } impl Decoder for Codec { @@ -240,9 +116,12 @@ impl Decoder for Codec { } else if let Some((req, payload)) = self.decoder.decode(src)? { self.flags .set(Flags::HEAD, req.inner.head.method == Method::HEAD); - self.version = req.inner.head.version; - if self.flags.contains(Flags::KEEPALIVE_ENABLED) { - self.flags.set(Flags::KEEPALIVE, req.keep_alive()); + self.version = req.inner().head.version; + self.ctype = req.inner().head.connection_type(); + if self.ctype == ConnectionType::KeepAlive + && !self.flags.contains(Flags::KEEPALIVE_ENABLED) + { + self.ctype = ConnectionType::Close } match payload { PayloadType::None => self.payload = None, @@ -270,14 +149,35 @@ impl Encoder for Codec { ) -> Result<(), Self::Error> { match item { Message::Item((mut res, length)) => { - self.prepare_te(res.head_mut(), length); - self.encode_response(res.head_mut(), length, dst)?; + // connection status + self.ctype = if let Some(ct) = res.head().ctype { + if ct == ConnectionType::KeepAlive { + self.ctype + } else { + ct + } + } else { + self.ctype + }; + + // encode message + let len = dst.len(); + self.encoder.encode( + dst, + &mut res, + self.flags.contains(Flags::HEAD), + self.version, + length, + self.ctype, + &self.config, + )?; + self.headers_size = (dst.len() - len) as u32; } Message::Chunk(Some(bytes)) => { - self.te.encode(bytes.as_ref(), dst)?; + self.encoder.encode_chunk(bytes.as_ref(), dst)?; } Message::Chunk(None) => { - self.te.encode_eof(dst)?; + self.encoder.encode_eof(dst)?; } } Ok(()) diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index de0df367f..a081a5cf3 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -10,15 +10,16 @@ use client::ClientResponse; use error::ParseError; use http::header::{HeaderName, HeaderValue}; use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version}; -use message::{ConnectionType, Head}; +use message::ConnectionType; use request::Request; const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; /// Incoming messagd decoder -pub(crate) struct MessageDecoder(PhantomData); +pub(crate) struct MessageDecoder(PhantomData); +#[derive(Debug)] /// Incoming request type pub(crate) enum PayloadType { None, @@ -26,13 +27,13 @@ pub(crate) enum PayloadType { Stream(PayloadDecoder), } -impl Default for MessageDecoder { +impl Default for MessageDecoder { fn default() -> Self { MessageDecoder(PhantomData) } } -impl Decoder for MessageDecoder { +impl Decoder for MessageDecoder { type Item = (T, PayloadType); type Error = ParseError; @@ -47,10 +48,8 @@ pub(crate) enum PayloadLength { None, } -pub(crate) trait MessageTypeDecoder: Sized { - fn keep_alive(&mut self); - - fn force_close(&mut self); +pub(crate) trait MessageType: Sized { + fn set_connection_type(&mut self, ctype: Option); fn headers_mut(&mut self) -> &mut HeaderMap; @@ -59,10 +58,9 @@ pub(crate) trait MessageTypeDecoder: Sized { fn set_headers( &mut self, slice: &Bytes, - version: Version, raw_headers: &[HeaderIndex], ) -> Result { - let mut ka = version != Version::HTTP_10; + let mut ka = None; let mut has_upgrade = false; let mut chunked = false; let mut content_length = None; @@ -104,18 +102,18 @@ pub(crate) trait MessageTypeDecoder: Sized { // connection keep-alive state header::CONNECTION => { ka = if let Ok(conn) = value.to_str() { - if version == Version::HTTP_10 - && conn.contains("keep-alive") - { - true + if conn.contains("keep-alive") { + Some(ConnectionType::KeepAlive) + } else if conn.contains("close") { + Some(ConnectionType::Close) + } else if conn.contains("upgrade") { + Some(ConnectionType::Upgrade) } else { - version == Version::HTTP_11 && !(conn - .contains("close") - || conn.contains("upgrade")) + None } } else { - false - } + None + }; } header::UPGRADE => { has_upgrade = true; @@ -136,12 +134,7 @@ pub(crate) trait MessageTypeDecoder: Sized { } } } - - if ka { - self.keep_alive(); - } else { - self.force_close(); - } + self.set_connection_type(ka); // https://tools.ietf.org/html/rfc7230#section-3.3.3 if chunked { @@ -162,17 +155,9 @@ pub(crate) trait MessageTypeDecoder: Sized { } } -impl MessageTypeDecoder for Request { - fn keep_alive(&mut self) { - self.inner_mut() - .head - .set_connection_type(ConnectionType::KeepAlive) - } - - fn force_close(&mut self) { - self.inner_mut() - .head - .set_connection_type(ConnectionType::Close) +impl MessageType for Request { + fn set_connection_type(&mut self, ctype: Option) { + self.inner_mut().head.ctype = ctype; } fn headers_mut(&mut self) -> &mut HeaderMap { @@ -210,8 +195,7 @@ impl MessageTypeDecoder for Request { let mut msg = Request::new(); // convert headers - let len = - msg.set_headers(&src.split_to(len).freeze(), ver, &headers[..h_len])?; + let len = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; // payload decoder let decoder = match len { @@ -243,13 +227,9 @@ impl MessageTypeDecoder for Request { } } -impl MessageTypeDecoder for ClientResponse { - fn keep_alive(&mut self) { - self.head.set_connection_type(ConnectionType::KeepAlive); - } - - fn force_close(&mut self) { - self.head.set_connection_type(ConnectionType::Close); +impl MessageType for ClientResponse { + fn set_connection_type(&mut self, ctype: Option) { + self.head.ctype = ctype; } fn headers_mut(&mut self) -> &mut HeaderMap { @@ -286,8 +266,7 @@ impl MessageTypeDecoder for ClientResponse { let mut msg = ClientResponse::new(); // convert headers - let len = - msg.set_headers(&src.split_to(len).freeze(), ver, &headers[..h_len])?; + let len = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; // message payload let decoder = if let PayloadLength::Payload(pl) = len { @@ -634,6 +613,7 @@ mod tests { use super::*; use error::ParseError; use httpmessage::HttpMessage; + use message::Head; impl PayloadType { fn unwrap(self) -> PayloadDecoder { @@ -886,7 +866,7 @@ mod tests { let mut buf = BytesMut::from("GET /test HTTP/1.0\r\n\r\n"); let req = parse_ready!(&mut buf); - assert!(!req.keep_alive()); + assert_eq!(req.head().connection_type(), ConnectionType::Close); } #[test] @@ -894,7 +874,7 @@ mod tests { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); let req = parse_ready!(&mut buf); - assert!(req.keep_alive()); + assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); } #[test] @@ -905,7 +885,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert!(!req.keep_alive()); + assert_eq!(req.inner().head.ctype, Some(ConnectionType::Close)); } #[test] @@ -916,7 +896,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert!(!req.keep_alive()); + assert_eq!(req.inner().head.ctype, Some(ConnectionType::Close)); } #[test] @@ -927,7 +907,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert!(req.keep_alive()); + assert_eq!(req.inner().head.ctype, Some(ConnectionType::KeepAlive)); } #[test] @@ -938,7 +918,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert!(req.keep_alive()); + assert_eq!(req.inner().head.ctype, Some(ConnectionType::KeepAlive)); } #[test] @@ -949,7 +929,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert!(!req.keep_alive()); + assert_eq!(req.inner().head.connection_type(), ConnectionType::Close); } #[test] @@ -960,7 +940,11 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert!(req.keep_alive()); + assert_eq!(req.inner().head.ctype, None); + assert_eq!( + req.inner().head.connection_type(), + ConnectionType::KeepAlive + ); } #[test] @@ -973,6 +957,7 @@ mod tests { let req = parse_ready!(&mut buf); assert!(req.upgrade()); + assert_eq!(req.inner().head.ctype, Some(ConnectionType::Upgrade)); } #[test] @@ -1070,7 +1055,7 @@ mod tests { ); let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); - assert!(!req.keep_alive()); + assert_eq!(req.inner().head.ctype, Some(ConnectionType::Upgrade)); assert!(req.upgrade()); assert!(pl.is_unhandled()); } diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 1664af162..f9b4aab35 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -1,40 +1,217 @@ #![allow(unused_imports, unused_variables, dead_code)] use std::fmt::Write as FmtWrite; use std::io::Write; +use std::marker::PhantomData; use std::str::FromStr; use std::{cmp, fmt, io, mem}; -use bytes::{Bytes, BytesMut}; -use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; -use http::{StatusCode, Version}; +use bytes::{BufMut, Bytes, BytesMut}; +use http::header::{ + HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, +}; +use http::{HeaderMap, StatusCode, Version}; use body::BodyLength; +use config::ServiceConfig; use header::ContentEncoding; +use helpers; use http::Method; -use message::{RequestHead, ResponseHead}; +use message::{ConnectionType, RequestHead, ResponseHead}; use request::Request; use response::Response; +const AVERAGE_HEADER_SIZE: usize = 30; + #[derive(Debug)] -pub(crate) struct ResponseEncoder { - head: bool, +pub(crate) struct MessageEncoder { pub length: BodyLength, pub te: TransferEncoding, + _t: PhantomData, } -impl Default for ResponseEncoder { +impl Default for MessageEncoder { fn default() -> Self { - ResponseEncoder { - head: false, + MessageEncoder { length: BodyLength::None, te: TransferEncoding::empty(), + _t: PhantomData, } } } -impl ResponseEncoder { +pub(crate) trait MessageType: Sized { + fn status(&self) -> Option; + + fn connection_type(&self) -> Option; + + fn headers(&self) -> &HeaderMap; + + fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()>; + + fn encode_headers( + &mut self, + dst: &mut BytesMut, + version: Version, + mut length: BodyLength, + ctype: ConnectionType, + config: &ServiceConfig, + ) -> io::Result<()> { + let mut skip_len = length != BodyLength::Stream; + + // Content length + if let Some(status) = self.status() { + match status { + StatusCode::NO_CONTENT + | StatusCode::CONTINUE + | StatusCode::PROCESSING => length = BodyLength::None, + StatusCode::SWITCHING_PROTOCOLS => { + skip_len = true; + length = BodyLength::Stream; + } + _ => (), + } + } + match length { + BodyLength::Chunked => { + dst.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") + } + BodyLength::Empty => { + dst.extend_from_slice(b"\r\ncontent-length: 0\r\n"); + } + BodyLength::Sized(len) => helpers::write_content_length(len, dst), + BodyLength::Sized64(len) => { + dst.extend_from_slice(b"\r\ncontent-length: "); + write!(dst.writer(), "{}", len)?; + dst.extend_from_slice(b"\r\n"); + } + BodyLength::None | BodyLength::Stream => dst.extend_from_slice(b"\r\n"), + } + + // Connection + match ctype { + ConnectionType::Upgrade => dst.extend_from_slice(b"connection: upgrade\r\n"), + ConnectionType::KeepAlive if version < Version::HTTP_11 => { + dst.extend_from_slice(b"connection: keep-alive\r\n") + } + ConnectionType::Close if version >= Version::HTTP_11 => { + dst.extend_from_slice(b"connection: close\r\n") + } + _ => (), + } + + // write headers + let mut pos = 0; + let mut has_date = false; + let mut remaining = dst.remaining_mut(); + let mut buf = unsafe { &mut *(dst.bytes_mut() as *mut [u8]) }; + for (key, value) in self.headers() { + match key { + &CONNECTION => continue, + &TRANSFER_ENCODING | &CONTENT_LENGTH if skip_len => continue, + &DATE => { + has_date = true; + } + _ => (), + } + + let v = value.as_ref(); + let k = key.as_str().as_bytes(); + let len = k.len() + v.len() + 4; + if len > remaining { + unsafe { + dst.advance_mut(pos); + } + pos = 0; + dst.reserve(len); + remaining = dst.remaining_mut(); + unsafe { + buf = &mut *(dst.bytes_mut() as *mut _); + } + } + + buf[pos..pos + k.len()].copy_from_slice(k); + pos += k.len(); + buf[pos..pos + 2].copy_from_slice(b": "); + pos += 2; + buf[pos..pos + v.len()].copy_from_slice(v); + pos += v.len(); + buf[pos..pos + 2].copy_from_slice(b"\r\n"); + pos += 2; + remaining -= len; + } + unsafe { + dst.advance_mut(pos); + } + + // optimized date header, set_date writes \r\n + if !has_date { + config.set_date(dst); + } else { + // msg eof + dst.extend_from_slice(b"\r\n"); + } + + Ok(()) + } +} + +impl MessageType for Response<()> { + fn status(&self) -> Option { + Some(self.head().status) + } + + fn connection_type(&self) -> Option { + self.head().ctype + } + + fn headers(&self) -> &HeaderMap { + &self.head().headers + } + + fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> { + let head = self.head(); + let reason = head.reason().as_bytes(); + dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE + reason.len()); + + // status line + helpers::write_status_line(head.version, head.status.as_u16(), dst); + dst.extend_from_slice(reason); + Ok(()) + } +} + +impl MessageType for RequestHead { + fn status(&self) -> Option { + None + } + + fn connection_type(&self) -> Option { + self.ctype + } + + fn headers(&self) -> &HeaderMap { + &self.headers + } + + fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> { + write!( + Writer(dst), + "{} {} {}", + self.method, + self.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), + match self.version { + Version::HTTP_09 => "HTTP/0.9", + Version::HTTP_10 => "HTTP/1.0", + Version::HTTP_11 => "HTTP/1.1", + Version::HTTP_2 => "HTTP/2.0", + } + ).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + } +} + +impl MessageEncoder { /// Encode message - pub fn encode(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result { + pub fn encode_chunk(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result { self.te.encode(msg, buf) } @@ -43,59 +220,32 @@ impl ResponseEncoder { self.te.encode_eof(buf) } - pub fn update( + pub fn encode( &mut self, - resp: &mut ResponseHead, + dst: &mut BytesMut, + message: &mut T, head: bool, version: Version, length: BodyLength, - ) { - self.head = head; - let transfer = match length { - BodyLength::Empty => TransferEncoding::empty(), - BodyLength::Sized(len) => TransferEncoding::length(len as u64), - BodyLength::Sized64(len) => TransferEncoding::length(len), - BodyLength::Chunked => TransferEncoding::chunked(), - BodyLength::Stream => TransferEncoding::eof(), - BodyLength::None => TransferEncoding::length(0), - }; - // check for head response - if !self.head { - self.te = transfer; + ctype: ConnectionType, + config: &ServiceConfig, + ) -> io::Result<()> { + // transfer encoding + if !head { + self.te = match length { + BodyLength::Empty => TransferEncoding::empty(), + BodyLength::Sized(len) => TransferEncoding::length(len as u64), + BodyLength::Sized64(len) => TransferEncoding::length(len), + BodyLength::Chunked => TransferEncoding::chunked(), + BodyLength::Stream => TransferEncoding::eof(), + BodyLength::None => TransferEncoding::empty(), + }; + } else { + self.te = TransferEncoding::empty(); } - } -} -#[derive(Debug)] -pub(crate) struct RequestEncoder { - head: bool, - pub length: BodyLength, - pub te: TransferEncoding, -} - -impl Default for RequestEncoder { - fn default() -> Self { - RequestEncoder { - head: false, - length: BodyLength::None, - te: TransferEncoding::empty(), - } - } -} - -impl RequestEncoder { - /// Encode message - pub fn encode(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result { - self.te.encode(msg, buf) - } - - /// Encode eof - pub fn encode_eof(&mut self, buf: &mut BytesMut) -> io::Result<()> { - self.te.encode_eof(buf) - } - - pub fn update(&mut self, resp: &mut RequestHead, head: bool, version: Version) { - self.head = head; + message.encode_status(dst)?; + message.encode_headers(dst, version, length, ctype, config) } } @@ -123,7 +273,7 @@ impl TransferEncoding { #[inline] pub fn empty() -> TransferEncoding { TransferEncoding { - kind: TransferEncodingKind::Eof, + kind: TransferEncodingKind::Length(0), } } diff --git a/src/message.rs b/src/message.rs index e1059fa48..d3d52e2f9 100644 --- a/src/message.rs +++ b/src/message.rs @@ -39,12 +39,13 @@ pub trait Head: Default + 'static { fn pool() -> &'static MessagePool; } +#[derive(Debug)] pub struct RequestHead { pub uri: Uri, pub method: Method, pub version: Version, pub headers: HeaderMap, - ctype: Option, + pub ctype: Option, } impl Default for RequestHead { @@ -72,7 +73,7 @@ impl Head for RequestHead { fn connection_type(&self) -> ConnectionType { if let Some(ct) = self.ctype { ct - } else if self.version <= Version::HTTP_11 { + } else if self.version < Version::HTTP_11 { ConnectionType::Close } else { ConnectionType::KeepAlive @@ -84,6 +85,7 @@ impl Head for RequestHead { } } +#[derive(Debug)] pub struct ResponseHead { pub version: Version, pub status: StatusCode, @@ -118,7 +120,7 @@ impl Head for ResponseHead { fn connection_type(&self) -> ConnectionType { if let Some(ct) = self.ctype { ct - } else if self.version <= Version::HTTP_11 { + } else if self.version < Version::HTTP_11 { ConnectionType::Close } else { ConnectionType::KeepAlive diff --git a/src/request.rs b/src/request.rs index d529c09f9..248555e60 100644 --- a/src/request.rs +++ b/src/request.rs @@ -8,7 +8,7 @@ use extensions::Extensions; use httpmessage::HttpMessage; use payload::Payload; -use message::{Head, Message, MessagePool, RequestHead}; +use message::{Message, MessagePool, RequestHead}; /// Request pub struct Request { @@ -67,6 +67,19 @@ impl Request { Rc::get_mut(&mut self.inner).expect("Multiple copies exist") } + #[inline] + /// Http message part of the request + pub fn head(&self) -> &RequestHead { + &self.inner.as_ref().head + } + + #[inline] + #[doc(hidden)] + /// Mutable reference to a http message part of the request + pub fn head_mut(&mut self) -> &mut RequestHead { + &mut self.inner_mut().head + } + /// Request's uri. #[inline] pub fn uri(&self) -> &Uri { @@ -109,12 +122,6 @@ impl Request { &mut self.inner_mut().head.headers } - /// Checks if a connection should be kept alive. - #[inline] - pub fn keep_alive(&self) -> bool { - self.inner().head.keep_alive() - } - /// Request extensions #[inline] pub fn extensions(&self) -> Ref { diff --git a/src/response.rs b/src/response.rs index f2ed72925..bc730718d 100644 --- a/src/response.rs +++ b/src/response.rs @@ -85,7 +85,14 @@ impl Response { } #[inline] - pub(crate) fn head_mut(&mut self) -> &mut ResponseHead { + /// Http message part of the response + pub fn head(&self) -> &ResponseHead { + &self.0.as_ref().head + } + + #[inline] + /// Mutable reference to a http message part of the response + pub fn head_mut(&mut self) -> &mut ResponseHead { &mut self.0.as_mut().head } @@ -314,7 +321,7 @@ impl ResponseBuilder { self } - /// Set a header. + /// Append a header to existing headers. /// /// ```rust,ignore /// # extern crate actix_web; @@ -347,6 +354,39 @@ impl ResponseBuilder { self } + /// Set a header. + /// + /// ```rust,ignore + /// # extern crate actix_web; + /// use actix_web::{http, Request, Response}; + /// + /// fn index(req: HttpRequest) -> Response { + /// Response::Ok() + /// .set_header("X-TEST", "value") + /// .set_header(http::header::CONTENT_TYPE, "application/json") + /// .finish() + /// } + /// fn main() {} + /// ``` + pub fn set_header(&mut self, key: K, value: V) -> &mut Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + if let Some(parts) = parts(&mut self.response, &self.err) { + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + parts.head.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + }, + Err(e) => self.err = Some(e.into()), + }; + } + self + } + /// Set the custom reason for the response. #[inline] pub fn reason(&mut self, reason: &'static str) -> &mut Self { @@ -367,11 +407,14 @@ impl ResponseBuilder { /// Set connection type to Upgrade #[inline] - pub fn upgrade(&mut self) -> &mut Self { + pub fn upgrade(&mut self, value: V) -> &mut Self + where + V: IntoHeaderValue, + { if let Some(parts) = parts(&mut self.response, &self.err) { parts.head.set_connection_type(ConnectionType::Upgrade); } - self + self.set_header(header::UPGRADE, value) } /// Force close connection, even if it is marked as keep-alive @@ -880,8 +923,14 @@ mod tests { #[test] fn test_upgrade() { - let resp = Response::build(StatusCode::OK).upgrade().finish(); - assert!(resp.upgrade()) + let resp = Response::build(StatusCode::OK) + .upgrade("websocket") + .finish(); + assert!(resp.upgrade()); + assert_eq!( + resp.headers().get(header::UPGRADE).unwrap(), + HeaderValue::from_static("websocket") + ); } #[test] diff --git a/src/test.rs b/src/test.rs index 9e14129b6..8f90246b4 100644 --- a/src/test.rs +++ b/src/test.rs @@ -443,7 +443,7 @@ where ) -> Result, ws::ClientError> { let url = self.url(path); self.rt - .block_on(ws::Client::default().call(ws::Connect::new(url))) + .block_on(lazy(|| ws::Client::default().call(ws::Connect::new(url)))) } /// Connect to a websocket server diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 5c86d8c49..f1c91714a 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -183,8 +183,7 @@ pub fn handshake_response(req: &Request) -> ResponseBuilder { }; Response::build(StatusCode::SWITCHING_PROTOCOLS) - .upgrade() - .header(header::UPGRADE, "websocket") + .upgrade("websocket") .header(header::TRANSFER_ENCODING, "chunked") .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) .take() diff --git a/tests/test_server.rs b/tests/test_server.rs index a01af4f0d..c6e03e285 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -89,7 +89,9 @@ fn test_content_length() { { for i in 0..4 { - let req = client::ClientRequest::get(srv.url("/")).finish().unwrap(); + let req = client::ClientRequest::get(srv.url(&format!("/{}", i))) + .finish() + .unwrap(); let response = srv.send_request(req).unwrap(); assert_eq!(response.headers().get(&header), None); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 21f635129..22ce3ca29 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -13,7 +13,7 @@ use actix_net::service::NewServiceExt; use actix_net::stream::TakeItem; use actix_web::ws as web_ws; use bytes::{Bytes, BytesMut}; -use futures::future::{ok, Either}; +use futures::future::{lazy, ok, Either}; use futures::{Future, Sink, Stream}; use actix_http::{h1, test, ws, ResponseError, SendResponse, ServiceConfig}; @@ -81,8 +81,9 @@ fn test_simple() { { let url = srv.url("/"); - let (reader, mut writer) = - srv.block_on(web_ws::Client::new(url).connect()).unwrap(); + let (reader, mut writer) = srv + .block_on(lazy(|| web_ws::Client::new(url).connect())) + .unwrap(); writer.text("text"); let (item, reader) = srv.block_on(reader.into_future()).unwrap(); From 6b60c9e2302abb1a40d06af271a08c7cf19a1cc8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 19 Nov 2018 16:11:58 -0800 Subject: [PATCH 082/208] add debug impl for H1ServiceResult --- src/h1/mod.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 395d66199..da80e55e9 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -1,4 +1,6 @@ //! HTTP/1 implementation +use std::fmt; + use actix_net::codec::Framed; use bytes::Bytes; @@ -23,6 +25,20 @@ pub enum H1ServiceResult { Unhandled(Request, Framed), } +impl fmt::Debug for H1ServiceResult { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + H1ServiceResult::Disconnected => write!(f, "H1ServiceResult::Disconnected"), + H1ServiceResult::Shutdown(ref v) => { + write!(f, "H1ServiceResult::Shutdown({:?})", v) + } + H1ServiceResult::Unhandled(ref req, _) => { + write!(f, "H1ServiceResult::Unhandled({:?})", req) + } + } + } +} + #[derive(Debug)] /// Codec message pub enum Message { From e1fc6dea844f0ffe47d12d2b724b93cb6c23479b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 19 Nov 2018 16:39:40 -0800 Subject: [PATCH 083/208] restore execute method --- src/test.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test.rs b/src/test.rs index 8f90246b4..84a959c41 100644 --- a/src/test.rs +++ b/src/test.rs @@ -375,6 +375,14 @@ impl TestServerRuntime { self.rt.block_on(fut) } + /// Execute future on current core + pub fn execute(&mut self, fut: F) -> Result + where + F: Future, + { + self.rt.block_on(fut) + } + /// Construct test server url pub fn addr(&self) -> net::SocketAddr { self.addr From 186d3d727a07bccb6423d36aa9475ece16ef7211 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 20 Nov 2018 10:55:50 -0800 Subject: [PATCH 084/208] add kee-alive tests --- src/h1/dispatcher.rs | 27 +++++---- tests/test_server.rs | 130 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 146 insertions(+), 11 deletions(-) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index bf0abb046..550d27c24 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -409,7 +409,7 @@ where if self.flags.contains(Flags::SHUTDOWN) { return Err(DispatchError::DisconnectTimeout); } else if self.ka_timer.as_mut().unwrap().deadline() >= self.ka_expire { - // check for any outstanding response processing + // check for any outstanding tasks if self.state.is_empty() && self.framed.is_write_buf_empty() { if self.flags.contains(Flags::STARTED) { trace!("Keep-alive timeout, close connection"); @@ -427,12 +427,16 @@ where } } else { // timeout on first request (slow request) return 408 - trace!("Slow request timeout"); - self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); - let _ = self.send_response( - Response::RequestTimeout().finish().drop_body(), - (), - ); + if !self.flags.contains(Flags::STARTED) { + trace!("Slow request timeout"); + let _ = self.send_response( + Response::RequestTimeout().finish().drop_body(), + (), + ); + } else { + trace!("Keep-alive connection timeout"); + } + self.flags.insert(Flags::STARTED | Flags::SHUTDOWN); self.state = State::None; } } else if let Some(deadline) = self.config.keep_alive_expire() { @@ -493,11 +497,14 @@ where false } // disconnect if keep-alive is not enabled - else if inner.flags.contains(Flags::STARTED) && !inner - .flags - .intersects(Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED) + else if inner.flags.contains(Flags::STARTED) + && !inner.flags.intersects(Flags::KEEPALIVE) { true + } + // disconnect if shutdown + else if inner.flags.contains(Flags::SHUTDOWN) { + true } else { return Ok(Async::NotReady); } diff --git a/tests/test_server.rs b/tests/test_server.rs index c6e03e285..16c65aac1 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -4,7 +4,9 @@ extern crate actix_net; extern crate bytes; extern crate futures; -use std::{io::Read, io::Write, net}; +use std::io::{Read, Write}; +use std::time::Duration; +use std::{net, thread}; use actix_net::service::NewServiceExt; use bytes::Bytes; @@ -62,6 +64,132 @@ fn test_malformed_request() { assert!(data.starts_with("HTTP/1.1 400 Bad Request")); } +#[test] +fn test_keepalive() { + let srv = test::TestServer::with_factory(|| { + h1::H1Service::build() + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); +} + +#[test] +fn test_keepalive_timeout() { + let srv = test::TestServer::with_factory(|| { + h1::H1Service::build() + .keep_alive(1) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + thread::sleep(Duration::from_millis(1100)); + + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); +} + +#[test] +fn test_keepalive_close() { + let srv = test::TestServer::with_factory(|| { + h1::H1Service::build() + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = + stream.write_all(b"GET /test/tests/test HTTP/1.1\r\nconnection: close\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); +} + +#[test] +fn test_keepalive_http10_default_close() { + let srv = test::TestServer::with_factory(|| { + h1::H1Service::build() + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); +} + +#[test] +fn test_keepalive_http10() { + let srv = test::TestServer::with_factory(|| { + h1::H1Service::build() + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream + .write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); +} + +#[test] +fn test_keepalive_disabled() { + let srv = test::TestServer::with_factory(|| { + h1::H1Service::build() + .keep_alive(KeepAlive::Disabled) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); +} + #[test] fn test_content_length() { use actix_http::http::{ From ab3e12f2b4a8a177421120bca0239a9e4cf3d874 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 20 Nov 2018 11:23:05 -0800 Subject: [PATCH 085/208] set server response version --- src/h1/codec.rs | 3 +++ tests/test_server.rs | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index f9f455e5d..3174e78ea 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -149,6 +149,9 @@ impl Encoder for Codec { ) -> Result<(), Self::Error> { match item { Message::Item((mut res, length)) => { + // set response version + res.head_mut().version = self.version; + // connection status self.ctype = if let Some(ct) = res.head().ctype { if ct == ConnectionType::KeepAlive { diff --git a/tests/test_server.rs b/tests/test_server.rs index 16c65aac1..a153e584d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -137,7 +137,7 @@ fn test_keepalive_http10_default_close() { let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); let mut data = vec![0; 1024]; let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); let mut data = vec![0; 1024]; let res = stream.read(&mut data).unwrap(); @@ -157,13 +157,13 @@ fn test_keepalive_http10() { .write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n"); let mut data = vec![0; 1024]; let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); let mut data = vec![0; 1024]; let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); let mut data = vec![0; 1024]; let res = stream.read(&mut data).unwrap(); From 1a322966ff2b25af1213459bd6d62fb9d4b6fcbd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 21 Nov 2018 07:49:24 -0800 Subject: [PATCH 086/208] handle response errors --- src/body.rs | 40 ++++++++++++++++++++++ src/h1/dispatcher.rs | 14 ++++---- src/response.rs | 81 ++++++++++++++++++++++---------------------- src/service.rs | 4 +-- tests/test_server.rs | 22 ++++++++++++ 5 files changed, 111 insertions(+), 50 deletions(-) diff --git a/src/body.rs b/src/body.rs index 3449448e3..cc4e77af5 100644 --- a/src/body.rs +++ b/src/body.rs @@ -37,6 +37,37 @@ impl MessageBody for () { } } +pub enum ResponseBody { + Body(B), + Other(Body), +} + +impl ResponseBody { + pub fn as_ref(&self) -> Option<&B> { + if let ResponseBody::Body(ref b) = self { + Some(b) + } else { + None + } + } +} + +impl MessageBody for ResponseBody { + fn length(&self) -> BodyLength { + match self { + ResponseBody::Body(ref body) => body.length(), + ResponseBody::Other(ref body) => body.length(), + } + } + + fn poll_next(&mut self) -> Poll, Error> { + match self { + ResponseBody::Body(ref mut body) => body.poll_next(), + ResponseBody::Other(ref mut body) => body.poll_next(), + } + } +} + /// Represents various types of http message body. pub enum Body { /// Empty response. `Content-Length` header is not set. @@ -332,6 +363,15 @@ mod tests { } } + impl ResponseBody { + pub(crate) fn get_ref(&self) -> &[u8] { + match *self { + ResponseBody::Body(ref b) => b.get_ref(), + ResponseBody::Other(ref b) => b.get_ref(), + } + } + } + #[test] fn test_static_str() { assert_eq!(Body::from("").length(), BodyLength::Sized(0)); diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 550d27c24..48c8e710b 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -13,7 +13,7 @@ use tokio_timer::Delay; use error::{ParseError, PayloadError}; use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; -use body::{BodyLength, MessageBody}; +use body::{Body, BodyLength, MessageBody, ResponseBody}; use config::ServiceConfig; use error::DispatchError; use request::Request; @@ -70,7 +70,7 @@ enum DispatcherMessage { enum State { None, ServiceCall(S::Future), - SendPayload(B), + SendPayload(ResponseBody), } impl State { @@ -186,11 +186,11 @@ where } } - fn send_response( + fn send_response( &mut self, message: Response<()>, - body: B1, - ) -> Result, DispatchError> { + body: ResponseBody, + ) -> Result, DispatchError> { self.framed .force_send(Message::Item((message, body.length()))) .map_err(|err| { @@ -217,7 +217,7 @@ where Some(self.handle_request(req)?) } Some(DispatcherMessage::Error(res)) => { - self.send_response(res, ())?; + self.send_response(res, ResponseBody::Other(Body::Empty))?; None } None => None, @@ -431,7 +431,7 @@ where trace!("Slow request timeout"); let _ = self.send_response( Response::RequestTimeout().finish().drop_body(), - (), + ResponseBody::Other(Body::Empty), ); } else { trace!("Keep-alive connection timeout"); diff --git a/src/response.rs b/src/response.rs index bc730718d..ae68189d8 100644 --- a/src/response.rs +++ b/src/response.rs @@ -12,7 +12,7 @@ use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version}; use serde::Serialize; use serde_json; -use body::{Body, BodyStream, MessageBody}; +use body::{Body, BodyStream, MessageBody, ResponseBody}; use error::Error; use header::{Header, IntoHeaderValue}; use message::{ConnectionType, Head, ResponseHead}; @@ -21,7 +21,7 @@ use message::{ConnectionType, Head, ResponseHead}; pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; /// An HTTP Response -pub struct Response(Box, B); +pub struct Response(Box, ResponseBody); impl Response { /// Create http response builder with specific status. @@ -71,6 +71,15 @@ impl Response { cookies: jar, } } + + /// Convert response to response with body + pub fn into_body(self) -> Response { + let b = match self.1 { + ResponseBody::Body(b) => b, + ResponseBody::Other(b) => b, + }; + Response(self.0, ResponseBody::Other(b)) + } } impl Response { @@ -195,23 +204,26 @@ impl Response { /// Get body os this response #[inline] - pub fn body(&self) -> &B { + pub(crate) fn body(&self) -> &ResponseBody { &self.1 } /// Set a body - pub fn set_body(self, body: B2) -> Response { - Response(self.0, body) + pub(crate) fn set_body(self, body: B2) -> Response { + Response(self.0, ResponseBody::Body(body)) } /// Drop request's body - pub fn drop_body(self) -> Response<()> { - Response(self.0, ()) + pub(crate) fn drop_body(self) -> Response<()> { + Response(self.0, ResponseBody::Body(())) } /// Set a body and return previous body value - pub fn replace_body(self, body: B2) -> (Response, B) { - (Response(self.0, body), self.1) + pub(crate) fn replace_body( + self, + body: B2, + ) -> (Response, ResponseBody) { + (Response(self.0, ResponseBody::Body(body)), self.1) } /// Size of response in bytes, excluding HTTP headers @@ -233,7 +245,10 @@ impl Response { } pub(crate) fn from_parts(parts: ResponseParts) -> Response { - Response(Box::new(InnerResponse::from_parts(parts)), Body::Empty) + Response( + Box::new(InnerResponse::from_parts(parts)), + ResponseBody::Body(Body::Empty), + ) } } @@ -250,7 +265,7 @@ impl fmt::Debug for Response { for (key, val) in self.get_ref().head.headers.iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); } - let _ = writeln!(f, " body: {:?}", self.body().length()); + let _ = writeln!(f, " body: {:?}", self.1.length()); res } } @@ -559,11 +574,9 @@ impl ResponseBuilder { /// /// `ResponseBuilder` can not be used after this call. pub fn message_body(&mut self, body: B) -> Response { - let mut error = if let Some(e) = self.err.take() { - Some(Error::from(e)) - } else { - None - }; + if let Some(e) = self.err.take() { + return Response::from(Error::from(e)).into_body(); + } let mut response = self.response.take().expect("cannot reuse response builder"); if let Some(ref jar) = self.cookies { @@ -572,17 +585,12 @@ impl ResponseBuilder { Ok(val) => { let _ = response.head.headers.append(header::SET_COOKIE, val); } - Err(e) => if error.is_none() { - error = Some(Error::from(e)); - }, + Err(e) => return Response::from(Error::from(e)).into_body(), }; } } - if let Some(error) = error { - response.error = Some(error); - } - Response(response, body) + Response(response, ResponseBody::Body(body)) } #[inline] @@ -812,9 +820,12 @@ impl ResponsePool { ) -> Response { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { msg.head.status = status; - Response(msg, body) + Response(msg, ResponseBody::Body(body)) } else { - Response(Box::new(InnerResponse::new(status, pool)), body) + Response( + Box::new(InnerResponse::new(status, pool)), + ResponseBody::Body(body), + ) } } @@ -971,10 +982,7 @@ mod tests { let resp = Response::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!( - *resp.body(), - Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")) - ); + assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); } #[test] @@ -984,10 +992,7 @@ mod tests { .json(vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("text/json")); - assert_eq!( - *resp.body(), - Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")) - ); + assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); } #[test] @@ -995,10 +1000,7 @@ mod tests { let resp = Response::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!( - *resp.body(), - Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")) - ); + assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); } #[test] @@ -1008,10 +1010,7 @@ mod tests { .json2(&vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("text/json")); - assert_eq!( - *resp.body(), - Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")) - ); + assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); } #[test] diff --git a/src/service.rs b/src/service.rs index f934305ce..6a31b6bb8 100644 --- a/src/service.rs +++ b/src/service.rs @@ -6,7 +6,7 @@ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll, Sink}; use tokio_io::{AsyncRead, AsyncWrite}; -use body::{BodyLength, MessageBody}; +use body::{BodyLength, MessageBody, ResponseBody}; use error::{Error, ResponseError}; use h1::{Codec, Message}; use response::Response; @@ -174,7 +174,7 @@ where pub struct SendResponseFut { res: Option, BodyLength)>>, - body: Option, + body: Option>, framed: Option>, } diff --git a/tests/test_server.rs b/tests/test_server.rs index a153e584d..300b38a80 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -445,3 +445,25 @@ fn test_body_chunked_implicit() { let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } + +#[test] +fn test_response_http_error_handling() { + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| { + let broken_header = Bytes::from_static(b"\0\0\0"); + ok::<_, ()>( + Response::Ok() + .header(http::header::CONTENT_TYPE, broken_header) + .body(STR), + ) + }).map(|_| ()) + }); + + let req = srv.get().finish().unwrap(); + let response = srv.send_request(req).unwrap(); + assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert!(bytes.is_empty()); +} From 41d68c87d926899cdce05ae3bf9c89c23fa1ac50 Mon Sep 17 00:00:00 2001 From: Ali Shirvani Date: Fri, 23 Nov 2018 07:42:40 +0330 Subject: [PATCH 087/208] hello-world example added. --- examples/hello-world.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 examples/hello-world.rs diff --git a/examples/hello-world.rs b/examples/hello-world.rs new file mode 100644 index 000000000..44e453df4 --- /dev/null +++ b/examples/hello-world.rs @@ -0,0 +1,35 @@ +#[macro_use] +extern crate log; +extern crate env_logger; + +extern crate actix_http; +extern crate actix_net; +extern crate futures; +extern crate http; + +use actix_http::{h1, Response}; +use actix_net::server::Server; +use actix_net::service::NewServiceExt; +use futures::future; +use http::header::{HeaderValue}; +use std::env; + +fn main() { + env::set_var("RUST_LOG", "hello_world=info"); + env_logger::init(); + + Server::new().bind("hello-world", "127.0.0.1:8080", || { + h1::H1Service::build() + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .finish(|_req| { + info!("{:?}", _req); + let mut res = Response::Ok(); + res.header("x-head", HeaderValue::from_static("dummy value!")); + future::ok::<_, ()>(res.body("Hello world!")) + }) + .map(|_| ()) + }).unwrap().run(); +} + From d5ca6e21e2daef2637b0be028990e7264055436c Mon Sep 17 00:00:00 2001 From: Ali Shirvani Date: Sat, 24 Nov 2018 11:29:14 +0330 Subject: [PATCH 088/208] simple echo server. --- examples/echo.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 examples/echo.rs diff --git a/examples/echo.rs b/examples/echo.rs new file mode 100644 index 000000000..91a3c76ad --- /dev/null +++ b/examples/echo.rs @@ -0,0 +1,42 @@ +#[macro_use] +extern crate log; +extern crate env_logger; + +extern crate actix_http; +extern crate actix_net; +extern crate futures; +extern crate http; +extern crate bytes; + +use actix_http::{h1, Response, Request}; +use bytes::Bytes; +use actix_net::server::Server; +use actix_net::service::NewServiceExt; +use futures::Future; +use http::header::{HeaderValue}; +use actix_http::HttpMessage; +use std::env; + +fn main() { + env::set_var("RUST_LOG", "echo=info"); + env_logger::init(); + + Server::new().bind("echo", "127.0.0.1:8080", || { + h1::H1Service::build() + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .finish(|_req: Request| { + _req.body() + .limit(512) + .and_then(|bytes: Bytes| { + info!("request body: {:?}", bytes); + let mut res = Response::Ok(); + res.header("x-head", HeaderValue::from_static("dummy value!")); + Ok(res.body(bytes)) + }) + }) + .map(|_| ()) + }).unwrap().run(); +} + From c3c2286e3ac3c1c9ac6914cc430a9a20dd899721 Mon Sep 17 00:00:00 2001 From: Ali Shirvani Date: Sat, 24 Nov 2018 17:07:30 +0330 Subject: [PATCH 089/208] An other hello word example and update sample in README.md --- README.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3cb6f2308..e7205893d 100644 --- a/README.md +++ b/README.md @@ -14,20 +14,23 @@ Actix http ## Example ```rust +// see examples/framed_hello.rs for complete list of used crates. extern crate actix_http; use actix_http::{h1, Response, ServiceConfig}; fn main() { - Server::new() - .bind("app", addr, move || { - IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) // <- create h1 codec - .and_then(TakeItem::new().map_err(|_| ())) // <- read one request - .and_then(|(req, framed): (_, Framed<_, _>)| { // <- send response and close conn - framed - .send(h1::OutMessage::Response(Response::Ok().finish())) - }) - }) - .run(); + env::set_var("RUST_LOG", "framed_hello=info"); + env_logger::init(); + + Server::new().bind("framed_hello", "127.0.0.1:8080", || { + IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) // <- create h1 codec + .and_then(TakeItem::new().map_err(|_| ())) // <- read one request + .and_then(|(_req, _framed): (_, Framed<_, _>)| { // <- send response and close conn + SendResponse::send(_framed, Response::Ok().body("Hello world!")) + .map_err(|_| ()) + .map(|_| ()) + }) + }).unwrap().run(); } ``` From d5b264034299bcaa75f3bd21b4d36dba574c2295 Mon Sep 17 00:00:00 2001 From: Ali Shirvani Date: Sat, 24 Nov 2018 17:08:17 +0330 Subject: [PATCH 090/208] add framed_hello.rs --- examples/framed_hello.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 examples/framed_hello.rs diff --git a/examples/framed_hello.rs b/examples/framed_hello.rs new file mode 100644 index 000000000..76d23d08c --- /dev/null +++ b/examples/framed_hello.rs @@ -0,0 +1,33 @@ +extern crate log; +extern crate env_logger; + +extern crate actix_http; +extern crate actix_net; +extern crate futures; +extern crate http; +extern crate bytes; + +use actix_http::{h1, ServiceConfig, SendResponse, Response}; +use actix_net::framed::IntoFramed; +use actix_net::codec::Framed; +use actix_net::stream::TakeItem; +use actix_net::server::Server; +use actix_net::service::NewServiceExt; +use futures::Future; +use std::env; + +fn main() { + env::set_var("RUST_LOG", "framed_hello=info"); + env_logger::init(); + + Server::new().bind("framed_hello", "127.0.0.1:8080", || { + IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) + .and_then(TakeItem::new().map_err(|_| ())) + .and_then(|(_req, _framed): (_, Framed<_, _>)| { + SendResponse::send(_framed, Response::Ok().body("Hello world!")) + .map_err(|_| ()) + .map(|_| ()) + }) + }).unwrap().run(); +} + From 7a97de3a1e222fa15e8495315dcbcedea11bdfd8 Mon Sep 17 00:00:00 2001 From: Ali Shirvani Date: Sat, 24 Nov 2018 17:17:34 +0330 Subject: [PATCH 091/208] update readme. --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e7205893d..093a0b949 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,10 @@ extern crate actix_http; use actix_http::{h1, Response, ServiceConfig}; fn main() { - env::set_var("RUST_LOG", "framed_hello=info"); - env_logger::init(); - Server::new().bind("framed_hello", "127.0.0.1:8080", || { IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) // <- create h1 codec - .and_then(TakeItem::new().map_err(|_| ())) // <- read one request - .and_then(|(_req, _framed): (_, Framed<_, _>)| { // <- send response and close conn + .and_then(TakeItem::new().map_err(|_| ())) // <- read one request + .and_then(|(_req, _framed): (_, Framed<_, _>)| { // <- send response and close conn SendResponse::send(_framed, Response::Ok().body("Hello world!")) .map_err(|_| ()) .map(|_| ()) From ca1b460924219f0cc2e10bf500094238e953c45e Mon Sep 17 00:00:00 2001 From: Ali Shirvani Date: Sun, 25 Nov 2018 05:48:33 +0330 Subject: [PATCH 092/208] comments aligned. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 093a0b949..be8160968 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ use actix_http::{h1, Response, ServiceConfig}; fn main() { Server::new().bind("framed_hello", "127.0.0.1:8080", || { IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) // <- create h1 codec - .and_then(TakeItem::new().map_err(|_| ())) // <- read one request - .and_then(|(_req, _framed): (_, Framed<_, _>)| { // <- send response and close conn + .and_then(TakeItem::new().map_err(|_| ())) // <- read one request + .and_then(|(_req, _framed): (_, Framed<_, _>)| { // <- send response and close conn SendResponse::send(_framed, Response::Ok().body("Hello world!")) .map_err(|_| ()) .map(|_| ()) From 9c038ee1891afe204b06c2e22dcdc947f6d3469b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 25 Nov 2018 20:14:42 -1000 Subject: [PATCH 093/208] allow to use Uri for client request --- src/client/connector.rs | 6 ++++++ src/client/mod.rs | 2 +- src/client/request.rs | 40 +++++++++++++++++++++++++--------------- src/lib.rs | 3 +++ 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 818085214..42cba9dec 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -62,6 +62,12 @@ impl Default for Connector { } impl Connector { + /// Use custom resolver. + pub fn resolver(mut self, resolver: Resolver) -> Self { + self.resolver = resolver;; + self + } + /// Use custom resolver configuration. pub fn resolver_config(mut self, cfg: ResolverConfig, opts: ResolverOpts) -> Self { self.resolver = Resolver::new(cfg, opts); diff --git a/src/client/mod.rs b/src/client/mod.rs index 76c3f8b88..dcc4f5d48 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -13,4 +13,4 @@ pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectorError, InvalidUrlKind, SendRequestError}; pub use self::request::{ClientRequest, ClientRequestBuilder}; -pub use self::response::ClientResponse; +pub use self::response::ClientResponse; \ No newline at end of file diff --git a/src/client/request.rs b/src/client/request.rs index 735ce4937..dd29d7978 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -7,7 +7,6 @@ use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; use futures::{Future, Stream}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; -use urlcrate::Url; use body::{BodyStream, MessageBody}; use error::Error; @@ -63,35 +62,50 @@ impl ClientRequest<()> { } /// Create request builder for `GET` request - pub fn get>(uri: U) -> ClientRequestBuilder { + pub fn get(uri: U) -> ClientRequestBuilder + where + Uri: HttpTryFrom, + { let mut builder = ClientRequest::build(); builder.method(Method::GET).uri(uri); builder } /// Create request builder for `HEAD` request - pub fn head>(uri: U) -> ClientRequestBuilder { + pub fn head(uri: U) -> ClientRequestBuilder + where + Uri: HttpTryFrom, + { let mut builder = ClientRequest::build(); builder.method(Method::HEAD).uri(uri); builder } /// Create request builder for `POST` request - pub fn post>(uri: U) -> ClientRequestBuilder { + pub fn post(uri: U) -> ClientRequestBuilder + where + Uri: HttpTryFrom, + { let mut builder = ClientRequest::build(); builder.method(Method::POST).uri(uri); builder } /// Create request builder for `PUT` request - pub fn put>(uri: U) -> ClientRequestBuilder { + pub fn put(uri: U) -> ClientRequestBuilder + where + Uri: HttpTryFrom, + { let mut builder = ClientRequest::build(); builder.method(Method::PUT).uri(uri); builder } /// Create request builder for `DELETE` request - pub fn delete>(uri: U) -> ClientRequestBuilder { + pub fn delete(uri: U) -> ClientRequestBuilder + where + Uri: HttpTryFrom, + { let mut builder = ClientRequest::build(); builder.method(Method::DELETE).uri(uri); builder @@ -202,15 +216,11 @@ pub struct ClientRequestBuilder { impl ClientRequestBuilder { /// Set HTTP URI of request. #[inline] - pub fn uri>(&mut self, uri: U) -> &mut Self { - match Url::parse(uri.as_ref()) { - Ok(url) => self._uri(url.as_str()), - Err(_) => self._uri(uri.as_ref()), - } - } - - fn _uri(&mut self, url: &str) -> &mut Self { - match Uri::try_from(url) { + pub fn uri(&mut self, uri: U) -> &mut Self + where + Uri: HttpTryFrom, + { + match Uri::try_from(uri) { Ok(uri) => { if let Some(parts) = parts(&mut self.head, &self.err) { parts.uri = uri; diff --git a/src/lib.rs b/src/lib.rs index 5256dd190..4870eb64f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -163,6 +163,9 @@ pub mod http { #[doc(hidden)] pub use modhttp::{uri, Error, HeaderMap, HttpTryFrom, Uri}; + #[doc(hidden)] + pub use modhttp::uri::PathAndQuery; + pub use cookie::{Cookie, CookieBuilder}; /// Various http headers From 397804a786d0a4e8167706b46a4bb08808bd17cd Mon Sep 17 00:00:00 2001 From: Ali Shirvani Date: Wed, 28 Nov 2018 09:15:08 +0330 Subject: [PATCH 094/208] echo example with `impl Future` --- examples/echo2.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 examples/echo2.rs diff --git a/examples/echo2.rs b/examples/echo2.rs new file mode 100644 index 000000000..7d8a428f9 --- /dev/null +++ b/examples/echo2.rs @@ -0,0 +1,47 @@ +#[macro_use] +extern crate log; +extern crate env_logger; + +extern crate actix_http; +extern crate actix_net; +extern crate futures; +extern crate http; +extern crate bytes; + +use actix_http::{h1, Response, Request, Error}; +use bytes::Bytes; +use actix_net::server::Server; +use actix_net::service::NewServiceExt; +use futures::Future; +use http::header::{HeaderValue}; +use actix_http::HttpMessage; +use std::env; + +fn handle_request(_req: Request) -> impl Future{ + _req.body() + .limit(512) + .from_err() + .and_then(|bytes: Bytes| { + info!("request body: {:?}", bytes); + let mut res = Response::Ok(); + res.header("x-head", HeaderValue::from_static("dummy value!")); + Ok(res.body(bytes)) + }) +} + +fn main() { + env::set_var("RUST_LOG", "echo=info"); + env_logger::init(); + + Server::new().bind("echo", "127.0.0.1:8080", || { + h1::H1Service::build() + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .finish(|_req: Request| { + handle_request(_req) + }) + .map(|_| ()) + }).unwrap().run(); +} + From 4028f6f6fd63cad32636d5f1b461f372302c9af4 Mon Sep 17 00:00:00 2001 From: Ali Shirvani Date: Wed, 28 Nov 2018 09:42:04 +0330 Subject: [PATCH 095/208] http crate removed, cargo fmt --- examples/echo.rs | 34 +++++++++++++--------------- examples/echo2.rs | 49 ++++++++++++++++++---------------------- examples/framed_hello.rs | 31 +++++++++++++------------ examples/hello-world.rs | 30 ++++++++++++------------ src/client/mod.rs | 2 +- 5 files changed, 70 insertions(+), 76 deletions(-) diff --git a/examples/echo.rs b/examples/echo.rs index 91a3c76ad..c98863e52 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -4,39 +4,37 @@ extern crate env_logger; extern crate actix_http; extern crate actix_net; +extern crate bytes; extern crate futures; extern crate http; -extern crate bytes; -use actix_http::{h1, Response, Request}; -use bytes::Bytes; +use actix_http::HttpMessage; +use actix_http::{h1, Request, Response}; use actix_net::server::Server; use actix_net::service::NewServiceExt; +use bytes::Bytes; use futures::Future; -use http::header::{HeaderValue}; -use actix_http::HttpMessage; +use http::header::HeaderValue; use std::env; fn main() { env::set_var("RUST_LOG", "echo=info"); env_logger::init(); - Server::new().bind("echo", "127.0.0.1:8080", || { - h1::H1Service::build() - .client_timeout(1000) - .client_disconnect(1000) - .server_hostname("localhost") - .finish(|_req: Request| { - _req.body() - .limit(512) - .and_then(|bytes: Bytes| { + Server::new() + .bind("echo", "127.0.0.1:8080", || { + h1::H1Service::build() + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .finish(|_req: Request| { + _req.body().limit(512).and_then(|bytes: Bytes| { info!("request body: {:?}", bytes); let mut res = Response::Ok(); res.header("x-head", HeaderValue::from_static("dummy value!")); Ok(res.body(bytes)) }) - }) - .map(|_| ()) - }).unwrap().run(); + }).map(|_| ()) + }).unwrap() + .run(); } - diff --git a/examples/echo2.rs b/examples/echo2.rs index 7d8a428f9..4c144b433 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -4,44 +4,39 @@ extern crate env_logger; extern crate actix_http; extern crate actix_net; -extern crate futures; -extern crate http; extern crate bytes; +extern crate futures; -use actix_http::{h1, Response, Request, Error}; -use bytes::Bytes; +use actix_http::http::HeaderValue; +use actix_http::HttpMessage; +use actix_http::{h1, Error, Request, Response}; use actix_net::server::Server; use actix_net::service::NewServiceExt; +use bytes::Bytes; use futures::Future; -use http::header::{HeaderValue}; -use actix_http::HttpMessage; use std::env; -fn handle_request(_req: Request) -> impl Future{ - _req.body() - .limit(512) - .from_err() - .and_then(|bytes: Bytes| { - info!("request body: {:?}", bytes); - let mut res = Response::Ok(); - res.header("x-head", HeaderValue::from_static("dummy value!")); - Ok(res.body(bytes)) - }) +fn handle_request(_req: Request) -> impl Future { + _req.body().limit(512).from_err().and_then(|bytes: Bytes| { + info!("request body: {:?}", bytes); + let mut res = Response::Ok(); + res.header("x-head", HeaderValue::from_static("dummy value!")); + Ok(res.body(bytes)) + }) } fn main() { env::set_var("RUST_LOG", "echo=info"); env_logger::init(); - Server::new().bind("echo", "127.0.0.1:8080", || { - h1::H1Service::build() - .client_timeout(1000) - .client_disconnect(1000) - .server_hostname("localhost") - .finish(|_req: Request| { - handle_request(_req) - }) - .map(|_| ()) - }).unwrap().run(); + Server::new() + .bind("echo", "127.0.0.1:8080", || { + h1::H1Service::build() + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .finish(|_req: Request| handle_request(_req)) + .map(|_| ()) + }).unwrap() + .run(); } - diff --git a/examples/framed_hello.rs b/examples/framed_hello.rs index 76d23d08c..0c9175a9e 100644 --- a/examples/framed_hello.rs +++ b/examples/framed_hello.rs @@ -1,18 +1,18 @@ -extern crate log; extern crate env_logger; +extern crate log; extern crate actix_http; extern crate actix_net; +extern crate bytes; extern crate futures; extern crate http; -extern crate bytes; -use actix_http::{h1, ServiceConfig, SendResponse, Response}; -use actix_net::framed::IntoFramed; +use actix_http::{h1, Response, SendResponse, ServiceConfig}; use actix_net::codec::Framed; -use actix_net::stream::TakeItem; +use actix_net::framed::IntoFramed; use actix_net::server::Server; use actix_net::service::NewServiceExt; +use actix_net::stream::TakeItem; use futures::Future; use std::env; @@ -20,14 +20,15 @@ fn main() { env::set_var("RUST_LOG", "framed_hello=info"); env_logger::init(); - Server::new().bind("framed_hello", "127.0.0.1:8080", || { - IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) - .and_then(TakeItem::new().map_err(|_| ())) - .and_then(|(_req, _framed): (_, Framed<_, _>)| { - SendResponse::send(_framed, Response::Ok().body("Hello world!")) - .map_err(|_| ()) - .map(|_| ()) - }) - }).unwrap().run(); + Server::new() + .bind("framed_hello", "127.0.0.1:8080", || { + IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) + .and_then(TakeItem::new().map_err(|_| ())) + .and_then(|(_req, _framed): (_, Framed<_, _>)| { + SendResponse::send(_framed, Response::Ok().body("Hello world!")) + .map_err(|_| ()) + .map(|_| ()) + }) + }).unwrap() + .run(); } - diff --git a/examples/hello-world.rs b/examples/hello-world.rs index 44e453df4..74ff509b3 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -11,25 +11,25 @@ use actix_http::{h1, Response}; use actix_net::server::Server; use actix_net::service::NewServiceExt; use futures::future; -use http::header::{HeaderValue}; +use http::header::HeaderValue; use std::env; fn main() { env::set_var("RUST_LOG", "hello_world=info"); env_logger::init(); - Server::new().bind("hello-world", "127.0.0.1:8080", || { - h1::H1Service::build() - .client_timeout(1000) - .client_disconnect(1000) - .server_hostname("localhost") - .finish(|_req| { - info!("{:?}", _req); - let mut res = Response::Ok(); - res.header("x-head", HeaderValue::from_static("dummy value!")); - future::ok::<_, ()>(res.body("Hello world!")) - }) - .map(|_| ()) - }).unwrap().run(); + Server::new() + .bind("hello-world", "127.0.0.1:8080", || { + h1::H1Service::build() + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .finish(|_req| { + info!("{:?}", _req); + let mut res = Response::Ok(); + res.header("x-head", HeaderValue::from_static("dummy value!")); + future::ok::<_, ()>(res.body("Hello world!")) + }).map(|_| ()) + }).unwrap() + .run(); } - diff --git a/src/client/mod.rs b/src/client/mod.rs index dcc4f5d48..76c3f8b88 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -13,4 +13,4 @@ pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectorError, InvalidUrlKind, SendRequestError}; pub use self::request::{ClientRequest, ClientRequestBuilder}; -pub use self::response::ClientResponse; \ No newline at end of file +pub use self::response::ClientResponse; From 06387fc778f8d941921127a72f29d9777566b804 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 28 Nov 2018 09:02:31 -1000 Subject: [PATCH 096/208] display parse error for ws client errors --- src/ws/client/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ws/client/error.rs b/src/ws/client/error.rs index 951cd6ac4..729a00ce5 100644 --- a/src/ws/client/error.rs +++ b/src/ws/client/error.rs @@ -37,7 +37,7 @@ pub enum ClientError { #[fail(display = "Http parsing error")] Http(HttpError), /// Response parsing error - #[fail(display = "Response parsing error")] + #[fail(display = "Response parsing error: {}", _0)] ParseError(ParseError), /// Protocol error #[fail(display = "{}", _0)] From d269904fbffe4b24a35508d44796b7e3373f9e36 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 28 Nov 2018 09:10:13 -1000 Subject: [PATCH 097/208] add cause for nested errors --- src/ws/client/error.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ws/client/error.rs b/src/ws/client/error.rs index 729a00ce5..589648eea 100644 --- a/src/ws/client/error.rs +++ b/src/ws/client/error.rs @@ -35,19 +35,19 @@ pub enum ClientError { InvalidChallengeResponse(String, HeaderValue), /// Http parsing error #[fail(display = "Http parsing error")] - Http(HttpError), + Http(#[cause] HttpError), /// Response parsing error #[fail(display = "Response parsing error: {}", _0)] - ParseError(ParseError), + ParseError(#[cause] ParseError), /// Protocol error #[fail(display = "{}", _0)] Protocol(#[cause] ProtocolError), /// Connect error - #[fail(display = "{:?}", _0)] + #[fail(display = "Connector error: {:?}", _0)] Connect(ConnectorError), /// IO Error #[fail(display = "{}", _0)] - Io(io::Error), + Io(#[cause] io::Error), /// "Disconnected" #[fail(display = "Disconnected")] Disconnected, From 5003c00efbdeb8ef76b8137a3d604d7ba4c16d4d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 30 Nov 2018 11:57:57 -0800 Subject: [PATCH 098/208] use new Service and NewService traits --- Cargo.toml | 4 +-- rustfmt.toml | 2 +- src/client/connector.rs | 56 ++++++++++++---------------------------- src/client/pipeline.rs | 2 +- src/client/pool.rs | 11 ++++---- src/client/request.rs | 2 +- src/h1/dispatcher.rs | 14 +++++----- src/h1/service.rs | 34 +++++++++++------------- src/service.rs | 16 +++++------- src/test.rs | 9 +++---- src/ws/client/service.rs | 13 +++++----- src/ws/service.rs | 8 +++--- src/ws/transport.rs | 10 +++---- 13 files changed, 73 insertions(+), 108 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b2c7c0848..b1dd69ac1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,8 +45,8 @@ rust-tls = ["rustls", "actix-net/rust-tls"] [dependencies] actix = "0.7.5" -actix-net = "0.2.3" -#actix-net = { git="https://github.com/actix/actix-net.git" } +#actix-net = "0.3.0" +actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" diff --git a/rustfmt.toml b/rustfmt.toml index 4fff285e7..5fcaaca0f 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,5 @@ max_width = 89 reorder_imports = true #wrap_comments = true -fn_args_density = "Compressed" +#fn_args_density = "Compressed" #use_small_heuristics = false diff --git a/src/client/connector.rs b/src/client/connector.rs index 42cba9dec..3729ce394 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -134,11 +134,8 @@ impl Connector { /// Finish configuration process and create connector service. pub fn service( self, - ) -> impl Service< - Request = Connect, - Response = impl Connection, - Error = ConnectorError, - > + Clone { + ) -> impl Service + Clone + { #[cfg(not(feature = "ssl"))] { let connector = TimeoutService::new( @@ -216,7 +213,7 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) tcp_pool: ConnectionPool, } @@ -224,8 +221,7 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service - + Clone, + T: Service + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -234,16 +230,15 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { - type Request = Connect; type Response = IoConnection; type Error = ConnectorError; type Future = Either< - as Service>::Future, + as Service>::Future, FutureResult, ConnectorError>, >; @@ -251,7 +246,7 @@ mod connect_impl { self.tcp_pool.poll_ready() } - fn call(&mut self, req: Self::Request) -> Self::Future { + fn call(&mut self, req: Connect) -> Self::Future { if req.is_secure() { Either::B(err(ConnectorError::SslIsNotSupported)) } else if let Err(e) = req.validate() { @@ -295,16 +290,8 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service< - Request = Connect, - Response = (Connect, Io1), - Error = ConnectorError, - > + Clone, - T2: Service< - Request = Connect, - Response = (Connect, Io2), - Error = ConnectorError, - > + Clone, + T1: Service + Clone, + T2: Service + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -318,18 +305,9 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service< - Request = Connect, - Response = (Connect, Io1), - Error = ConnectorError, - >, - T2: Service< - Request = Connect, - Response = (Connect, Io2), - Error = ConnectorError, - >, + T1: Service, + T2: Service, { - type Request = Connect; type Response = IoEither, IoConnection>; type Error = ConnectorError; type Future = Either< @@ -344,7 +322,7 @@ mod connect_impl { self.tcp_pool.poll_ready() } - fn call(&mut self, req: Self::Request) -> Self::Future { + fn call(&mut self, req: Connect) -> Self::Future { if let Err(e) = req.validate() { Either::A(err(e)) } else if req.is_secure() { @@ -364,7 +342,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -372,7 +350,7 @@ mod connect_impl { impl Future for InnerConnectorResponseA where - T: Service, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { @@ -390,7 +368,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -398,7 +376,7 @@ mod connect_impl { impl Future for InnerConnectorResponseB where - T: Service, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index e1d8421e9..e75265507 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -21,7 +21,7 @@ pub(crate) fn send_request( connector: &mut T, ) -> impl Future where - T: Service, + T: Service, B: MessageBody, I: Connection, { diff --git a/src/client/pool.rs b/src/client/pool.rs index 44008f346..decf80194 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -47,7 +47,7 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) fn new( connector: T, @@ -83,12 +83,11 @@ where } } -impl Service for ConnectionPool +impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { - type Request = Connect; type Response = IoConnection; type Error = ConnectorError; type Future = Either< @@ -100,7 +99,7 @@ where self.0.poll_ready() } - fn call(&mut self, req: Self::Request) -> Self::Future { + fn call(&mut self, req: Connect) -> Self::Future { let key = req.key(); // acquire connection @@ -456,7 +455,7 @@ where impl Future for ConnectorPoolSupport where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, T::Future: 'static, { type Item = (); diff --git a/src/client/request.rs b/src/client/request.rs index dd29d7978..e71c3ffd8 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -177,7 +177,7 @@ where connector: &mut T, ) -> impl Future where - T: Service, + T: Service, I: Connection, { pipeline::send_request(self.head, self.body, connector) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 48c8e710b..5e2742aa5 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -36,14 +36,14 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher +pub struct Dispatcher, B: MessageBody> where S::Error: Debug, { inner: Option>, } -struct InnerDispatcher +struct InnerDispatcher, B: MessageBody> where S::Error: Debug, { @@ -67,13 +67,13 @@ enum DispatcherMessage { Error(Response<()>), } -enum State { +enum State, B: MessageBody> { None, ServiceCall(S::Future), SendPayload(ResponseBody), } -impl State { +impl, B: MessageBody> State { fn is_empty(&self) -> bool { if let State::None = self { true @@ -86,7 +86,7 @@ impl State { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service>, S::Error: Debug, B: MessageBody, { @@ -140,7 +140,7 @@ where impl InnerDispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service>, S::Error: Debug, B: MessageBody, { @@ -463,7 +463,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service>, S::Error: Debug, B: MessageBody, { diff --git a/src/h1/service.rs b/src/h1/service.rs index 0f0452ee7..e21d0fb60 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -27,13 +27,13 @@ pub struct H1Service { impl H1Service where - S: NewService> + Clone, + S: NewService> + Clone, S::Service: Clone, S::Error: Debug, B: MessageBody, { /// Create new `HttpService` instance. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H1Service { @@ -49,15 +49,14 @@ where } } -impl NewService for H1Service +impl NewService for H1Service where T: AsyncRead + AsyncWrite, - S: NewService> + Clone, + S: NewService> + Clone, S::Service: Clone, S::Error: Debug, B: MessageBody, { - type Request = T; type Response = H1ServiceResult; type Error = DispatchError; type InitError = S::InitError; @@ -89,7 +88,7 @@ pub struct H1ServiceBuilder { impl H1ServiceBuilder where - S: NewService, + S: NewService, S::Service: Clone, S::Error: Debug, { @@ -186,7 +185,7 @@ where pub fn finish(self, service: F) -> H1Service where B: MessageBody, - F: IntoNewService, + F: IntoNewService, { let cfg = ServiceConfig::new( self.keep_alive, @@ -202,7 +201,7 @@ where } #[doc(hidden)] -pub struct H1ServiceResponse { +pub struct H1ServiceResponse, B> { fut: S::Future, cfg: Option, _t: PhantomData<(T, B)>, @@ -211,7 +210,7 @@ pub struct H1ServiceResponse { impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService>, + S: NewService>, S::Service: Clone, S::Error: Debug, B: MessageBody, @@ -237,7 +236,7 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where - S: Service> + Clone, + S: Service> + Clone, S::Error: Debug, B: MessageBody, { @@ -250,14 +249,13 @@ where } } -impl Service for H1ServiceHandler +impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service> + Clone, + S: Service> + Clone, S::Error: Debug, B: MessageBody, { - type Request = T; type Response = H1ServiceResult; type Error = DispatchError; type Future = Dispatcher; @@ -266,7 +264,7 @@ where self.srv.poll_ready().map_err(DispatchError::Service) } - fn call(&mut self, req: Self::Request) -> Self::Future { + fn call(&mut self, req: T) -> Self::Future { Dispatcher::new(req, self.cfg.clone(), self.srv.clone()) } } @@ -290,11 +288,10 @@ where } } -impl NewService for OneRequest +impl NewService for OneRequest where T: AsyncRead + AsyncWrite, { - type Request = T; type Response = (Request, Framed); type Error = ParseError; type InitError = (); @@ -316,11 +313,10 @@ pub struct OneRequestService { _t: PhantomData, } -impl Service for OneRequestService +impl Service for OneRequestService where T: AsyncRead + AsyncWrite, { - type Request = T; type Response = (Request, Framed); type Error = ParseError; type Future = OneRequestServiceResponse; @@ -329,7 +325,7 @@ where Ok(Async::Ready(())) } - fn call(&mut self, req: Self::Request) -> Self::Future { + fn call(&mut self, req: T) -> Self::Future { OneRequestServiceResponse { framed: Some(Framed::new(req, Codec::new(self.config.clone()))), } diff --git a/src/service.rs b/src/service.rs index 6a31b6bb8..aa507acb8 100644 --- a/src/service.rs +++ b/src/service.rs @@ -23,12 +23,11 @@ where } } -impl NewService for SendError +impl NewService)>> for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, { - type Request = Result)>; type Response = R; type Error = (E, Framed); type InitError = (); @@ -40,12 +39,11 @@ where } } -impl Service for SendError +impl Service)>> for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, { - type Request = Result)>; type Response = R; type Error = (E, Framed); type Future = Either)>, SendErrorFut>; @@ -54,7 +52,7 @@ where Ok(Async::Ready(())) } - fn call(&mut self, req: Self::Request) -> Self::Future { + fn call(&mut self, req: Result)>) -> Self::Future { match req { Ok(r) => Either::A(ok(r)), Err((e, framed)) => { @@ -131,12 +129,11 @@ where } } -impl NewService for SendResponse +impl NewService<(Response, Framed)> for SendResponse where T: AsyncRead + AsyncWrite, B: MessageBody, { - type Request = (Response, Framed); type Response = Framed; type Error = Error; type InitError = (); @@ -148,12 +145,11 @@ where } } -impl Service for SendResponse +impl Service<(Response, Framed)> for SendResponse where T: AsyncRead + AsyncWrite, B: MessageBody, { - type Request = (Response, Framed); type Response = Framed; type Error = Error; type Future = SendResponseFut; @@ -162,7 +158,7 @@ where Ok(Async::Ready(())) } - fn call(&mut self, (res, framed): Self::Request) -> Self::Future { + fn call(&mut self, (res, framed): (Response, Framed)) -> Self::Future { let (res, body) = res.replace_body(()); SendResponseFut { res: Some(Message::Item((res, body.length()))), diff --git a/src/test.rs b/src/test.rs index 84a959c41..3d12e344b 100644 --- a/src/test.rs +++ b/src/test.rs @@ -306,8 +306,7 @@ impl TestServer { pub fn with_factory( factory: F, ) -> TestServerRuntime< - impl Service - + Clone, + impl Service + Clone, > { let (tx, rx) = mpsc::channel(); @@ -339,8 +338,8 @@ impl TestServer { } fn new_connector( -) -> impl Service - + Clone { +) -> impl Service + Clone + { #[cfg(feature = "ssl")] { use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; @@ -441,7 +440,7 @@ impl TestServerRuntime { impl TestServerRuntime where - T: Service + Clone, + T: Service + Clone, T::Response: Connection, { /// Connect to websocket server at a given path diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 94be59f6e..68f8032e6 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -26,7 +26,7 @@ pub type DefaultClient = Client; /// WebSocket's client pub struct Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { connector: T, @@ -34,7 +34,7 @@ where impl Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { /// Create new websocket's client factory @@ -51,7 +51,7 @@ impl Default for Client { impl Clone for Client where - T: Service + Clone, + T: Service + Clone, T::Response: AsyncRead + AsyncWrite, { fn clone(&self) -> Self { @@ -61,13 +61,12 @@ where } } -impl Service for Client +impl Service for Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite + 'static, T::Future: 'static, { - type Request = Connect; type Response = Framed; type Error = ClientError; type Future = Either< @@ -79,7 +78,7 @@ where self.connector.poll_ready().map_err(ClientError::from) } - fn call(&mut self, mut req: Self::Request) -> Self::Future { + fn call(&mut self, mut req: Connect) -> Self::Future { if let Some(e) = req.err.take() { Either::A(err(e)) } else if let Some(e) = req.http_err.take() { diff --git a/src/ws/service.rs b/src/ws/service.rs index 9cce4d639..118a2244a 100644 --- a/src/ws/service.rs +++ b/src/ws/service.rs @@ -20,8 +20,7 @@ impl Default for VerifyWebSockets { } } -impl NewService for VerifyWebSockets { - type Request = (Request, Framed); +impl NewService<(Request, Framed)> for VerifyWebSockets { type Response = (Request, Framed); type Error = (HandshakeError, Framed); type InitError = (); @@ -33,8 +32,7 @@ impl NewService for VerifyWebSockets { } } -impl Service for VerifyWebSockets { - type Request = (Request, Framed); +impl Service<(Request, Framed)> for VerifyWebSockets { type Response = (Request, Framed); type Error = (HandshakeError, Framed); type Future = FutureResult; @@ -43,7 +41,7 @@ impl Service for VerifyWebSockets { Ok(Async::Ready(())) } - fn call(&mut self, (req, framed): Self::Request) -> Self::Future { + fn call(&mut self, (req, framed): (Request, Framed)) -> Self::Future { match verify_handshake(&req) { Err(e) => Err((e, framed)).into_future(), Ok(_) => Ok((req, framed)).into_future(), diff --git a/src/ws/transport.rs b/src/ws/transport.rs index 102d02b43..8cd79cb03 100644 --- a/src/ws/transport.rs +++ b/src/ws/transport.rs @@ -8,7 +8,7 @@ use super::{Codec, Frame, Message}; pub struct Transport where - S: Service, + S: Service, T: AsyncRead + AsyncWrite, { inner: FramedTransport, @@ -17,17 +17,17 @@ where impl Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { - pub fn new>(io: T, service: F) -> Self { + pub fn new>(io: T, service: F) -> Self { Transport { inner: FramedTransport::new(Framed::new(io, Codec::new()), service), } } - pub fn with>(framed: Framed, service: F) -> Self { + pub fn with>(framed: Framed, service: F) -> Self { Transport { inner: FramedTransport::new(framed, service), } @@ -37,7 +37,7 @@ where impl Future for Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { From c0f8bc9e902036ef72b7be105e2fe0a7e8b7c6b3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 30 Nov 2018 16:04:33 -0800 Subject: [PATCH 099/208] fix ssl support --- src/client/connector.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 3729ce394..9d0841541 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -272,12 +272,12 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Request = Connect, + Connect, Response = (Connect, Io1), Error = ConnectorError, >, T2: Service< - Request = Connect, + Connect, Response = (Connect, Io2), Error = ConnectorError, >, @@ -301,7 +301,7 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, @@ -344,7 +344,7 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, T: Service, { - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } @@ -370,7 +370,7 @@ mod connect_impl { Io2: AsyncRead + AsyncWrite + 'static, T: Service, { - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } From e9121025b7abde064124584118c7689a3e5b519e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 6 Dec 2018 14:32:52 -0800 Subject: [PATCH 100/208] convert to 2018 edition --- Cargo.toml | 1 + examples/echo.rs | 6 ++- examples/echo2.rs | 3 +- examples/framed_hello.rs | 3 +- examples/hello-world.rs | 6 ++- src/body.rs | 2 +- src/client/connector.rs | 21 ++++------ src/client/error.rs | 13 ++----- src/client/pipeline.rs | 26 +++++++------ src/client/request.rs | 24 ++++++------ src/client/response.rs | 8 ++-- src/config.rs | 9 +++-- src/error.rs | 7 ++-- src/h1/client.rs | 21 +++++----- src/h1/codec.rs | 19 ++++----- src/h1/decoder.rs | 21 +++++----- src/h1/dispatcher.rs | 22 +++++------ src/h1/encoder.rs | 20 +++++----- src/h1/mod.rs | 2 +- src/h1/service.rs | 21 +++++----- src/header.rs | 9 ++--- src/httpcodes.rs | 3 +- src/httpmessage.rs | 39 ++++++++++++------- src/json.rs | 22 +++++++---- src/lib.rs | 83 +++++++++------------------------------- src/message.rs | 4 +- src/payload.rs | 26 ++++++++----- src/request.rs | 9 ++--- src/response.rs | 11 +++--- src/service.rs | 22 ++++++----- src/test.rs | 14 +++---- src/ws/client/connect.rs | 5 +-- src/ws/client/error.rs | 9 ++--- src/ws/client/service.rs | 11 +++--- src/ws/frame.rs | 7 ++-- src/ws/mod.rs | 47 +++++++++++++++-------- src/ws/service.rs | 4 +- tests/test_client.rs | 3 +- tests/test_server.rs | 18 ++++++--- tests/test_ws.rs | 3 +- 40 files changed, 310 insertions(+), 294 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b1dd69ac1..57ed3f9b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ categories = ["network-programming", "asynchronous", "web-programming::websocket"] license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] +edition = "2018" [package.metadata.docs.rs] features = ["session"] diff --git a/examples/echo.rs b/examples/echo.rs index c98863e52..0453ad6a7 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -34,7 +34,9 @@ fn main() { res.header("x-head", HeaderValue::from_static("dummy value!")); Ok(res.body(bytes)) }) - }).map(|_| ()) - }).unwrap() + }) + .map(|_| ()) + }) + .unwrap() .run(); } diff --git a/examples/echo2.rs b/examples/echo2.rs index 4c144b433..3206ff507 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -37,6 +37,7 @@ fn main() { .server_hostname("localhost") .finish(|_req: Request| handle_request(_req)) .map(|_| ()) - }).unwrap() + }) + .unwrap() .run(); } diff --git a/examples/framed_hello.rs b/examples/framed_hello.rs index 0c9175a9e..6c53d27f3 100644 --- a/examples/framed_hello.rs +++ b/examples/framed_hello.rs @@ -29,6 +29,7 @@ fn main() { .map_err(|_| ()) .map(|_| ()) }) - }).unwrap() + }) + .unwrap() .run(); } diff --git a/examples/hello-world.rs b/examples/hello-world.rs index 74ff509b3..b477f191d 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -29,7 +29,9 @@ fn main() { let mut res = Response::Ok(); res.header("x-head", HeaderValue::from_static("dummy value!")); future::ok::<_, ()>(res.body("Hello world!")) - }).map(|_| ()) - }).unwrap() + }) + .map(|_| ()) + }) + .unwrap() .run(); } diff --git a/src/body.rs b/src/body.rs index cc4e77af5..4b71e9bb9 100644 --- a/src/body.rs +++ b/src/body.rs @@ -4,7 +4,7 @@ use std::{fmt, mem}; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll, Stream}; -use error::{Error, PayloadError}; +use crate::error::{Error, PayloadError}; /// Type represent streaming payload pub type PayloadStream = Box>; diff --git a/src/client/connector.rs b/src/client/connector.rs index 9d0841541..74ee6a7e3 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -143,7 +143,8 @@ impl Connector { self.resolver .map_err(ConnectorError::from) .and_then(TcpConnector::default().from_err()), - ).map_err(|e| match e { + ) + .map_err(|e| match e { TimeoutError::Service(e) => e, TimeoutError::Timeout => ConnectorError::Timeout, }); @@ -170,7 +171,8 @@ impl Connector { OpensslConnector::service(self.connector) .map_err(ConnectorError::SslError), ), - ).map_err(|e| match e { + ) + .map_err(|e| match e { TimeoutError::Service(e) => e, TimeoutError::Timeout => ConnectorError::Timeout, }); @@ -180,7 +182,8 @@ impl Connector { self.resolver .map_err(ConnectorError::from) .and_then(TcpConnector::default().from_err()), - ).map_err(|e| match e { + ) + .map_err(|e| match e { TimeoutError::Service(e) => e, TimeoutError::Timeout => ConnectorError::Timeout, }); @@ -271,16 +274,8 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service< - Connect, - Response = (Connect, Io1), - Error = ConnectorError, - >, - T2: Service< - Connect, - Response = (Connect, Io2), - Error = ConnectorError, - >, + T1: Service, + T2: Service, { pub(crate) tcp_pool: ConnectionPool, pub(crate) ssl_pool: ConnectionPool, diff --git a/src/client/error.rs b/src/client/error.rs index 2c4753642..d2a0f38ec 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -1,23 +1,18 @@ use std::io; +use failure::Fail; use trust_dns_resolver::error::ResolveError; #[cfg(feature = "ssl")] use openssl::ssl::Error as SslError; -#[cfg(all( - feature = "tls", - not(any(feature = "ssl", feature = "rust-tls")) -))] +#[cfg(all(feature = "tls", not(any(feature = "ssl", feature = "rust-tls"))))] use native_tls::Error as SslError; -#[cfg(all( - feature = "rust-tls", - not(any(feature = "tls", feature = "ssl")) -))] +#[cfg(all(feature = "rust-tls", not(any(feature = "tls", feature = "ssl"))))] use std::io::Error as SslError; -use error::{Error, ParseError}; +use crate::error::{Error, ParseError}; /// A set of errors that can occur while connecting to an HTTP host #[derive(Fail, Debug)] diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index e75265507..fc1e53e8f 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -10,10 +10,10 @@ use tokio_io::{AsyncRead, AsyncWrite}; use super::error::{ConnectorError, SendRequestError}; use super::response::ClientResponse; use super::{Connect, Connection}; -use body::{BodyLength, MessageBody, PayloadStream}; -use error::PayloadError; -use h1; -use message::RequestHead; +use crate::body::{BodyLength, MessageBody, PayloadStream}; +use crate::error::PayloadError; +use crate::h1; +use crate::message::RequestHead; pub(crate) fn send_request( head: RequestHead, @@ -174,14 +174,16 @@ impl Stream for Payload { fn poll(&mut self) -> Poll, Self::Error> { match self.framed.as_mut().unwrap().poll()? { Async::NotReady => Ok(Async::NotReady), - Async::Ready(Some(chunk)) => if let Some(chunk) = chunk { - Ok(Async::Ready(Some(chunk))) - } else { - let framed = self.framed.take().unwrap(); - let force_close = framed.get_codec().keepalive(); - release_connection(framed, force_close); - Ok(Async::Ready(None)) - }, + Async::Ready(Some(chunk)) => { + if let Some(chunk) = chunk { + Ok(Async::Ready(Some(chunk))) + } else { + let framed = self.framed.take().unwrap(); + let force_close = framed.get_codec().keepalive(); + release_connection(framed, force_close); + Ok(Async::Ready(None)) + } + } Async::Ready(None) => Ok(Async::Ready(None)), } } diff --git a/src/client/request.rs b/src/client/request.rs index e71c3ffd8..5f294bbd8 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -8,14 +8,14 @@ use cookie::{Cookie, CookieJar}; use futures::{Future, Stream}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; -use body::{BodyStream, MessageBody}; -use error::Error; -use header::{self, Header, IntoHeaderValue}; -use http::{ +use crate::body::{BodyStream, MessageBody}; +use crate::error::Error; +use crate::header::{self, Header, IntoHeaderValue}; +use crate::http::{ uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, Uri, Version, }; -use message::{ConnectionType, Head, RequestHead}; +use crate::message::{ConnectionType, Head, RequestHead}; use super::response::ClientResponse; use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; @@ -355,14 +355,16 @@ impl ClientRequestBuilder { { if let Some(parts) = parts(&mut self.head, &self.err) { match HeaderName::try_from(key) { - Ok(key) => if !parts.headers.contains_key(&key) { - match value.try_into() { - Ok(value) => { - parts.headers.insert(key, value); + Ok(key) => { + if !parts.headers.contains_key(&key) { + match value.try_into() { + Ok(value) => { + parts.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), } - Err(e) => self.err = Some(e.into()), } - }, + } Err(e) => self.err = Some(e.into()), }; } diff --git a/src/client/response.rs b/src/client/response.rs index dc7b13c18..6bfdfc321 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -5,10 +5,10 @@ use bytes::Bytes; use futures::{Async, Poll, Stream}; use http::{HeaderMap, StatusCode, Version}; -use body::PayloadStream; -use error::PayloadError; -use httpmessage::HttpMessage; -use message::{Head, ResponseHead}; +use crate::body::PayloadStream; +use crate::error::PayloadError; +use crate::httpmessage::HttpMessage; +use crate::message::{Head, ResponseHead}; use super::pipeline::Payload; diff --git a/src/config.rs b/src/config.rs index 833bca7f2..661c0901f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,6 +6,7 @@ use std::{fmt, net}; use bytes::BytesMut; use futures::{future, Future}; +use log::error; use time; use tokio_current_thread::spawn; use tokio_timer::{sleep, Delay}; @@ -268,9 +269,11 @@ impl ServiceConfigBuilder { pub fn server_address(mut self, addr: S) -> Self { match addr.to_socket_addrs() { Err(err) => error!("Can not convert to SocketAddr: {}", err), - Ok(mut addrs) => if let Some(addr) = addrs.next() { - self.addr = addr; - }, + Ok(mut addrs) => { + if let Some(addr) = addrs.next() { + self.addr = addr; + } + } } self } diff --git a/src/error.rs b/src/error.rs index 2e0c2382a..280f9a329 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,8 +20,8 @@ use tokio_timer::Error as TimerError; // re-exports pub use cookie::ParseError as CookieParseError; -use body::Body; -use response::{Response, ResponseParts}; +use crate::body::Body; +use crate::response::{Response, ResponseParts}; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// for actix web operations @@ -186,7 +186,8 @@ impl From for Error { /// Compatibility for `failure::Error` impl ResponseError for failure::Compat where T: fmt::Display + fmt::Debug + Sync + Send + 'static -{} +{ +} impl From for Error { fn from(err: failure::Error) -> Error { diff --git a/src/h1/client.rs b/src/h1/client.rs index 7704ba97a..f547983fa 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -1,22 +1,23 @@ #![allow(unused_imports, unused_variables, dead_code)] use std::io::{self, Write}; +use bitflags::bitflags; use bytes::{BufMut, Bytes, BytesMut}; +use http::header::{ + HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, +}; +use http::{Method, Version}; use tokio_codec::{Decoder, Encoder}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; use super::{decoder, encoder}; use super::{Message, MessageType}; -use body::BodyLength; -use client::ClientResponse; -use config::ServiceConfig; -use error::{ParseError, PayloadError}; -use helpers; -use http::header::{ - HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, -}; -use http::{Method, Version}; -use message::{ConnectionType, Head, MessagePool, RequestHead}; +use crate::body::BodyLength; +use crate::client::ClientResponse; +use crate::config::ServiceConfig; +use crate::error::{ParseError, PayloadError}; +use crate::helpers; +use crate::message::{ConnectionType, Head, MessagePool, RequestHead}; bitflags! { struct Flags: u8 { diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 3174e78ea..54c1ce2e6 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -2,21 +2,22 @@ use std::fmt; use std::io::{self, Write}; +use bitflags::bitflags; use bytes::{BufMut, Bytes, BytesMut}; +use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; +use http::{Method, StatusCode, Version}; use tokio_codec::{Decoder, Encoder}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; use super::{decoder, encoder}; use super::{Message, MessageType}; -use body::BodyLength; -use config::ServiceConfig; -use error::ParseError; -use helpers; -use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; -use http::{Method, StatusCode, Version}; -use message::{ConnectionType, Head, ResponseHead}; -use request::Request; -use response::Response; +use crate::body::BodyLength; +use crate::config::ServiceConfig; +use crate::error::ParseError; +use crate::helpers; +use crate::message::{ConnectionType, Head, ResponseHead}; +use crate::request::Request; +use crate::response::Response; bitflags! { struct Flags: u8 { diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index a081a5cf3..26b28440b 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -3,15 +3,16 @@ use std::{io, mem}; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; -use httparse; -use tokio_codec::Decoder; - -use client::ClientResponse; -use error::ParseError; use http::header::{HeaderName, HeaderValue}; use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version}; -use message::ConnectionType; -use request::Request; +use httparse; +use log::{debug, error, trace}; +use tokio_codec::Decoder; + +use crate::client::ClientResponse; +use crate::error::ParseError; +use crate::message::ConnectionType; +use crate::request::Request; const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; @@ -825,13 +826,13 @@ mod tests { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); let mut reader = MessageDecoder::::default(); - assert!{ reader.decode(&mut buf).unwrap().is_none() } + assert! { reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"t"); - assert!{ reader.decode(&mut buf).unwrap().is_none() } + assert! { reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"es"); - assert!{ reader.decode(&mut buf).unwrap().is_none() } + assert! { reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"t: value\r\n\r\n"); let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 5e2742aa5..59b419c3e 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -5,19 +5,19 @@ use std::time::Instant; use actix_net::codec::Framed; use actix_net::service::Service; - -use futures::{Async, Future, Poll, Sink, Stream}; +use bitflags::bitflags; +use futures::{try_ready, Async, Future, Poll, Sink, Stream}; +use log::{debug, error, trace}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; -use error::{ParseError, PayloadError}; -use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; - -use body::{Body, BodyLength, MessageBody, ResponseBody}; -use config::ServiceConfig; -use error::DispatchError; -use request::Request; -use response::Response; +use crate::body::{Body, BodyLength, MessageBody, ResponseBody}; +use crate::config::ServiceConfig; +use crate::error::DispatchError; +use crate::error::{ParseError, PayloadError}; +use crate::payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; +use crate::request::Request; +use crate::response::Response; use super::codec::Codec; use super::{H1ServiceResult, Message, MessageType}; @@ -224,7 +224,7 @@ where }, State::ServiceCall(mut fut) => { match fut.poll().map_err(DispatchError::Service)? { - Async::Ready(mut res) => { + Async::Ready(res) => { let (res, body) = res.replace_body(()); Some(self.send_response(res, body)?) } diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index f9b4aab35..92456520a 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -9,16 +9,15 @@ use bytes::{BufMut, Bytes, BytesMut}; use http::header::{ HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, }; -use http::{HeaderMap, StatusCode, Version}; +use http::{HeaderMap, Method, StatusCode, Version}; -use body::BodyLength; -use config::ServiceConfig; -use header::ContentEncoding; -use helpers; -use http::Method; -use message::{ConnectionType, RequestHead, ResponseHead}; -use request::Request; -use response::Response; +use crate::body::BodyLength; +use crate::config::ServiceConfig; +use crate::header::ContentEncoding; +use crate::helpers; +use crate::message::{ConnectionType, RequestHead, ResponseHead}; +use crate::request::Request; +use crate::response::Response; const AVERAGE_HEADER_SIZE: usize = 30; @@ -205,7 +204,8 @@ impl MessageType for RequestHead { Version::HTTP_11 => "HTTP/1.1", Version::HTTP_2 => "HTTP/2.0", } - ).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + ) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) } } diff --git a/src/h1/mod.rs b/src/h1/mod.rs index da80e55e9..461ecb959 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -16,7 +16,7 @@ pub use self::codec::Codec; pub use self::dispatcher::Dispatcher; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; -use request::Request; +use crate::request::Request; /// H1 service response type pub enum H1ServiceResult { diff --git a/src/h1/service.rs b/src/h1/service.rs index e21d0fb60..1a5a587c0 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -5,14 +5,15 @@ use std::net; use actix_net::codec::Framed; use actix_net::service::{IntoNewService, NewService, Service}; use futures::future::{ok, FutureResult}; -use futures::{Async, Future, Poll, Stream}; +use futures::{try_ready, Async, Future, Poll, Stream}; +use log::error; use tokio_io::{AsyncRead, AsyncWrite}; -use body::MessageBody; -use config::{KeepAlive, ServiceConfig}; -use error::{DispatchError, ParseError}; -use request::Request; -use response::Response; +use crate::body::MessageBody; +use crate::config::{KeepAlive, ServiceConfig}; +use crate::error::{DispatchError, ParseError}; +use crate::request::Request; +use crate::response::Response; use super::codec::Codec; use super::dispatcher::Dispatcher; @@ -174,9 +175,11 @@ where pub fn server_address(mut self, addr: U) -> Self { match addr.to_socket_addrs() { Err(err) => error!("Can not convert to SocketAddr: {}", err), - Ok(mut addrs) => if let Some(addr) = addrs.next() { - self.addr = addr; - }, + Ok(mut addrs) => { + if let Some(addr) = addrs.next() { + self.addr = addr; + } + } } self } diff --git a/src/header.rs b/src/header.rs index b1ba6524a..6276dd4f4 100644 --- a/src/header.rs +++ b/src/header.rs @@ -1,13 +1,12 @@ //! Various http headers use bytes::Bytes; +pub use http::header::*; +use http::Error as HttpError; use mime::Mime; -use modhttp::Error as HttpError; -pub use modhttp::header::*; - -use error::ParseError; -use httpmessage::HttpMessage; +use crate::error::ParseError; +use crate::httpmessage::HttpMessage; #[doc(hidden)] /// A trait for any object that will represent a header field and value. diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 7d42a1cc3..80722734a 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -1,7 +1,8 @@ //! Basic http responses #![allow(non_upper_case_globals)] use http::StatusCode; -use response::{Response, ResponseBuilder}; + +use crate::response::{Response, ResponseBuilder}; macro_rules! STATIC_RESP { ($name:ident, $status:expr) => { diff --git a/src/httpmessage.rs b/src/httpmessage.rs index f68f3650b..e239a7337 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -10,11 +10,11 @@ use serde::de::DeserializeOwned; use serde_urlencoded; use std::str; -use error::{ +use crate::error::{ ContentTypeError, ParseError, PayloadError, ReadlinesError, UrlencodedError, }; -use header::Header; -use json::JsonBody; +use crate::header::Header; +use crate::json::JsonBody; /// Trait that implements general purpose operations on http messages pub trait HttpMessage: Sized { @@ -438,7 +438,8 @@ where body.extend_from_slice(&chunk); Ok(body) } - }).map(|body| body.freeze()), + }) + .map(|body| body.freeze()), )); self.poll() } @@ -546,7 +547,8 @@ where body.extend_from_slice(&chunk); Ok(body) } - }).and_then(move |body| { + }) + .and_then(move |body| { if (encoding as *const Encoding) == UTF_8 { serde_urlencoded::from_bytes::(&body) .map_err(|_| UrlencodedError::Parse) @@ -604,7 +606,8 @@ mod tests { let req = TestRequest::with_header( "content-type", "applicationadfadsfasdflknadsfklnadsfjson", - ).finish(); + ) + .finish(); assert_eq!(Err(ContentTypeError::ParseError), req.mime_type()); } @@ -619,7 +622,8 @@ mod tests { let req = TestRequest::with_header( "content-type", "application/json; charset=ISO-8859-2", - ).finish(); + ) + .finish(); assert_eq!(ISO_8859_2.name(), req.encoding().unwrap().name()); } @@ -631,7 +635,8 @@ mod tests { let req = TestRequest::with_header( "content-type", "application/json; charset=kkkttktk", - ).finish(); + ) + .finish(); assert_eq!( Some(ContentTypeError::UnknownEncoding), req.encoding().err() @@ -651,7 +656,8 @@ mod tests { .header( header::TRANSFER_ENCODING, Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"), - ).finish(); + ) + .finish(); assert!(req.chunked().is_err()); } @@ -689,7 +695,8 @@ mod tests { let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "xxxx") + ) + .header(header::CONTENT_LENGTH, "xxxx") .finish(); assert_eq!( req.urlencoded::().poll().err().unwrap(), @@ -699,7 +706,8 @@ mod tests { let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "1000000") + ) + .header(header::CONTENT_LENGTH, "1000000") .finish(); assert_eq!( req.urlencoded::().poll().err().unwrap(), @@ -720,7 +728,8 @@ mod tests { let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "11") + ) + .header(header::CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) .finish(); @@ -735,7 +744,8 @@ mod tests { let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8", - ).header(header::CONTENT_LENGTH, "11") + ) + .header(header::CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) .finish(); @@ -786,7 +796,8 @@ mod tests { b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ industry. Lorem Ipsum has been the industry's standard dummy\n\ Contrary to popular belief, Lorem Ipsum is not simply random text.", - )).finish(); + )) + .finish(); let mut r = Readlines::new(&req); match r.poll().ok().unwrap() { Async::Ready(Some(s)) => assert_eq!( diff --git a/src/json.rs b/src/json.rs index e2c99ba3f..0b6ac377f 100644 --- a/src/json.rs +++ b/src/json.rs @@ -6,8 +6,8 @@ use mime; use serde::de::DeserializeOwned; use serde_json; -use error::JsonPayloadError; -use httpmessage::HttpMessage; +use crate::error::JsonPayloadError; +use crate::httpmessage::HttpMessage; /// Request payload json parser that resolves to a deserialized `T` value. /// @@ -124,7 +124,8 @@ impl Future for JsonBod body.extend_from_slice(&chunk); Ok(body) } - }).and_then(|body| Ok(serde_json::from_slice::(&body)?)); + }) + .and_then(|body| Ok(serde_json::from_slice::(&body)?)); self.fut = Some(Box::new(fut)); self.poll() } @@ -170,7 +171,8 @@ mod tests { .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/text"), - ).finish(); + ) + .finish(); let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); @@ -178,10 +180,12 @@ mod tests { .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ).header( + ) + .header( header::CONTENT_LENGTH, header::HeaderValue::from_static("10000"), - ).finish(); + ) + .finish(); let mut json = req.json::().limit(100); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); @@ -189,10 +193,12 @@ mod tests { .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ).header( + ) + .header( header::CONTENT_LENGTH, header::HeaderValue::from_static("16"), - ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .finish(); let mut json = req.json::(); diff --git a/src/lib.rs b/src/lib.rs index 4870eb64f..76c3a9679 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,56 +62,12 @@ // #![warn(missing_docs)] #![allow(dead_code)] -extern crate actix; -extern crate actix_net; -#[macro_use] -extern crate log; -extern crate base64; -extern crate byteorder; -extern crate bytes; -extern crate sha1; -extern crate time; -#[macro_use] -extern crate bitflags; -#[macro_use] -extern crate failure; -#[macro_use] -extern crate futures; -extern crate cookie; -extern crate encoding; -extern crate http as modhttp; -extern crate httparse; -extern crate indexmap; -extern crate mime; -extern crate net2; -extern crate percent_encoding; -extern crate rand; -extern crate serde; -extern crate serde_json; -extern crate serde_urlencoded; -extern crate slab; -extern crate tokio; -extern crate tokio_codec; -extern crate tokio_current_thread; -extern crate tokio_io; -extern crate tokio_tcp; -extern crate tokio_timer; -extern crate trust_dns_proto; -extern crate trust_dns_resolver; -extern crate url as urlcrate; - -#[cfg(test)] -#[macro_use] -extern crate serde_derive; - -#[cfg(feature = "ssl")] -extern crate openssl; - pub mod body; pub mod client; mod config; mod extensions; mod header; +mod helpers; mod httpcodes; mod httpmessage; mod json; @@ -123,18 +79,17 @@ mod service; pub mod error; pub mod h1; -pub(crate) mod helpers; pub mod test; pub mod ws; -pub use body::{Body, MessageBody}; -pub use error::{Error, ResponseError, Result}; -pub use extensions::Extensions; -pub use httpmessage::HttpMessage; -pub use request::Request; -pub use response::Response; -pub use service::{SendError, SendResponse}; +pub use self::body::{Body, MessageBody}; pub use self::config::{KeepAlive, ServiceConfig, ServiceConfigBuilder}; +pub use self::error::{Error, ResponseError, Result}; +pub use self::extensions::Extensions; +pub use self::httpmessage::HttpMessage; +pub use self::request::Request; +pub use self::response::Response; +pub use self::service::{SendError, SendResponse}; pub mod dev { //! The `actix-web` prelude for library developers @@ -147,31 +102,31 @@ pub mod dev { //! use actix_http::dev::*; //! ``` - pub use httpmessage::{MessageBody, Readlines, UrlEncoded}; - pub use json::JsonBody; - pub use payload::{Payload, PayloadBuffer}; - pub use response::ResponseBuilder; + pub use crate::httpmessage::{MessageBody, Readlines, UrlEncoded}; + pub use crate::json::JsonBody; + pub use crate::payload::{Payload, PayloadBuffer}; + pub use crate::response::ResponseBuilder; } pub mod http { //! Various HTTP related types // re-exports - pub use modhttp::header::{HeaderName, HeaderValue}; - pub use modhttp::{Method, StatusCode, Version}; + pub use http::header::{HeaderName, HeaderValue}; + pub use http::{Method, StatusCode, Version}; #[doc(hidden)] - pub use modhttp::{uri, Error, HeaderMap, HttpTryFrom, Uri}; + pub use http::{uri, Error, HeaderMap, HttpTryFrom, Uri}; #[doc(hidden)] - pub use modhttp::uri::PathAndQuery; + pub use http::uri::PathAndQuery; pub use cookie::{Cookie, CookieBuilder}; /// Various http headers pub mod header { - pub use header::*; + pub use crate::header::*; } - pub use header::ContentEncoding; - pub use message::ConnectionType; + pub use crate::header::ContentEncoding; + pub use crate::message::ConnectionType; } diff --git a/src/message.rs b/src/message.rs index d3d52e2f9..31d61f63d 100644 --- a/src/message.rs +++ b/src/message.rs @@ -4,8 +4,8 @@ use std::rc::Rc; use http::{HeaderMap, Method, StatusCode, Uri, Version}; -use extensions::Extensions; -use payload::Payload; +use crate::extensions::Extensions; +use crate::payload::Payload; /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] diff --git a/src/payload.rs b/src/payload.rs index b05924969..37f06d433 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -9,7 +9,7 @@ use std::cmp; use std::collections::VecDeque; use std::rc::{Rc, Weak}; -use error::PayloadError; +use crate::error::PayloadError; /// max buffer size 32k pub(crate) const MAX_BUFFER_SIZE: usize = 32_768; @@ -515,7 +515,8 @@ where .fold(BytesMut::new(), |mut b, c| { b.extend_from_slice(c); b - }).freeze() + }) + .freeze() } } @@ -547,7 +548,8 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })).unwrap(); + })) + .unwrap(); } #[test] @@ -571,7 +573,8 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })).unwrap(); + })) + .unwrap(); } #[test] @@ -588,7 +591,8 @@ mod tests { payload.readany().err().unwrap(); let res: Result<(), ()> = Ok(()); result(res) - })).unwrap(); + })) + .unwrap(); } #[test] @@ -616,7 +620,8 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })).unwrap(); + })) + .unwrap(); } #[test] @@ -649,7 +654,8 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })).unwrap(); + })) + .unwrap(); } #[test] @@ -682,7 +688,8 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })).unwrap(); + })) + .unwrap(); } #[test] @@ -703,6 +710,7 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })).unwrap(); + })) + .unwrap(); } } diff --git a/src/request.rs b/src/request.rs index 248555e60..60ddee19b 100644 --- a/src/request.rs +++ b/src/request.rs @@ -4,11 +4,10 @@ use std::rc::Rc; use http::{header, HeaderMap, Method, Uri, Version}; -use extensions::Extensions; -use httpmessage::HttpMessage; -use payload::Payload; - -use message::{Message, MessagePool, RequestHead}; +use crate::extensions::Extensions; +use crate::httpmessage::HttpMessage; +use crate::message::{Message, MessagePool, RequestHead}; +use crate::payload::Payload; /// Request pub struct Request { diff --git a/src/response.rs b/src/response.rs index ae68189d8..e506cd163 100644 --- a/src/response.rs +++ b/src/response.rs @@ -12,10 +12,10 @@ use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version}; use serde::Serialize; use serde_json; -use body::{Body, BodyStream, MessageBody, ResponseBody}; -use error::Error; -use header::{Header, IntoHeaderValue}; -use message::{ConnectionType, Head, ResponseHead}; +use crate::body::{Body, BodyStream, MessageBody, ResponseBody}; +use crate::error::Error; +use crate::header::{Header, IntoHeaderValue}; +use crate::message::{ConnectionType, Head, ResponseHead}; /// max write buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; @@ -161,7 +161,8 @@ impl Response { HeaderValue::from_str(&cookie.to_string()) .map(|c| { h.append(header::SET_COOKIE, c); - }).map_err(|e| e.into()) + }) + .map_err(|e| e.into()) } /// Remove all cookies with the given name from this response. Returns diff --git a/src/service.rs b/src/service.rs index aa507acb8..a6a820977 100644 --- a/src/service.rs +++ b/src/service.rs @@ -6,10 +6,10 @@ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll, Sink}; use tokio_io::{AsyncRead, AsyncWrite}; -use body::{BodyLength, MessageBody, ResponseBody}; -use error::{Error, ResponseError}; -use h1::{Codec, Message}; -use response::Response; +use crate::body::{BodyLength, MessageBody, ResponseBody}; +use crate::error::{Error, ResponseError}; +use crate::h1::{Codec, Message}; +use crate::response::Response; pub struct SendError(PhantomData<(T, R, E)>); @@ -56,7 +56,7 @@ where match req { Ok(r) => Either::A(ok(r)), Err((e, framed)) => { - let mut res = e.error_response().set_body(format!("{}", e)); + let res = e.error_response().set_body(format!("{}", e)); let (res, _body) = res.replace_body(()); Either::B(SendErrorFut { framed: Some(framed), @@ -206,11 +206,13 @@ where // flush write buffer if !framed.is_write_buf_empty() { match framed.poll_complete()? { - Async::Ready(_) => if body_ready { - continue; - } else { - return Ok(Async::NotReady); - }, + Async::Ready(_) => { + if body_ready { + continue; + } else { + return Ok(Async::NotReady); + } + } Async::NotReady => return Ok(Async::NotReady), } } diff --git a/src/test.rs b/src/test.rs index 3d12e344b..308d2b4df 100644 --- a/src/test.rs +++ b/src/test.rs @@ -17,15 +17,15 @@ use net2::TcpBuilder; use tokio::runtime::current_thread::Runtime; use tokio_io::{AsyncRead, AsyncWrite}; -use body::MessageBody; -use client::{ +use crate::body::MessageBody; +use crate::client::{ ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, ConnectorError, SendRequestError, }; -use header::{Header, IntoHeaderValue}; -use payload::Payload; -use request::Request; -use ws; +use crate::header::{Header, IntoHeaderValue}; +use crate::payload::Payload; +use crate::request::Request; +use crate::ws; /// Test `Request` builder /// @@ -338,7 +338,7 @@ impl TestServer { } fn new_connector( -) -> impl Service + Clone + ) -> impl Service + Clone { #[cfg(feature = "ssl")] { diff --git a/src/ws/client/connect.rs b/src/ws/client/connect.rs index 575b4e4d8..09d025631 100644 --- a/src/ws/client/connect.rs +++ b/src/ws/client/connect.rs @@ -5,10 +5,9 @@ use cookie::Cookie; use http::header::{HeaderName, HeaderValue}; use http::{Error as HttpError, HttpTryFrom}; -use client::{ClientRequest, ClientRequestBuilder}; -use header::IntoHeaderValue; - use super::ClientError; +use crate::client::{ClientRequest, ClientRequestBuilder}; +use crate::header::IntoHeaderValue; /// `WebSocket` connection pub struct Connect { diff --git a/src/ws/client/error.rs b/src/ws/client/error.rs index 589648eea..62a3f47a9 100644 --- a/src/ws/client/error.rs +++ b/src/ws/client/error.rs @@ -2,12 +2,11 @@ use std::io; use actix_net::connector::ConnectorError; -use http::header::HeaderValue; -use http::StatusCode; +use failure::Fail; +use http::{header::HeaderValue, Error as HttpError, StatusCode}; -use error::ParseError; -use http::Error as HttpError; -use ws::ProtocolError; +use crate::error::ParseError; +use crate::ws::ProtocolError; /// Websocket client error #[derive(Fail, Debug)] diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 68f8032e6..8dd407b0e 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -6,17 +6,18 @@ use actix_net::connector::{Connect as TcpConnect, ConnectorError, DefaultConnect use actix_net::service::Service; use base64; use futures::future::{err, Either, FutureResult}; -use futures::{Async, Future, Poll, Sink, Stream}; +use futures::{try_ready, Async, Future, Poll, Sink, Stream}; use http::header::{self, HeaderValue}; use http::{HttpTryFrom, StatusCode}; +use log::trace; use rand; use sha1::Sha1; use tokio_io::{AsyncRead, AsyncWrite}; -use body::BodyLength; -use client::ClientResponse; -use h1; -use ws::Codec; +use crate::body::BodyLength; +use crate::client::ClientResponse; +use crate::h1; +use crate::ws::Codec; use super::{ClientError, Connect, Protocol}; diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 56d282964..32ad4ef4f 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -1,10 +1,11 @@ use byteorder::{ByteOrder, LittleEndian, NetworkEndian}; use bytes::{BufMut, Bytes, BytesMut}; +use log::debug; use rand; -use ws::mask::apply_mask; -use ws::proto::{CloseCode, CloseReason, OpCode}; -use ws::ProtocolError; +use crate::ws::mask::apply_mask; +use crate::ws::proto::{CloseCode, CloseReason, OpCode}; +use crate::ws::ProtocolError; /// A struct representing a `WebSocket` frame. #[derive(Debug)] diff --git a/src/ws/mod.rs b/src/ws/mod.rs index f1c91714a..ccd9ef4ac 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -5,10 +5,12 @@ //! communicate with the peer. use std::io; -use error::ResponseError; +use failure::Fail; use http::{header, Method, StatusCode}; -use request::Request; -use response::{Response, ResponseBuilder}; + +use crate::error::ResponseError; +use crate::request::Request; +use crate::response::{Response, ResponseBuilder}; mod client; mod codec; @@ -221,7 +223,8 @@ mod tests { .header( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ).finish(); + ) + .finish(); assert_eq!( HandshakeError::NoConnectionUpgrade, verify_handshake(&req).err().unwrap() @@ -231,10 +234,12 @@ mod tests { .header( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ).header( + ) + .header( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ).finish(); + ) + .finish(); assert_eq!( HandshakeError::NoVersionHeader, verify_handshake(&req).err().unwrap() @@ -244,13 +249,16 @@ mod tests { .header( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ).header( + ) + .header( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ).header( + ) + .header( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("5"), - ).finish(); + ) + .finish(); assert_eq!( HandshakeError::UnsupportedVersion, verify_handshake(&req).err().unwrap() @@ -260,13 +268,16 @@ mod tests { .header( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ).header( + ) + .header( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ).header( + ) + .header( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ).finish(); + ) + .finish(); assert_eq!( HandshakeError::BadWebsocketKey, verify_handshake(&req).err().unwrap() @@ -276,16 +287,20 @@ mod tests { .header( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ).header( + ) + .header( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ).header( + ) + .header( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ).header( + ) + .header( header::SEC_WEBSOCKET_KEY, header::HeaderValue::from_static("13"), - ).finish(); + ) + .finish(); assert_eq!( StatusCode::SWITCHING_PROTOCOLS, handshake_response(&req).finish().status() diff --git a/src/ws/service.rs b/src/ws/service.rs index 118a2244a..846278147 100644 --- a/src/ws/service.rs +++ b/src/ws/service.rs @@ -5,8 +5,8 @@ use actix_net::service::{NewService, Service}; use futures::future::{ok, FutureResult}; use futures::{Async, IntoFuture, Poll}; -use h1::Codec; -use request::Request; +use crate::h1::Codec; +use crate::request::Request; use super::{verify_handshake, HandshakeError}; diff --git a/tests/test_client.rs b/tests/test_client.rs index 4a4ccb7dd..3e14cd7b7 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -91,7 +91,8 @@ fn test_with_query_parameter() { } else { ok::<_, ()>(Response::BadRequest().finish()) } - }).map(|_| ()) + }) + .map(|_| ()) }); let mut connector = srv.new_connector(); diff --git a/tests/test_server.rs b/tests/test_server.rs index 300b38a80..d169e5a98 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -209,7 +209,8 @@ fn test_content_length() { StatusCode::NOT_FOUND, ]; future::ok::<_, ()>(Response::new(statuses[indx])) - }).map(|_| ()) + }) + .map(|_| ()) }); let header = HeaderName::from_static("content-length"); @@ -348,7 +349,8 @@ fn test_head_binary() { let mut srv = test::TestServer::with_factory(|| { h1::H1Service::new(|_| { ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) - }).map(|_| ()) + }) + .map(|_| ()) }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); @@ -396,7 +398,8 @@ fn test_body_length() { Response::Ok() .body(Body::from_message(body::SizedStream::new(STR.len(), body))), ) - }).map(|_| ()) + }) + .map(|_| ()) }); let req = srv.get().finish().unwrap(); @@ -414,7 +417,8 @@ fn test_body_chunked_explicit() { h1::H1Service::new(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) - }).map(|_| ()) + }) + .map(|_| ()) }); let req = srv.get().finish().unwrap(); @@ -434,7 +438,8 @@ fn test_body_chunked_implicit() { h1::H1Service::new(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) - }).map(|_| ()) + }) + .map(|_| ()) }); let req = srv.get().finish().unwrap(); @@ -456,7 +461,8 @@ fn test_response_http_error_handling() { .header(http::header::CONTENT_TYPE, broken_header) .body(STR), ) - }).map(|_| ()) + }) + .map(|_| ()) }); let req = srv.get().finish().unwrap(); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 22ce3ca29..f890c586e 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -62,7 +62,8 @@ fn test_simple() { SendResponse::send( framed, ws::handshake_response(&req).finish(), - ).map_err(|_| ()) + ) + .map_err(|_| ()) .and_then(|framed| { // start websocket service let framed = framed.into_framed(ws::Codec::new()); From 9f4d48f7a1b28f23537b043f17d48bdcb1a2f10b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 6 Dec 2018 15:03:01 -0800 Subject: [PATCH 101/208] update tests --- src/h1/codec.rs | 8 ++++---- src/h1/decoder.rs | 6 +++--- src/httpcodes.rs | 4 ++-- src/httpmessage.rs | 3 ++- src/json.rs | 3 ++- src/response.rs | 6 +++--- src/ws/mod.rs | 2 +- 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 54c1ce2e6..d67d36085 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -197,10 +197,10 @@ mod tests { use tokio_io::{AsyncRead, AsyncWrite}; use super::*; - use error::ParseError; - use h1::Message; - use httpmessage::HttpMessage; - use request::Request; + use crate::error::ParseError; + use crate::h1::Message; + use crate::httpmessage::HttpMessage; + use crate::request::Request; #[test] fn test_http_request_chunked_payload_and_next_message() { diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 26b28440b..e5971f750 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -612,9 +612,9 @@ mod tests { use tokio_io::{AsyncRead, AsyncWrite}; use super::*; - use error::ParseError; - use httpmessage::HttpMessage; - use message::Head; + use crate::error::ParseError; + use crate::httpmessage::HttpMessage; + use crate::message::Head; impl PayloadType { fn unwrap(self) -> PayloadDecoder { diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 80722734a..7806bd806 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -73,9 +73,9 @@ impl Response { #[cfg(test)] mod tests { - use body::Body; + use crate::body::Body; + use crate::response::Response; use http::StatusCode; - use response::Response; #[test] fn test_build() { diff --git a/src/httpmessage.rs b/src/httpmessage.rs index e239a7337..373b7ed45 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -568,11 +568,12 @@ where #[cfg(test)] mod tests { use super::*; + use crate::test::TestRequest; use encoding::all::ISO_8859_2; use encoding::Encoding; use futures::Async; use mime; - use test::TestRequest; + use serde_derive::Deserialize; #[test] fn test_content_type() { diff --git a/src/json.rs b/src/json.rs index 0b6ac377f..bfecf0cc3 100644 --- a/src/json.rs +++ b/src/json.rs @@ -137,8 +137,9 @@ mod tests { use bytes::Bytes; use futures::Async; use http::header; + use serde_derive::{Deserialize, Serialize}; - use test::TestRequest; + use crate::test::TestRequest; impl PartialEq for JsonPayloadError { fn eq(&self, other: &JsonPayloadError) -> bool { diff --git a/src/response.rs b/src/response.rs index e506cd163..5cff612f7 100644 --- a/src/response.rs +++ b/src/response.rs @@ -855,9 +855,9 @@ impl ResponsePool { #[cfg(test)] mod tests { use super::*; - use body::Body; - use http; - use http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; + use crate::body::Body; + use crate::http; + use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; // use test::TestRequest; diff --git a/src/ws/mod.rs b/src/ws/mod.rs index ccd9ef4ac..02667c964 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -194,8 +194,8 @@ pub fn handshake_response(req: &Request) -> ResponseBuilder { #[cfg(test)] mod tests { use super::*; + use crate::test::TestRequest; use http::{header, Method}; - use test::TestRequest; #[test] fn test_handshake() { From aaae368ed9e59d41ec03a7025bb95fc419e89566 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 10 Dec 2018 18:08:33 -0800 Subject: [PATCH 102/208] use new actix crates --- Cargo.toml | 56 ++++++++++++--------------- examples/echo.rs | 17 ++------- examples/echo2.rs | 16 ++------ examples/framed_hello.rs | 21 +++------- examples/hello-world.rs | 16 ++------ src/client/connect.rs | 3 +- src/client/connection.rs | 2 +- src/client/connector.rs | 13 +++---- src/client/error.rs | 32 ++++++++++++---- src/client/pipeline.rs | 5 +-- src/client/pool.rs | 6 +-- src/client/request.rs | 36 +++++++++--------- src/config.rs | 6 +-- src/error.rs | 6 +-- src/h1/client.rs | 2 +- src/h1/codec.rs | 4 +- src/h1/decoder.rs | 4 +- src/h1/dispatcher.rs | 5 +-- src/h1/mod.rs | 2 +- src/h1/service.rs | 5 +-- src/payload.rs | 2 +- src/service.rs | 5 +-- src/test.rs | 16 ++++---- src/ws/client/error.rs | 2 +- src/ws/client/service.rs | 7 ++-- src/ws/codec.rs | 2 +- src/ws/service.rs | 4 +- src/ws/transport.rs | 7 ++-- tests/test_client.rs | 9 +---- tests/test_server.rs | 8 +--- tests/test_ws.rs | 82 +++++++++++++++++++--------------------- 31 files changed, 174 insertions(+), 227 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 57ed3f9b7..5afeda941 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,61 +28,55 @@ name = "actix_http" path = "src/lib.rs" [features] -default = ["session", "cell"] +default = ["session"] # sessions feature, session require "ring" crate and c compiler session = ["cookie/secure"] -cell = ["actix-net/cell"] - -# tls -tls = ["native-tls", "actix-net/tls"] - # openssl -ssl = ["openssl", "actix-net/ssl"] - -# rustls -rust-tls = ["rustls", "actix-net/rust-tls"] +ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix = "0.7.5" -#actix-net = "0.3.0" -actix-net = { git="https://github.com/actix/actix-net.git" } +actix-service = "0.1.1" +actix-codec = { git="https://github.com/actix/actix-net.git" } +actix-connector = { git="https://github.com/actix/actix-net.git" } +actix-rt = { git="https://github.com/actix/actix-net.git" } +actix-server = { git="https://github.com/actix/actix-net.git" } +actix-utils = { git="https://github.com/actix/actix-net.git" } + +# actix-codec = { path="../actix-net/actix-codec/" } +# actix-connector = { path="../actix-net/actix-connector/" } +# actix-rt = { path="../actix-net/actix-rt/" } +# actix-server = { path="../actix-net/actix-server/" } +# actix-utils = { path="../actix-net/actix-utils/" } base64 = "0.9" bitflags = "1.0" +bytes = "0.4" +byteorder = "1.2" +cookie = { version="0.11", features=["percent-encode"] } +encoding = "0.2" +failure = "0.1.3" +futures = "0.1" http = "0.1.8" httparse = "1.3" -failure = "0.1.3" indexmap = "1.0" log = "0.4" mime = "0.3" +net2 = "0.2" +percent-encoding = "1.0" rand = "0.5" serde = "1.0" serde_json = "1.0" sha1 = "0.6" -time = "0.1" -encoding = "0.2" -serde_urlencoded = "0.5.3" - -cookie = { version="0.11", features=["percent-encode"] } -percent-encoding = "1.0" -url = { version="1.7", features=["query_encoding"] } - -# io -net2 = "0.2" slab = "0.4" -bytes = "0.4" -byteorder = "1.2" -futures = "0.1" -tokio-codec = "0.1" -tokio = "0.1" -tokio-io = "0.1" +serde_urlencoded = "0.5.3" +time = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" -tokio-current-thread = "0.1" trust-dns-proto = "0.5.0" trust-dns-resolver = "0.10.0" +url = { version="1.7", features=["query_encoding"] } # native-tls native-tls = { version="0.2", optional = true } diff --git a/examples/echo.rs b/examples/echo.rs index 0453ad6a7..3bfb04d7c 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -1,27 +1,18 @@ -#[macro_use] -extern crate log; -extern crate env_logger; - -extern crate actix_http; -extern crate actix_net; -extern crate bytes; -extern crate futures; -extern crate http; - use actix_http::HttpMessage; use actix_http::{h1, Request, Response}; -use actix_net::server::Server; -use actix_net::service::NewServiceExt; +use actix_server::Server; +use actix_service::NewService; use bytes::Bytes; use futures::Future; use http::header::HeaderValue; +use log::info; use std::env; fn main() { env::set_var("RUST_LOG", "echo=info"); env_logger::init(); - Server::new() + Server::build() .bind("echo", "127.0.0.1:8080", || { h1::H1Service::build() .client_timeout(1000) diff --git a/examples/echo2.rs b/examples/echo2.rs index 3206ff507..0e2bc9d52 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -1,19 +1,11 @@ -#[macro_use] -extern crate log; -extern crate env_logger; - -extern crate actix_http; -extern crate actix_net; -extern crate bytes; -extern crate futures; - use actix_http::http::HeaderValue; use actix_http::HttpMessage; use actix_http::{h1, Error, Request, Response}; -use actix_net::server::Server; -use actix_net::service::NewServiceExt; +use actix_server::Server; +use actix_service::NewService; use bytes::Bytes; use futures::Future; +use log::info; use std::env; fn handle_request(_req: Request) -> impl Future { @@ -29,7 +21,7 @@ fn main() { env::set_var("RUST_LOG", "echo=info"); env_logger::init(); - Server::new() + Server::build() .bind("echo", "127.0.0.1:8080", || { h1::H1Service::build() .client_timeout(1000) diff --git a/examples/framed_hello.rs b/examples/framed_hello.rs index 6c53d27f3..5bbc3be9b 100644 --- a/examples/framed_hello.rs +++ b/examples/framed_hello.rs @@ -1,18 +1,9 @@ -extern crate env_logger; -extern crate log; - -extern crate actix_http; -extern crate actix_net; -extern crate bytes; -extern crate futures; -extern crate http; - +use actix_codec::Framed; use actix_http::{h1, Response, SendResponse, ServiceConfig}; -use actix_net::codec::Framed; -use actix_net::framed::IntoFramed; -use actix_net::server::Server; -use actix_net::service::NewServiceExt; -use actix_net::stream::TakeItem; +use actix_server::Server; +use actix_service::NewService; +use actix_utils::framed::IntoFramed; +use actix_utils::stream::TakeItem; use futures::Future; use std::env; @@ -20,7 +11,7 @@ fn main() { env::set_var("RUST_LOG", "framed_hello=info"); env_logger::init(); - Server::new() + Server::build() .bind("framed_hello", "127.0.0.1:8080", || { IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) .and_then(TakeItem::new().map_err(|_| ())) diff --git a/examples/hello-world.rs b/examples/hello-world.rs index b477f191d..e0c322a22 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -1,24 +1,16 @@ -#[macro_use] -extern crate log; -extern crate env_logger; - -extern crate actix_http; -extern crate actix_net; -extern crate futures; -extern crate http; - use actix_http::{h1, Response}; -use actix_net::server::Server; -use actix_net::service::NewServiceExt; +use actix_server::Server; +use actix_service::NewService; use futures::future; use http::header::HeaderValue; +use log::info; use std::env; fn main() { env::set_var("RUST_LOG", "hello_world=info"); env_logger::init(); - Server::new() + Server::build() .bind("hello-world", "127.0.0.1:8080", || { h1::H1Service::build() .client_timeout(1000) diff --git a/src/client/connect.rs b/src/client/connect.rs index a445228e3..f4112cfaa 100644 --- a/src/client/connect.rs +++ b/src/client/connect.rs @@ -1,5 +1,4 @@ -use actix_net::connector::RequestPort; -use actix_net::resolver::RequestHost; +use actix_connector::{RequestHost, RequestPort}; use http::uri::Uri; use http::{Error as HttpError, HttpTryFrom}; diff --git a/src/client/connection.rs b/src/client/connection.rs index 363a4ece9..ed156bf84 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -1,7 +1,7 @@ use std::{fmt, io, time}; +use actix_codec::{AsyncRead, AsyncWrite}; use futures::Poll; -use tokio_io::{AsyncRead, AsyncWrite}; use super::pool::Acquired; diff --git a/src/client/connector.rs b/src/client/connector.rs index 74ee6a7e3..fdc996ffe 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1,13 +1,12 @@ use std::time::Duration; use std::{fmt, io}; -use actix_net::connector::TcpConnector; -use actix_net::resolver::Resolver; -use actix_net::service::{Service, ServiceExt}; -use actix_net::timeout::{TimeoutError, TimeoutService}; +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_connector::{Resolver, TcpConnector}; +use actix_service::Service; +use actix_utils::timeout::{TimeoutError, TimeoutService}; use futures::future::Either; use futures::Poll; -use tokio_io::{AsyncRead, AsyncWrite}; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; use super::connect::Connect; @@ -16,7 +15,7 @@ use super::error::ConnectorError; use super::pool::ConnectionPool; #[cfg(feature = "ssl")] -use actix_net::ssl::OpensslConnector; +use actix_connector::ssl::OpensslConnector; #[cfg(feature = "ssl")] use openssl::ssl::{SslConnector, SslMethod}; @@ -169,7 +168,7 @@ impl Connector { .and_then(TcpConnector::default().from_err()) .and_then( OpensslConnector::service(self.connector) - .map_err(ConnectorError::SslError), + .map_err(ConnectorError::from), ), ) .map_err(|e| match e { diff --git a/src/client/error.rs b/src/client/error.rs index d2a0f38ec..815bc1edc 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -4,13 +4,7 @@ use failure::Fail; use trust_dns_resolver::error::ResolveError; #[cfg(feature = "ssl")] -use openssl::ssl::Error as SslError; - -#[cfg(all(feature = "tls", not(any(feature = "ssl", feature = "rust-tls"))))] -use native_tls::Error as SslError; - -#[cfg(all(feature = "rust-tls", not(any(feature = "tls", feature = "ssl"))))] -use std::io::Error as SslError; +use openssl::ssl::{Error as SslError, HandshakeError}; use crate::error::{Error, ParseError}; @@ -26,7 +20,7 @@ pub enum ConnectorError { SslIsNotSupported, /// SSL error - #[cfg(any(feature = "tls", feature = "ssl", feature = "rust-tls"))] + #[cfg(feature = "ssl")] #[fail(display = "{}", _0)] SslError(#[cause] SslError), @@ -73,6 +67,28 @@ impl From for ConnectorError { } } +#[cfg(feature = "ssl")] +impl From for ConnectorError { + fn from(err: SslError) -> ConnectorError { + ConnectorError::SslError(err) + } +} + +#[cfg(feature = "ssl")] +impl From> for ConnectorError { + fn from(err: HandshakeError) -> ConnectorError { + match err { + HandshakeError::SetupFailure(stack) => SslError::from(stack).into(), + HandshakeError::Failure(stream) => { + SslError::from(stream.into_error()).into() + } + HandshakeError::WouldBlock(stream) => { + SslError::from(stream.into_error()).into() + } + } + } +} + /// A set of errors that can occur during request sending and response reading #[derive(Debug)] pub enum SendRequestError { diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index fc1e53e8f..8d946d644 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -1,11 +1,10 @@ use std::collections::VecDeque; -use actix_net::codec::Framed; -use actix_net::service::Service; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_service::Service; use bytes::Bytes; use futures::future::{err, ok, Either}; use futures::{Async, Future, Poll, Sink, Stream}; -use tokio_io::{AsyncRead, AsyncWrite}; use super::error::{ConnectorError, SendRequestError}; use super::response::ClientResponse; diff --git a/src/client/pool.rs b/src/client/pool.rs index decf80194..94e96899e 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -4,7 +4,9 @@ use std::io; use std::rc::Rc; use std::time::{Duration, Instant}; -use actix_net::service::Service; +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_rt::spawn; +use actix_service::Service; use futures::future::{ok, Either, FutureResult}; use futures::sync::oneshot; use futures::task::AtomicTask; @@ -12,8 +14,6 @@ use futures::{Async, Future, Poll}; use http::uri::Authority; use indexmap::IndexSet; use slab::Slab; -use tokio_current_thread::spawn; -use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::{sleep, Delay}; use super::connect::Connect; diff --git a/src/client/request.rs b/src/client/request.rs index 5f294bbd8..fbb1e840b 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -2,7 +2,7 @@ use std::fmt; use std::fmt::Write as FmtWrite; use std::io::Write; -use actix_net::service::Service; +use actix_service::Service; use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; use futures::{Future, Stream}; @@ -23,26 +23,24 @@ use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; /// An HTTP Client Request /// /// ```rust -/// # extern crate actix_web; -/// # extern crate futures; -/// # extern crate tokio; -/// # use futures::Future; -/// # use std::process; -/// use actix_web::{actix, client}; +/// use futures::future::{Future, lazy}; +/// use actix_rt::System; +/// use actix_http::client; /// /// fn main() { -/// actix::run( -/// || client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") -/// .finish().unwrap() -/// .send() // <- Send http request -/// .map_err(|_| ()) -/// .and_then(|response| { // <- server http response -/// println!("Response: {:?}", response); -/// # actix::System::current().stop(); -/// Ok(()) -/// }), -/// ); +/// System::new("test").block_on(lazy(|| { +/// let mut connector = client::Connector::default().service(); +/// client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder +/// .header("User-Agent", "Actix-web") +/// .finish().unwrap() +/// .send(&mut connector) // <- Send http request +/// .map_err(|_| ()) +/// .and_then(|response| { // <- server http response +/// println!("Response: {:?}", response); +/// # actix_rt::System::current().stop(); +/// Ok(()) +/// }) +/// })); /// } /// ``` pub struct ClientRequest { diff --git a/src/config.rs b/src/config.rs index 661c0901f..67c928fb7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,11 +4,11 @@ use std::rc::Rc; use std::time::{Duration, Instant}; use std::{fmt, net}; +use actix_rt::spawn; use bytes::BytesMut; use futures::{future, Future}; use log::error; use time; -use tokio_current_thread::spawn; use tokio_timer::{sleep, Delay}; // "Sun, 06 Nov 1994 08:49:37 GMT".len() @@ -378,8 +378,8 @@ impl DateService { #[cfg(test)] mod tests { use super::*; + use actix_rt::System; use futures::future; - use tokio::runtime::current_thread; #[test] fn test_date_len() { @@ -388,7 +388,7 @@ mod tests { #[test] fn test_date() { - let mut rt = current_thread::Runtime::new().unwrap(); + let mut rt = System::new("test"); let _ = rt.block_on(future::lazy(|| { let settings = ServiceConfig::new(KeepAlive::Os, 0, 0); diff --git a/src/error.rs b/src/error.rs index 280f9a329..49602bd98 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,7 +5,7 @@ use std::string::FromUtf8Error; use std::sync::Mutex; use std::{fmt, io, result}; -use actix::MailboxError; +// use actix::MailboxError; use cookie; use failure::{self, Backtrace, Fail}; use futures::Canceled; @@ -250,8 +250,8 @@ impl ResponseError for header::InvalidHeaderValueBytes { /// `InternalServerError` for `futures::Canceled` impl ResponseError for Canceled {} -/// `InternalServerError` for `actix::MailboxError` -impl ResponseError for MailboxError {} +// /// `InternalServerError` for `actix::MailboxError` +// impl ResponseError for MailboxError {} /// A set of errors that can occur during parsing HTTP streams #[derive(Fail, Debug)] diff --git a/src/h1/client.rs b/src/h1/client.rs index f547983fa..de4d10e1b 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -1,13 +1,13 @@ #![allow(unused_imports, unused_variables, dead_code)] use std::io::{self, Write}; +use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; use bytes::{BufMut, Bytes, BytesMut}; use http::header::{ HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, }; use http::{Method, Version}; -use tokio_codec::{Decoder, Encoder}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; use super::{decoder, encoder}; diff --git a/src/h1/codec.rs b/src/h1/codec.rs index d67d36085..fbc8b4a58 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -2,11 +2,11 @@ use std::fmt; use std::io::{self, Write}; +use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; use bytes::{BufMut, Bytes, BytesMut}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{Method, StatusCode, Version}; -use tokio_codec::{Decoder, Encoder}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; use super::{decoder, encoder}; @@ -192,9 +192,9 @@ impl Encoder for Codec { mod tests { use std::{cmp, io}; + use actix_codec::{AsyncRead, AsyncWrite}; use bytes::{Buf, Bytes, BytesMut}; use http::{Method, Version}; - use tokio_io::{AsyncRead, AsyncWrite}; use super::*; use crate::error::ParseError; diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index e5971f750..460d2b3aa 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -1,13 +1,13 @@ use std::marker::PhantomData; use std::{io, mem}; +use actix_codec::Decoder; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; use http::header::{HeaderName, HeaderValue}; use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version}; use httparse; use log::{debug, error, trace}; -use tokio_codec::Decoder; use crate::client::ClientResponse; use crate::error::ParseError; @@ -607,9 +607,9 @@ impl ChunkedState { mod tests { use std::{cmp, io}; + use actix_codec::{AsyncRead, AsyncWrite}; use bytes::{Buf, Bytes, BytesMut}; use http::{Method, Version}; - use tokio_io::{AsyncRead, AsyncWrite}; use super::*; use crate::error::ParseError; diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 59b419c3e..569b1deeb 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -3,12 +3,11 @@ use std::fmt::Debug; use std::mem; use std::time::Instant; -use actix_net::codec::Framed; -use actix_net::service::Service; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_service::Service; use bitflags::bitflags; use futures::{try_ready, Async, Future, Poll, Sink, Stream}; use log::{debug, error, trace}; -use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; use crate::body::{Body, BodyLength, MessageBody, ResponseBody}; diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 461ecb959..a375b60d3 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -1,7 +1,7 @@ //! HTTP/1 implementation use std::fmt; -use actix_net::codec::Framed; +use actix_codec::Framed; use bytes::Bytes; mod client; diff --git a/src/h1/service.rs b/src/h1/service.rs index 1a5a587c0..7c2589cc8 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -2,12 +2,11 @@ use std::fmt::Debug; use std::marker::PhantomData; use std::net; -use actix_net::codec::Framed; -use actix_net::service::{IntoNewService, NewService, Service}; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_service::{IntoNewService, NewService, Service}; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, Poll, Stream}; use log::error; -use tokio_io::{AsyncRead, AsyncWrite}; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; diff --git a/src/payload.rs b/src/payload.rs index 37f06d433..ea266f70b 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -523,8 +523,8 @@ where #[cfg(test)] mod tests { use super::*; + use actix_rt::Runtime; use futures::future::{lazy, result}; - use tokio::runtime::current_thread::Runtime; #[test] fn test_error() { diff --git a/src/service.rs b/src/service.rs index a6a820977..f98234e76 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,10 +1,9 @@ use std::marker::PhantomData; -use actix_net::codec::Framed; -use actix_net::service::{NewService, Service}; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_service::{NewService, Service}; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll, Sink}; -use tokio_io::{AsyncRead, AsyncWrite}; use crate::body::{BodyLength, MessageBody, ResponseBody}; use crate::error::{Error, ResponseError}; diff --git a/src/test.rs b/src/test.rs index 308d2b4df..c264ac47a 100644 --- a/src/test.rs +++ b/src/test.rs @@ -3,10 +3,10 @@ use std::str::FromStr; use std::sync::mpsc; use std::{net, thread}; -use actix::System; -use actix_net::codec::Framed; -use actix_net::server::{Server, StreamServiceFactory}; -use actix_net::service::Service; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_rt::{Runtime, System}; +use actix_server::{Server, StreamServiceFactory}; +use actix_service::Service; use bytes::Bytes; use cookie::Cookie; @@ -14,8 +14,6 @@ use futures::future::{lazy, Future}; use http::header::HeaderName; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; use net2::TcpBuilder; -use tokio::runtime::current_thread::Runtime; -use tokio_io::{AsyncRead, AsyncWrite}; use crate::body::MessageBody; use crate::client::{ @@ -316,7 +314,7 @@ impl TestServer { let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = tcp.local_addr().unwrap(); - Server::default() + Server::build() .listen("test", tcp, factory) .workers(1) .disable_signals() @@ -390,9 +388,9 @@ impl TestServerRuntime { /// Construct test server url pub fn url(&self, uri: &str) -> String { if uri.starts_with('/') { - format!("http://localhost:{}{}", self.addr.port(), uri) + format!("http://127.0.0.1:{}{}", self.addr.port(), uri) } else { - format!("http://localhost:{}/{}", self.addr.port(), uri) + format!("http://127.0.0.1:{}/{}", self.addr.port(), uri) } } diff --git a/src/ws/client/error.rs b/src/ws/client/error.rs index 62a3f47a9..02c723755 100644 --- a/src/ws/client/error.rs +++ b/src/ws/client/error.rs @@ -1,7 +1,7 @@ //! Http client request use std::io; -use actix_net::connector::ConnectorError; +use actix_connector::ConnectorError; use failure::Fail; use http::{header::HeaderValue, Error as HttpError, StatusCode}; diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 8dd407b0e..c48b6e0c1 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -1,9 +1,9 @@ //! websockets client use std::marker::PhantomData; -use actix_net::codec::Framed; -use actix_net::connector::{Connect as TcpConnect, ConnectorError, DefaultConnector}; -use actix_net::service::Service; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_connector::{Connect as TcpConnect, ConnectorError, DefaultConnector}; +use actix_service::Service; use base64; use futures::future::{err, Either, FutureResult}; use futures::{try_ready, Async, Future, Poll, Sink, Stream}; @@ -12,7 +12,6 @@ use http::{HttpTryFrom, StatusCode}; use log::trace; use rand; use sha1::Sha1; -use tokio_io::{AsyncRead, AsyncWrite}; use crate::body::BodyLength; use crate::client::ClientResponse; diff --git a/src/ws/codec.rs b/src/ws/codec.rs index 10c505287..286d15f8c 100644 --- a/src/ws/codec.rs +++ b/src/ws/codec.rs @@ -1,5 +1,5 @@ +use actix_codec::{Decoder, Encoder}; use bytes::{Bytes, BytesMut}; -use tokio_codec::{Decoder, Encoder}; use super::frame::Parser; use super::proto::{CloseReason, OpCode}; diff --git a/src/ws/service.rs b/src/ws/service.rs index 846278147..8189b1955 100644 --- a/src/ws/service.rs +++ b/src/ws/service.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; -use actix_net::codec::Framed; -use actix_net::service::{NewService, Service}; +use actix_codec::Framed; +use actix_service::{NewService, Service}; use futures::future::{ok, FutureResult}; use futures::{Async, IntoFuture, Poll}; diff --git a/src/ws/transport.rs b/src/ws/transport.rs index 8cd79cb03..f59ad67a7 100644 --- a/src/ws/transport.rs +++ b/src/ws/transport.rs @@ -1,8 +1,7 @@ -use actix_net::codec::Framed; -use actix_net::framed::{FramedTransport, FramedTransportError}; -use actix_net::service::{IntoService, Service}; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_service::{IntoService, Service}; +use actix_utils::framed::{FramedTransport, FramedTransportError}; use futures::{Future, Poll}; -use tokio_io::{AsyncRead, AsyncWrite}; use super::{Codec, Frame, Message}; diff --git a/tests/test_client.rs b/tests/test_client.rs index 3e14cd7b7..f19edda13 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -1,10 +1,4 @@ -extern crate actix; -extern crate actix_http; -extern crate actix_net; -extern crate bytes; -extern crate futures; - -use actix_net::service::NewServiceExt; +use actix_service::NewService; use bytes::Bytes; use futures::future::{self, ok}; @@ -35,6 +29,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_h1_v2() { + env_logger::init(); let mut srv = TestServer::with_factory(move || { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) diff --git a/tests/test_server.rs b/tests/test_server.rs index d169e5a98..cb9cd3f9d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,14 +1,8 @@ -extern crate actix; -extern crate actix_http; -extern crate actix_net; -extern crate bytes; -extern crate futures; - use std::io::{Read, Write}; use std::time::Duration; use std::{net, thread}; -use actix_net::service::NewServiceExt; +use actix_service::NewService; use bytes::Bytes; use futures::future::{self, ok}; use futures::stream::once; diff --git a/tests/test_ws.rs b/tests/test_ws.rs index f890c586e..11a3f472b 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -1,16 +1,9 @@ -extern crate actix; -extern crate actix_http; -extern crate actix_net; -extern crate actix_web; -extern crate bytes; -extern crate futures; - use std::io; -use actix_net::codec::Framed; -use actix_net::framed::IntoFramed; -use actix_net::service::NewServiceExt; -use actix_net::stream::TakeItem; +use actix_codec::Framed; +use actix_service::NewService; +use actix_utils::framed::IntoFramed; +use actix_utils::stream::TakeItem; use actix_web::ws as web_ws; use bytes::{Bytes, BytesMut}; use futures::future::{lazy, ok, Either}; @@ -79,38 +72,6 @@ fn test_simple() { }) }); - { - let url = srv.url("/"); - - let (reader, mut writer) = srv - .block_on(lazy(|| web_ws::Client::new(url).connect())) - .unwrap(); - - writer.text("text"); - let (item, reader) = srv.block_on(reader.into_future()).unwrap(); - assert_eq!(item, Some(web_ws::Message::Text("text".to_owned()))); - - writer.binary(b"text".as_ref()); - let (item, reader) = srv.block_on(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(web_ws::Message::Binary(Bytes::from_static(b"text").into())) - ); - - writer.ping("ping"); - let (item, reader) = srv.block_on(reader.into_future()).unwrap(); - assert_eq!(item, Some(web_ws::Message::Pong("ping".to_owned()))); - - writer.close(Some(web_ws::CloseCode::Normal.into())); - let (item, _) = srv.block_on(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(web_ws::Message::Close(Some( - web_ws::CloseCode::Normal.into() - ))) - ); - } - // client service let framed = srv.ws().unwrap(); let framed = srv @@ -142,5 +103,38 @@ fn test_simple() { assert_eq!( item, Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) - ) + ); + + { + let mut sys = actix_web::actix::System::new("test"); + let url = srv.url("/"); + + let (reader, mut writer) = sys + .block_on(lazy(|| web_ws::Client::new(url).connect())) + .unwrap(); + + writer.text("text"); + let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + assert_eq!(item, Some(web_ws::Message::Text("text".to_owned()))); + + writer.binary(b"text".as_ref()); + let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + assert_eq!( + item, + Some(web_ws::Message::Binary(Bytes::from_static(b"text").into())) + ); + + writer.ping("ping"); + let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + assert_eq!(item, Some(web_ws::Message::Pong("ping".to_owned()))); + + writer.close(Some(web_ws::CloseCode::Normal.into())); + let (item, _) = sys.block_on(reader.into_future()).unwrap(); + assert_eq!( + item, + Some(web_ws::Message::Close(Some( + web_ws::CloseCode::Normal.into() + ))) + ); + } } From 1c60992723eff00f05832b499ed9b0fbaa954c80 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 11 Dec 2018 09:29:12 -0800 Subject: [PATCH 103/208] use released crates --- Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5afeda941..0c6d53f50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,11 +38,11 @@ ssl = ["openssl", "actix-connector/ssl"] [dependencies] actix-service = "0.1.1" -actix-codec = { git="https://github.com/actix/actix-net.git" } -actix-connector = { git="https://github.com/actix/actix-net.git" } -actix-rt = { git="https://github.com/actix/actix-net.git" } -actix-server = { git="https://github.com/actix/actix-net.git" } -actix-utils = { git="https://github.com/actix/actix-net.git" } +actix-codec = "0.1.0" +actix-connector = "0.1.0" +actix-rt = "0.1.0" +actix-server = "0.1.0" +actix-utils = "0.1.0" # actix-codec = { path="../actix-net/actix-codec/" } # actix-connector = { path="../actix-net/actix-connector/" } From b1001b80b7cf150737e830bb11f144cf09ad61a6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 12 Dec 2018 18:39:01 -0800 Subject: [PATCH 104/208] upgrade actix-service dependency --- Cargo.toml | 2 +- src/client/connector.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0c6d53f50..d992de647 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix-service = "0.1.1" +actix-service = "0.1.3" actix-codec = "0.1.0" actix-connector = "0.1.0" actix-rt = "0.1.0" diff --git a/src/client/connector.rs b/src/client/connector.rs index fdc996ffe..05caf1ed2 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -3,7 +3,7 @@ use std::{fmt, io}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_connector::{Resolver, TcpConnector}; -use actix_service::Service; +use actix_service::{Service, ServiceExt}; use actix_utils::timeout::{TimeoutError, TimeoutService}; use futures::future::Either; use futures::Poll; From 67df9399df8faa7983e4831d3de2abab37ef49dd Mon Sep 17 00:00:00 2001 From: Douman Date: Sun, 16 Dec 2018 18:43:11 +0300 Subject: [PATCH 105/208] H1 decoder should ignore headers case --- src/h1/decoder.rs | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 460d2b3aa..b2c410c47 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -94,20 +94,20 @@ pub(crate) trait MessageType: Sized { } // transfer-encoding header::TRANSFER_ENCODING => { - if let Ok(s) = value.to_str() { - chunked = s.to_lowercase().contains("chunked"); + if let Ok(s) = value.to_str().map(|s| s.trim()) { + chunked = s.eq_ignore_ascii_case("chunked"); } else { return Err(ParseError::Header); } } // connection keep-alive state header::CONNECTION => { - ka = if let Ok(conn) = value.to_str() { - if conn.contains("keep-alive") { + ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim()) { + if conn.eq_ignore_ascii_case("keep-alive") { Some(ConnectionType::KeepAlive) - } else if conn.contains("close") { + } else if conn.eq_ignore_ascii_case("close") { Some(ConnectionType::Close) - } else if conn.contains("upgrade") { + } else if conn.eq_ignore_ascii_case("upgrade") { Some(ConnectionType::Upgrade) } else { None @@ -120,8 +120,8 @@ pub(crate) trait MessageType: Sized { has_upgrade = true; // check content-length, some clients (dart) // sends "content-length: 0" with websocket upgrade - if let Ok(val) = value.to_str() { - if val == "websocket" { + if let Ok(val) = value.to_str().map(|val| val.trim()) { + if val.eq_ignore_ascii_case("websocket") { content_length = None; } } @@ -887,6 +887,14 @@ mod tests { let req = parse_ready!(&mut buf); assert_eq!(req.inner().head.ctype, Some(ConnectionType::Close)); + + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + connection: Close\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert_eq!(req.inner().head.ctype, Some(ConnectionType::Close)); } #[test] @@ -909,6 +917,15 @@ mod tests { let req = parse_ready!(&mut buf); assert_eq!(req.inner().head.ctype, Some(ConnectionType::KeepAlive)); + + let mut buf = BytesMut::from( + "GET /test HTTP/1.0\r\n\ + connection: Keep-Alive\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert_eq!(req.inner().head.ctype, Some(ConnectionType::KeepAlive)); + } #[test] @@ -959,6 +976,17 @@ mod tests { assert!(req.upgrade()); assert_eq!(req.inner().head.ctype, Some(ConnectionType::Upgrade)); + + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + upgrade: Websockets\r\n\ + connection: Upgrade\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(req.upgrade()); + assert_eq!(req.inner().head.ctype, Some(ConnectionType::Upgrade)); + } #[test] From cc74435b0155b11c7405e5b9b3288721c9b5edcd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 19 Dec 2018 18:34:56 -0800 Subject: [PATCH 106/208] drop failure crate --- Cargo.toml | 13 +- src/client/error.rs | 79 ++------ src/error.rs | 401 ++++++++++++++++++----------------------- src/response.rs | 33 ---- src/ws/client/error.rs | 68 ++----- src/ws/mod.rs | 44 ++--- 6 files changed, 235 insertions(+), 403 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d992de647..a5fc597a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,12 +51,13 @@ actix-utils = "0.1.0" # actix-utils = { path="../actix-net/actix-utils/" } base64 = "0.9" +backtrace = "0.3" bitflags = "1.0" bytes = "0.4" byteorder = "1.2" cookie = { version="0.11", features=["percent-encode"] } +derive_more = "0.13" encoding = "0.2" -failure = "0.1.3" futures = "0.1" http = "0.1.8" httparse = "1.3" @@ -74,19 +75,11 @@ serde_urlencoded = "0.5.3" time = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" -trust-dns-proto = "0.5.0" -trust-dns-resolver = "0.10.0" -url = { version="1.7", features=["query_encoding"] } - -# native-tls -native-tls = { version="0.2", optional = true } +trust-dns-resolver = "0.10.1" # openssl openssl = { version="0.10", optional = true } -# rustls -rustls = { version = "^0.14", optional = true } - [dev-dependencies] actix-web = "0.7" env_logger = "0.5" diff --git a/src/client/error.rs b/src/client/error.rs index 815bc1edc..2a5df9c97 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -1,6 +1,6 @@ use std::io; -use failure::Fail; +use derive_more::{Display, From}; use trust_dns_resolver::error::ResolveError; #[cfg(feature = "ssl")] @@ -9,71 +9,52 @@ use openssl::ssl::{Error as SslError, HandshakeError}; use crate::error::{Error, ParseError}; /// A set of errors that can occur while connecting to an HTTP host -#[derive(Fail, Debug)] +#[derive(Debug, Display, From)] pub enum ConnectorError { /// Invalid URL - #[fail(display = "Invalid URL")] + #[display(fmt = "Invalid URL")] InvalidUrl(InvalidUrlKind), /// SSL feature is not enabled - #[fail(display = "SSL is not supported")] + #[display(fmt = "SSL is not supported")] SslIsNotSupported, /// SSL error #[cfg(feature = "ssl")] - #[fail(display = "{}", _0)] - SslError(#[cause] SslError), + #[display(fmt = "{}", _0)] + SslError(SslError), /// Failed to resolve the hostname - #[fail(display = "Failed resolving hostname: {}", _0)] + #[display(fmt = "Failed resolving hostname: {}", _0)] Resolver(ResolveError), /// No dns records - #[fail(display = "No dns records found for the input")] + #[display(fmt = "No dns records found for the input")] NoRecords, /// Connecting took too long - #[fail(display = "Timeout out while establishing connection")] + #[display(fmt = "Timeout out while establishing connection")] Timeout, /// Connector has been disconnected - #[fail(display = "Internal error: connector has been disconnected")] + #[display(fmt = "Internal error: connector has been disconnected")] Disconnected, /// Connection io error - #[fail(display = "{}", _0)] - IoError(io::Error), + #[display(fmt = "{}", _0)] + Io(io::Error), } -#[derive(Fail, Debug)] +#[derive(Debug, Display)] pub enum InvalidUrlKind { - #[fail(display = "Missing url scheme")] + #[display(fmt = "Missing url scheme")] MissingScheme, - #[fail(display = "Unknown url scheme")] + #[display(fmt = "Unknown url scheme")] UnknownScheme, - #[fail(display = "Missing host name")] + #[display(fmt = "Missing host name")] MissingHost, } -impl From for ConnectorError { - fn from(err: io::Error) -> ConnectorError { - ConnectorError::IoError(err) - } -} - -impl From for ConnectorError { - fn from(err: ResolveError) -> ConnectorError { - ConnectorError::Resolver(err) - } -} - -#[cfg(feature = "ssl")] -impl From for ConnectorError { - fn from(err: SslError) -> ConnectorError { - ConnectorError::SslError(err) - } -} - #[cfg(feature = "ssl")] impl From> for ConnectorError { fn from(err: HandshakeError) -> ConnectorError { @@ -90,10 +71,10 @@ impl From> for ConnectorError { } /// A set of errors that can occur during request sending and response reading -#[derive(Debug)] +#[derive(Debug, Display, From)] pub enum SendRequestError { /// Failed to connect to host - // #[fail(display = "Failed to connect to host: {}", _0)] + #[display(fmt = "Failed to connect to host: {}", _0)] Connector(ConnectorError), /// Error sending request Send(io::Error), @@ -102,27 +83,3 @@ pub enum SendRequestError { /// Error sending request body Body(Error), } - -impl From for SendRequestError { - fn from(err: io::Error) -> SendRequestError { - SendRequestError::Send(err) - } -} - -impl From for SendRequestError { - fn from(err: ConnectorError) -> SendRequestError { - SendRequestError::Connector(err) - } -} - -impl From for SendRequestError { - fn from(err: ParseError) -> SendRequestError { - SendRequestError::Response(err) - } -} - -impl From for SendRequestError { - fn from(err: Error) -> SendRequestError { - SendRequestError::Body(err) - } -} diff --git a/src/error.rs b/src/error.rs index 49602bd98..8af422fc8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,13 +1,14 @@ //! Error and Result module +use std::cell::RefCell; use std::io::Error as IoError; use std::str::Utf8Error; use std::string::FromUtf8Error; -use std::sync::Mutex; use std::{fmt, io, result}; // use actix::MailboxError; +use backtrace::Backtrace; use cookie; -use failure::{self, Backtrace, Fail}; +use derive_more::{Display, From}; use futures::Canceled; use http::uri::InvalidUri; use http::{header, Error as HttpError, StatusCode}; @@ -21,7 +22,7 @@ use tokio_timer::Error as TimerError; pub use cookie::ParseError as CookieParseError; use crate::body::Body; -use crate::response::{Response, ResponseParts}; +use crate::response::Response; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// for actix web operations @@ -47,20 +48,6 @@ pub struct Error { } impl Error { - /// Deprecated way to reference the underlying response error. - #[deprecated( - since = "0.6.0", - note = "please use `Error::as_response_error()` instead" - )] - pub fn cause(&self) -> &ResponseError { - self.cause.as_ref() - } - - /// Returns a reference to the underlying cause of this `Error` as `Fail` - pub fn as_fail(&self) -> &Fail { - self.cause.as_fail() - } - /// Returns the reference to the underlying `ResponseError`. pub fn as_response_error(&self) -> &ResponseError { self.cause.as_ref() @@ -78,27 +65,27 @@ impl Error { } } - /// Attempts to downcast this `Error` to a particular `Fail` type by - /// reference. - /// - /// If the underlying error is not of type `T`, this will return `None`. - pub fn downcast_ref(&self) -> Option<&T> { - // in the most trivial way the cause is directly of the requested type. - if let Some(rv) = Fail::downcast_ref(self.cause.as_fail()) { - return Some(rv); - } + // /// Attempts to downcast this `Error` to a particular `Fail` type by + // /// reference. + // /// + // /// If the underlying error is not of type `T`, this will return `None`. + // pub fn downcast_ref(&self) -> Option<&T> { + // // in the most trivial way the cause is directly of the requested type. + // if let Some(rv) = Fail::downcast_ref(self.cause.as_fail()) { + // return Some(rv); + // } - // in the more complex case the error has been constructed from a failure - // error. This happens because we implement From by - // calling compat() and then storing it here. In failure this is - // represented by a failure::Error being wrapped in a failure::Compat. - // - // So we first downcast into that compat, to then further downcast through - // the failure's Error downcasting system into the original failure. - let compat: Option<&failure::Compat> = - Fail::downcast_ref(self.cause.as_fail()); - compat.and_then(|e| e.get_ref().downcast_ref()) - } + // // in the more complex case the error has been constructed from a failure + // // error. This happens because we implement From by + // // calling compat() and then storing it here. In failure this is + // // represented by a failure::Error being wrapped in a failure::Compat. + // // + // // So we first downcast into that compat, to then further downcast through + // // the failure's Error downcasting system into the original failure. + // let compat: Option<&failure::Compat> = + // Fail::downcast_ref(self.cause.as_fail()); + // compat.and_then(|e| e.get_ref().downcast_ref()) + // } /// Converts error to a response instance and set error message as response body pub fn response_with_message(self) -> Response { @@ -108,36 +95,41 @@ impl Error { } } -/// Helper trait to downcast a response error into a fail. -/// -/// This is currently not exposed because it's unclear if this is the best way -/// to achieve the downcasting on `Error` for which this is needed. -#[doc(hidden)] -pub trait InternalResponseErrorAsFail { - #[doc(hidden)] - fn as_fail(&self) -> &Fail; - #[doc(hidden)] - fn as_mut_fail(&mut self) -> &mut Fail; -} +// /// Helper trait to downcast a response error into a fail. +// /// +// /// This is currently not exposed because it's unclear if this is the best way +// /// to achieve the downcasting on `Error` for which this is needed. +// #[doc(hidden)] +// pub trait InternalResponseErrorAsFail { +// #[doc(hidden)] +// fn as_fail(&self) -> &Fail; +// #[doc(hidden)] +// fn as_mut_fail(&mut self) -> &mut Fail; +// } -#[doc(hidden)] -impl InternalResponseErrorAsFail for T { - fn as_fail(&self) -> &Fail { - self - } - fn as_mut_fail(&mut self) -> &mut Fail { - self - } -} +// #[doc(hidden)] +// impl InternalResponseErrorAsFail for T { +// fn as_fail(&self) -> &Fail { +// self +// } +// fn as_mut_fail(&mut self) -> &mut Fail { +// self +// } +// } /// Error that can be converted to `Response` -pub trait ResponseError: Fail + InternalResponseErrorAsFail { +pub trait ResponseError: fmt::Debug + fmt::Display { /// Create response for error /// /// Internal server error is generated by default. fn error_response(&self) -> Response { Response::new(StatusCode::INTERNAL_SERVER_ERROR) } + + /// Response + fn backtrace(&self) -> Option<&Backtrace> { + None + } } impl fmt::Display for Error { @@ -169,7 +161,7 @@ impl From for Response { } /// `Error` for any error that implements `ResponseError` -impl From for Error { +impl From for Error { fn from(err: T) -> Error { let backtrace = if err.backtrace().is_none() { Some(Backtrace::new()) @@ -183,17 +175,17 @@ impl From for Error { } } -/// Compatibility for `failure::Error` -impl ResponseError for failure::Compat where - T: fmt::Display + fmt::Debug + Sync + Send + 'static -{ -} +// /// Compatibility for `failure::Error` +// impl ResponseError for failure::Compat where +// T: fmt::Display + fmt::Debug + Sync + Send + 'static +// { +// } -impl From for Error { - fn from(err: failure::Error) -> Error { - err.compat().into() - } -} +// impl From for Error { +// fn from(err: failure::Error) -> Error { +// err.compat().into() +// } +// } /// `InternalServerError` for `JsonError` impl ResponseError for JsonError {} @@ -254,40 +246,40 @@ impl ResponseError for Canceled {} // impl ResponseError for MailboxError {} /// A set of errors that can occur during parsing HTTP streams -#[derive(Fail, Debug)] +#[derive(Debug, Display)] pub enum ParseError { /// An invalid `Method`, such as `GE.T`. - #[fail(display = "Invalid Method specified")] + #[display(fmt = "Invalid Method specified")] Method, /// An invalid `Uri`, such as `exam ple.domain`. - #[fail(display = "Uri error: {}", _0)] + #[display(fmt = "Uri error: {}", _0)] Uri(InvalidUri), /// An invalid `HttpVersion`, such as `HTP/1.1` - #[fail(display = "Invalid HTTP version specified")] + #[display(fmt = "Invalid HTTP version specified")] Version, /// An invalid `Header`. - #[fail(display = "Invalid Header provided")] + #[display(fmt = "Invalid Header provided")] Header, /// A message head is too large to be reasonable. - #[fail(display = "Message head is too large")] + #[display(fmt = "Message head is too large")] TooLarge, /// A message reached EOF, but is not complete. - #[fail(display = "Message is incomplete")] + #[display(fmt = "Message is incomplete")] Incomplete, /// An invalid `Status`, such as `1337 ELITE`. - #[fail(display = "Invalid Status provided")] + #[display(fmt = "Invalid Status provided")] Status, /// A timeout occurred waiting for an IO event. #[allow(dead_code)] - #[fail(display = "Timeout")] + #[display(fmt = "Timeout")] Timeout, /// An `io::Error` that occurred while trying to read or write to a network /// stream. - #[fail(display = "IO error: {}", _0)] - Io(#[cause] IoError), + #[display(fmt = "IO error: {}", _0)] + Io(IoError), /// Parsing a field as string failed - #[fail(display = "UTF8 error: {}", _0)] - Utf8(#[cause] Utf8Error), + #[display(fmt = "UTF8 error: {}", _0)] + Utf8(Utf8Error), } /// Return `BadRequest` for `ParseError` @@ -335,20 +327,20 @@ impl From for ParseError { } } -#[derive(Fail, Debug)] +#[derive(Display, Debug)] /// A set of errors that can occur during payload parsing pub enum PayloadError { /// A payload reached EOF, but is not complete. - #[fail(display = "A payload reached EOF, but is not complete.")] + #[display(fmt = "A payload reached EOF, but is not complete.")] Incomplete(Option), /// Content encoding stream corruption - #[fail(display = "Can not decode content-encoding.")] + #[display(fmt = "Can not decode content-encoding.")] EncodingCorrupted, /// A payload reached size limit. - #[fail(display = "A payload reached size limit.")] + #[display(fmt = "A payload reached size limit.")] Overflow, /// A payload length is unknown. - #[fail(display = "A payload length is unknown.")] + #[display(fmt = "A payload length is unknown.")] UnknownLength, } @@ -378,44 +370,44 @@ impl ResponseError for cookie::ParseError { } } -#[derive(Debug)] +#[derive(Debug, Display)] /// A set of errors that can occur during dispatching http requests pub enum DispatchError { /// Service error - // #[fail(display = "Application specific error: {}", _0)] + #[display(fmt = "Service specific error: {:?}", _0)] Service(E), /// An `io::Error` that occurred while trying to read or write to a network /// stream. - // #[fail(display = "IO error: {}", _0)] + #[display(fmt = "IO error: {}", _0)] Io(io::Error), /// Http request parse error. - // #[fail(display = "Parse error: {}", _0)] + #[display(fmt = "Parse error: {}", _0)] Parse(ParseError), /// The first request did not complete within the specified timeout. - // #[fail(display = "The first request did not complete within the specified timeout")] + #[display(fmt = "The first request did not complete within the specified timeout")] SlowRequestTimeout, /// Disconnect timeout. Makes sense for ssl streams. - // #[fail(display = "Connection shutdown timeout")] + #[display(fmt = "Connection shutdown timeout")] DisconnectTimeout, /// Payload is not consumed - // #[fail(display = "Task is completed but request's payload is not consumed")] + #[display(fmt = "Task is completed but request's payload is not consumed")] PayloadIsNotConsumed, /// Malformed request - // #[fail(display = "Malformed request")] + #[display(fmt = "Malformed request")] MalformedRequest, /// Internal error - // #[fail(display = "Internal error")] + #[display(fmt = "Internal error")] InternalError, /// Unknown error - // #[fail(display = "Unknown error")] + #[display(fmt = "Unknown error")] Unknown, } @@ -432,13 +424,13 @@ impl From for DispatchError { } /// A set of error that can occure during parsing content type -#[derive(Fail, PartialEq, Debug)] +#[derive(PartialEq, Debug, Display)] pub enum ContentTypeError { /// Can not parse content type - #[fail(display = "Can not parse content type")] + #[display(fmt = "Can not parse content type")] ParseError, /// Unknown content encoding - #[fail(display = "Unknown content encoding")] + #[display(fmt = "Unknown content encoding")] UnknownEncoding, } @@ -450,28 +442,26 @@ impl ResponseError for ContentTypeError { } /// A set of errors that can occur during parsing urlencoded payloads -#[derive(Fail, Debug)] +#[derive(Debug, Display, From)] pub enum UrlencodedError { /// Can not decode chunked transfer encoding - #[fail(display = "Can not decode chunked transfer encoding")] + #[display(fmt = "Can not decode chunked transfer encoding")] Chunked, /// Payload size is bigger than allowed. (default: 256kB) - #[fail( - display = "Urlencoded payload size is bigger than allowed. (default: 256kB)" - )] + #[display(fmt = "Urlencoded payload size is bigger than allowed. (default: 256kB)")] Overflow, /// Payload size is now known - #[fail(display = "Payload size is now known")] + #[display(fmt = "Payload size is now known")] UnknownLength, /// Content type error - #[fail(display = "Content type error")] + #[display(fmt = "Content type error")] ContentType, /// Parse error - #[fail(display = "Parse error")] + #[display(fmt = "Parse error")] Parse, /// Payload error - #[fail(display = "Error that occur during reading payload: {}", _0)] - Payload(#[cause] PayloadError), + #[display(fmt = "Error that occur during reading payload: {}", _0)] + Payload(PayloadError), } /// Return `BadRequest` for `UrlencodedError` @@ -485,27 +475,21 @@ impl ResponseError for UrlencodedError { } } -impl From for UrlencodedError { - fn from(err: PayloadError) -> UrlencodedError { - UrlencodedError::Payload(err) - } -} - /// A set of errors that can occur during parsing json payloads -#[derive(Fail, Debug)] +#[derive(Debug, Display, From)] pub enum JsonPayloadError { /// Payload size is bigger than allowed. (default: 256kB) - #[fail(display = "Json payload size is bigger than allowed. (default: 256kB)")] + #[display(fmt = "Json payload size is bigger than allowed. (default: 256kB)")] Overflow, /// Content type error - #[fail(display = "Content type error")] + #[display(fmt = "Content type error")] ContentType, /// Deserialize error - #[fail(display = "Json deserialize error: {}", _0)] - Deserialize(#[cause] JsonError), + #[display(fmt = "Json deserialize error: {}", _0)] + Deserialize(JsonError), /// Payload error - #[fail(display = "Error that occur during reading payload: {}", _0)] - Payload(#[cause] PayloadError), + #[display(fmt = "Error that occur during reading payload: {}", _0)] + Payload(PayloadError), } /// Return `BadRequest` for `UrlencodedError` @@ -518,19 +502,8 @@ impl ResponseError for JsonPayloadError { } } -impl From for JsonPayloadError { - fn from(err: PayloadError) -> JsonPayloadError { - JsonPayloadError::Payload(err) - } -} - -impl From for JsonPayloadError { - fn from(err: JsonError) -> JsonPayloadError { - JsonPayloadError::Deserialize(err) - } -} - /// Error type returned when reading body as lines. +#[derive(From)] pub enum ReadlinesError { /// Error when decoding a line. EncodingError, @@ -542,18 +515,6 @@ pub enum ReadlinesError { ContentTypeError(ContentTypeError), } -impl From for ReadlinesError { - fn from(err: PayloadError) -> Self { - ReadlinesError::PayloadError(err) - } -} - -impl From for ReadlinesError { - fn from(err: ContentTypeError) -> Self { - ReadlinesError::ContentTypeError(err) - } -} - /// Helper type that can wrap any error and generate custom response. /// /// In following example any `io::Error` will be converted into "BAD REQUEST" @@ -578,7 +539,7 @@ pub struct InternalError { enum InternalErrorType { Status(StatusCode), - Response(Box>>), + Response(RefCell>), } impl InternalError { @@ -593,27 +554,17 @@ impl InternalError { /// Create `InternalError` with predefined `Response`. pub fn from_response(cause: T, response: Response) -> Self { - let resp = response.into_parts(); InternalError { cause, - status: InternalErrorType::Response(Box::new(Mutex::new(Some(resp)))), + status: InternalErrorType::Response(RefCell::new(Some(response))), backtrace: Backtrace::new(), } } } -impl Fail for InternalError -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - fn backtrace(&self) -> Option<&Backtrace> { - Some(&self.backtrace) - } -} - impl fmt::Debug for InternalError where - T: Send + Sync + fmt::Debug + 'static, + T: fmt::Debug + 'static, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.cause, f) @@ -622,7 +573,7 @@ where impl fmt::Display for InternalError where - T: Send + Sync + fmt::Display + 'static, + T: fmt::Display + 'static, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.cause, f) @@ -631,14 +582,18 @@ where impl ResponseError for InternalError where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { + fn backtrace(&self) -> Option<&Backtrace> { + Some(&self.backtrace) + } + fn error_response(&self) -> Response { match self.status { InternalErrorType::Status(st) => Response::new(st), InternalErrorType::Response(ref resp) => { - if let Some(resp) = resp.lock().unwrap().take() { - Response::<()>::from_parts(resp) + if let Some(resp) = resp.borrow_mut().take() { + resp } else { Response::new(StatusCode::INTERNAL_SERVER_ERROR) } @@ -659,7 +614,7 @@ impl From for Error { #[allow(non_snake_case)] pub fn ErrorBadRequest(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::BAD_REQUEST).into() } @@ -669,7 +624,7 @@ where #[allow(non_snake_case)] pub fn ErrorUnauthorized(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::UNAUTHORIZED).into() } @@ -679,7 +634,7 @@ where #[allow(non_snake_case)] pub fn ErrorForbidden(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::FORBIDDEN).into() } @@ -689,7 +644,7 @@ where #[allow(non_snake_case)] pub fn ErrorNotFound(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::NOT_FOUND).into() } @@ -699,7 +654,7 @@ where #[allow(non_snake_case)] pub fn ErrorMethodNotAllowed(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into() } @@ -709,7 +664,7 @@ where #[allow(non_snake_case)] pub fn ErrorRequestTimeout(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::REQUEST_TIMEOUT).into() } @@ -719,7 +674,7 @@ where #[allow(non_snake_case)] pub fn ErrorConflict(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::CONFLICT).into() } @@ -729,7 +684,7 @@ where #[allow(non_snake_case)] pub fn ErrorGone(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::GONE).into() } @@ -739,7 +694,7 @@ where #[allow(non_snake_case)] pub fn ErrorPreconditionFailed(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::PRECONDITION_FAILED).into() } @@ -749,7 +704,7 @@ where #[allow(non_snake_case)] pub fn ErrorExpectationFailed(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::EXPECTATION_FAILED).into() } @@ -759,7 +714,7 @@ where #[allow(non_snake_case)] pub fn ErrorInternalServerError(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into() } @@ -769,7 +724,7 @@ where #[allow(non_snake_case)] pub fn ErrorNotImplemented(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::NOT_IMPLEMENTED).into() } @@ -779,7 +734,7 @@ where #[allow(non_snake_case)] pub fn ErrorBadGateway(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::BAD_GATEWAY).into() } @@ -789,7 +744,7 @@ where #[allow(non_snake_case)] pub fn ErrorServiceUnavailable(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::SERVICE_UNAVAILABLE).into() } @@ -799,7 +754,7 @@ where #[allow(non_snake_case)] pub fn ErrorGatewayTimeout(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into() } @@ -808,10 +763,8 @@ where mod tests { use super::*; use cookie::ParseError as CookieParseError; - use failure; use http::{Error as HttpError, StatusCode}; use httparse; - use std::env; use std::error::Error as StdError; use std::io; @@ -829,11 +782,10 @@ mod tests { } #[test] - fn test_as_fail() { + fn test_as_response() { let orig = io::Error::new(io::ErrorKind::Other, "other"); - let desc = orig.description().to_owned(); - let e = ParseError::Io(orig); - assert_eq!(format!("{}", e.cause().unwrap()), desc); + let e: Error = ParseError::Io(orig).into(); + assert_eq!(format!("{}", e.as_response_error()), "IO error: other"); } #[test] @@ -847,7 +799,7 @@ mod tests { let orig = io::Error::new(io::ErrorKind::Other, "other"); let desc = orig.description().to_owned(); let e = Error::from(orig); - assert_eq!(format!("{}", e.as_fail()), desc); + assert_eq!(format!("{}", e.as_response_error()), desc); } #[test] @@ -881,7 +833,7 @@ mod tests { ($from:expr => $error:pat) => { match ParseError::from($from) { e @ $error => { - let desc = format!("{}", e.cause().unwrap()); + let desc = format!("{}", e); assert_eq!(desc, $from.description().to_owned()); } _ => unreachable!("{:?}", $from), @@ -891,8 +843,7 @@ mod tests { #[test] fn test_from() { - from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => ParseError::Io(..)); - + // from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => ParseError::Io(..)); from!(httparse::Error::HeaderName => ParseError::Header); from!(httparse::Error::HeaderName => ParseError::Header); from!(httparse::Error::HeaderValue => ParseError::Header); @@ -903,22 +854,22 @@ mod tests { from!(httparse::Error::Version => ParseError::Version); } - #[test] - fn failure_error() { - const NAME: &str = "RUST_BACKTRACE"; - let old_tb = env::var(NAME); - env::set_var(NAME, "0"); - let error = failure::err_msg("Hello!"); - let resp: Error = error.into(); - assert_eq!( - format!("{:?}", resp), - "Compat { error: ErrorMessage { msg: \"Hello!\" } }\n\n" - ); - match old_tb { - Ok(x) => env::set_var(NAME, x), - _ => env::remove_var(NAME), - } - } + // #[test] + // fn failure_error() { + // const NAME: &str = "RUST_BACKTRACE"; + // let old_tb = env::var(NAME); + // env::set_var(NAME, "0"); + // let error = failure::err_msg("Hello!"); + // let resp: Error = error.into(); + // assert_eq!( + // format!("{:?}", resp), + // "Compat { error: ErrorMessage { msg: \"Hello!\" } }\n\n" + // ); + // match old_tb { + // Ok(x) => env::set_var(NAME, x), + // _ => env::remove_var(NAME), + // } + // } #[test] fn test_internal_error() { @@ -928,31 +879,31 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } - #[test] - fn test_error_downcasting_direct() { - #[derive(Debug, Fail)] - #[fail(display = "demo error")] - struct DemoError; + // #[test] + // fn test_error_downcasting_direct() { + // #[derive(Debug, Display)] + // #[display(fmt = "demo error")] + // struct DemoError; - impl ResponseError for DemoError {} + // impl ResponseError for DemoError {} - let err: Error = DemoError.into(); - let err_ref: &DemoError = err.downcast_ref().unwrap(); - assert_eq!(err_ref.to_string(), "demo error"); - } + // let err: Error = DemoError.into(); + // let err_ref: &DemoError = err.downcast_ref().unwrap(); + // assert_eq!(err_ref.to_string(), "demo error"); + // } - #[test] - fn test_error_downcasting_compat() { - #[derive(Debug, Fail)] - #[fail(display = "demo error")] - struct DemoError; + // #[test] + // fn test_error_downcasting_compat() { + // #[derive(Debug, Display)] + // #[display(fmt = "demo error")] + // struct DemoError; - impl ResponseError for DemoError {} + // impl ResponseError for DemoError {} - let err: Error = failure::Error::from(DemoError).into(); - let err_ref: &DemoError = err.downcast_ref().unwrap(); - assert_eq!(err_ref.to_string(), "demo error"); - } + // let err: Error = failure::Error::from(DemoError).into(); + // let err_ref: &DemoError = err.downcast_ref().unwrap(); + // assert_eq!(err_ref.to_string(), "demo error"); + // } #[test] fn test_error_helpers() { diff --git a/src/response.rs b/src/response.rs index 5cff612f7..49f2b63fb 100644 --- a/src/response.rs +++ b/src/response.rs @@ -240,17 +240,6 @@ impl Response { pub(crate) fn release(self) { ResponsePool::release(self.0); } - - pub(crate) fn into_parts(self) -> ResponseParts { - self.0.into_parts() - } - - pub(crate) fn from_parts(parts: ResponseParts) -> Response { - Response( - Box::new(InnerResponse::from_parts(parts)), - ResponseBody::Body(Body::Empty), - ) - } } impl fmt::Debug for Response { @@ -736,11 +725,6 @@ struct InnerResponse { pool: &'static ResponsePool, } -pub(crate) struct ResponseParts { - head: ResponseHead, - error: Option, -} - impl InnerResponse { #[inline] fn new(status: StatusCode, pool: &'static ResponsePool) -> InnerResponse { @@ -757,23 +741,6 @@ impl InnerResponse { error: None, } } - - /// This is for failure, we can not have Send + Sync on Streaming and Actor response - fn into_parts(self) -> ResponseParts { - ResponseParts { - head: self.head, - error: self.error, - } - } - - fn from_parts(parts: ResponseParts) -> InnerResponse { - InnerResponse { - head: parts.head, - response_size: 0, - error: parts.error, - pool: ResponsePool::pool(), - } - } } /// Internal use only! diff --git a/src/ws/client/error.rs b/src/ws/client/error.rs index 02c723755..1eccb0b95 100644 --- a/src/ws/client/error.rs +++ b/src/ws/client/error.rs @@ -2,82 +2,52 @@ use std::io; use actix_connector::ConnectorError; -use failure::Fail; +use derive_more::{Display, From}; use http::{header::HeaderValue, Error as HttpError, StatusCode}; use crate::error::ParseError; use crate::ws::ProtocolError; /// Websocket client error -#[derive(Fail, Debug)] +#[derive(Debug, Display, From)] pub enum ClientError { /// Invalid url - #[fail(display = "Invalid url")] + #[display(fmt = "Invalid url")] InvalidUrl, /// Invalid response status - #[fail(display = "Invalid response status")] + #[display(fmt = "Invalid response status")] InvalidResponseStatus(StatusCode), /// Invalid upgrade header - #[fail(display = "Invalid upgrade header")] + #[display(fmt = "Invalid upgrade header")] InvalidUpgradeHeader, /// Invalid connection header - #[fail(display = "Invalid connection header")] + #[display(fmt = "Invalid connection header")] InvalidConnectionHeader(HeaderValue), /// Missing CONNECTION header - #[fail(display = "Missing CONNECTION header")] + #[display(fmt = "Missing CONNECTION header")] MissingConnectionHeader, /// Missing SEC-WEBSOCKET-ACCEPT header - #[fail(display = "Missing SEC-WEBSOCKET-ACCEPT header")] + #[display(fmt = "Missing SEC-WEBSOCKET-ACCEPT header")] MissingWebSocketAcceptHeader, /// Invalid challenge response - #[fail(display = "Invalid challenge response")] + #[display(fmt = "Invalid challenge response")] InvalidChallengeResponse(String, HeaderValue), /// Http parsing error - #[fail(display = "Http parsing error")] - Http(#[cause] HttpError), + #[display(fmt = "Http parsing error")] + Http(HttpError), /// Response parsing error - #[fail(display = "Response parsing error: {}", _0)] - ParseError(#[cause] ParseError), + #[display(fmt = "Response parsing error: {}", _0)] + ParseError(ParseError), /// Protocol error - #[fail(display = "{}", _0)] - Protocol(#[cause] ProtocolError), + #[display(fmt = "{}", _0)] + Protocol(ProtocolError), /// Connect error - #[fail(display = "Connector error: {:?}", _0)] + #[display(fmt = "Connector error: {:?}", _0)] Connect(ConnectorError), /// IO Error - #[fail(display = "{}", _0)] - Io(#[cause] io::Error), + #[display(fmt = "{}", _0)] + Io(io::Error), /// "Disconnected" - #[fail(display = "Disconnected")] + #[display(fmt = "Disconnected")] Disconnected, } - -impl From for ClientError { - fn from(err: HttpError) -> ClientError { - ClientError::Http(err) - } -} - -impl From for ClientError { - fn from(err: ConnectorError) -> ClientError { - ClientError::Connect(err) - } -} - -impl From for ClientError { - fn from(err: ProtocolError) -> ClientError { - ClientError::Protocol(err) - } -} - -impl From for ClientError { - fn from(err: io::Error) -> ClientError { - ClientError::Io(err) - } -} - -impl From for ClientError { - fn from(err: ParseError) -> ClientError { - ClientError::ParseError(err) - } -} diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 02667c964..2d629c73b 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -5,7 +5,7 @@ //! communicate with the peer. use std::io; -use failure::Fail; +use derive_more::{Display, From}; use http::{header, Method, StatusCode}; use crate::error::ResponseError; @@ -28,65 +28,59 @@ pub use self::service::VerifyWebSockets; pub use self::transport::Transport; /// Websocket protocol errors -#[derive(Fail, Debug)] +#[derive(Debug, Display, From)] pub enum ProtocolError { /// Received an unmasked frame from client - #[fail(display = "Received an unmasked frame from client")] + #[display(fmt = "Received an unmasked frame from client")] UnmaskedFrame, /// Received a masked frame from server - #[fail(display = "Received a masked frame from server")] + #[display(fmt = "Received a masked frame from server")] MaskedFrame, /// Encountered invalid opcode - #[fail(display = "Invalid opcode: {}", _0)] + #[display(fmt = "Invalid opcode: {}", _0)] InvalidOpcode(u8), /// Invalid control frame length - #[fail(display = "Invalid control frame length: {}", _0)] + #[display(fmt = "Invalid control frame length: {}", _0)] InvalidLength(usize), /// Bad web socket op code - #[fail(display = "Bad web socket op code")] + #[display(fmt = "Bad web socket op code")] BadOpCode, /// A payload reached size limit. - #[fail(display = "A payload reached size limit.")] + #[display(fmt = "A payload reached size limit.")] Overflow, /// Continuation is not supported - #[fail(display = "Continuation is not supported.")] + #[display(fmt = "Continuation is not supported.")] NoContinuation, /// Bad utf-8 encoding - #[fail(display = "Bad utf-8 encoding.")] + #[display(fmt = "Bad utf-8 encoding.")] BadEncoding, /// Io error - #[fail(display = "io error: {}", _0)] - Io(#[cause] io::Error), + #[display(fmt = "io error: {}", _0)] + Io(io::Error), } impl ResponseError for ProtocolError {} -impl From for ProtocolError { - fn from(err: io::Error) -> ProtocolError { - ProtocolError::Io(err) - } -} - /// Websocket handshake errors -#[derive(Fail, PartialEq, Debug)] +#[derive(PartialEq, Debug, Display)] pub enum HandshakeError { /// Only get method is allowed - #[fail(display = "Method not allowed")] + #[display(fmt = "Method not allowed")] GetMethodRequired, /// Upgrade header if not set to websocket - #[fail(display = "Websocket upgrade is expected")] + #[display(fmt = "Websocket upgrade is expected")] NoWebsocketUpgrade, /// Connection header is not set to upgrade - #[fail(display = "Connection upgrade is expected")] + #[display(fmt = "Connection upgrade is expected")] NoConnectionUpgrade, /// Websocket version header is not set - #[fail(display = "Websocket version header is required")] + #[display(fmt = "Websocket version header is required")] NoVersionHeader, /// Unsupported websocket version - #[fail(display = "Unsupported version")] + #[display(fmt = "Unsupported version")] UnsupportedVersion, /// Websocket key is not set or wrong - #[fail(display = "Unknown websocket key")] + #[display(fmt = "Unknown websocket key")] BadWebsocketKey, } From 42277c5c8f8a3db0fa1e0b445235154e4f87a9fc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 26 Jan 2019 22:09:26 -0800 Subject: [PATCH 107/208] update deps --- Cargo.toml | 10 +++++----- src/h1/decoder.rs | 5 ++--- src/ws/transport.rs | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a5fc597a4..c7622fe2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix-service = "0.1.3" +actix-service = "0.1.6" actix-codec = "0.1.0" actix-connector = "0.1.0" actix-rt = "0.1.0" @@ -50,7 +50,7 @@ actix-utils = "0.1.0" # actix-server = { path="../actix-net/actix-server/" } # actix-utils = { path="../actix-net/actix-utils/" } -base64 = "0.9" +base64 = "0.10" backtrace = "0.3" bitflags = "1.0" bytes = "0.4" @@ -66,7 +66,7 @@ log = "0.4" mime = "0.3" net2 = "0.2" percent-encoding = "1.0" -rand = "0.5" +rand = "0.6" serde = "1.0" serde_json = "1.0" sha1 = "0.6" @@ -75,14 +75,14 @@ serde_urlencoded = "0.5.3" time = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" -trust-dns-resolver = "0.10.1" +trust-dns-resolver = { version="0.11.0-alpha.1", default-features = false } # openssl openssl = { version="0.10", optional = true } [dev-dependencies] actix-web = "0.7" -env_logger = "0.5" +env_logger = "0.6" serde_derive = "1.0" [profile.release] diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index b2c410c47..7c17e909a 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -102,7 +102,8 @@ pub(crate) trait MessageType: Sized { } // connection keep-alive state header::CONNECTION => { - ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim()) { + ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim()) + { if conn.eq_ignore_ascii_case("keep-alive") { Some(ConnectionType::KeepAlive) } else if conn.eq_ignore_ascii_case("close") { @@ -925,7 +926,6 @@ mod tests { let req = parse_ready!(&mut buf); assert_eq!(req.inner().head.ctype, Some(ConnectionType::KeepAlive)); - } #[test] @@ -986,7 +986,6 @@ mod tests { assert!(req.upgrade()); assert_eq!(req.inner().head.ctype, Some(ConnectionType::Upgrade)); - } #[test] diff --git a/src/ws/transport.rs b/src/ws/transport.rs index f59ad67a7..6a4f4d227 100644 --- a/src/ws/transport.rs +++ b/src/ws/transport.rs @@ -7,7 +7,7 @@ use super::{Codec, Frame, Message}; pub struct Transport where - S: Service, + S: Service + 'static, T: AsyncRead + AsyncWrite, { inner: FramedTransport, From c3d3e8b465b7406484ee6f5d845426e033a15c60 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 27 Jan 2019 10:59:07 -0800 Subject: [PATCH 108/208] move TestServer to separate crate --- Cargo.toml | 21 ++-- src/client/pool.rs | 13 ++- src/config.rs | 11 +- src/httpmessage.rs | 5 +- src/json.rs | 2 +- src/lib.rs | 3 - src/test.rs | 227 +---------------------------------------- test-server/Cargo.toml | 64 ++++++++++++ test-server/src/lib.rs | 227 +++++++++++++++++++++++++++++++++++++++++ tests/test_client.rs | 3 +- tests/test_server.rs | 41 ++++---- tests/test_ws.rs | 5 +- 12 files changed, 345 insertions(+), 277 deletions(-) create mode 100644 test-server/Cargo.toml create mode 100644 test-server/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index c7622fe2d..426712201 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,15 +39,13 @@ ssl = ["openssl", "actix-connector/ssl"] [dependencies] actix-service = "0.1.6" actix-codec = "0.1.0" -actix-connector = "0.1.0" -actix-rt = "0.1.0" -actix-server = "0.1.0" -actix-utils = "0.1.0" +# actix-connector = "0.1.0" +# actix-utils = "0.1.0" +actix-connector = { git = "https://github.com/actix/actix-net.git" } +actix-utils = { git = "https://github.com/actix/actix-net.git" } # actix-codec = { path="../actix-net/actix-codec/" } # actix-connector = { path="../actix-net/actix-connector/" } -# actix-rt = { path="../actix-net/actix-rt/" } -# actix-server = { path="../actix-net/actix-server/" } # actix-utils = { path="../actix-net/actix-utils/" } base64 = "0.10" @@ -64,7 +62,6 @@ httparse = "1.3" indexmap = "1.0" log = "0.4" mime = "0.3" -net2 = "0.2" percent-encoding = "1.0" rand = "0.6" serde = "1.0" @@ -73,19 +70,17 @@ sha1 = "0.6" slab = "0.4" serde_urlencoded = "0.5.3" time = "0.1" -tokio-tcp = "0.1" tokio-timer = "0.2" +tokio-current-thread = "0.1" trust-dns-resolver = { version="0.11.0-alpha.1", default-features = false } # openssl openssl = { version="0.10", optional = true } [dev-dependencies] +actix-rt = "0.1.0" actix-web = "0.7" +actix-server = "0.1" +actix-http-test = { path="test-server" } env_logger = "0.6" serde_derive = "1.0" - -[profile.release] -lto = true -opt-level = 3 -codegen-units = 1 diff --git a/src/client/pool.rs b/src/client/pool.rs index 94e96899e..11828dcb8 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -5,7 +5,6 @@ use std::rc::Rc; use std::time::{Duration, Instant}; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_rt::spawn; use actix_service::Service; use futures::future::{ok, Either, FutureResult}; use futures::sync::oneshot; @@ -265,7 +264,7 @@ where inner: Rc>>, fut: F, ) { - spawn(OpenWaitingConnection { + tokio_current_thread::spawn(OpenWaitingConnection { key, fut, rx: Some(rx), @@ -408,7 +407,9 @@ where || (now - conn.created) > self.conn_lifetime { if let Some(timeout) = self.disconnect_timeout { - spawn(CloseConnection::new(conn.io, timeout)) + tokio_current_thread::spawn(CloseConnection::new( + conn.io, timeout, + )) } } else { let mut io = conn.io; @@ -417,7 +418,9 @@ where Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), Ok(n) if n > 0 => { if let Some(timeout) = self.disconnect_timeout { - spawn(CloseConnection::new(io, timeout)) + tokio_current_thread::spawn(CloseConnection::new( + io, timeout, + )) } continue; } @@ -433,7 +436,7 @@ where fn release_close(&mut self, io: Io) { self.acquired -= 1; if let Some(timeout) = self.disconnect_timeout { - spawn(CloseConnection::new(io, timeout)) + tokio_current_thread::spawn(CloseConnection::new(io, timeout)) } } diff --git a/src/config.rs b/src/config.rs index 67c928fb7..c37601dbe 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,7 +4,6 @@ use std::rc::Rc; use std::time::{Duration, Instant}; use std::{fmt, net}; -use actix_rt::spawn; use bytes::BytesMut; use futures::{future, Future}; use log::error; @@ -355,10 +354,12 @@ impl DateService { // periodic date update let s = self.clone(); - spawn(sleep(Duration::from_millis(500)).then(move |_| { - s.0.reset(); - future::ok(()) - })); + tokio_current_thread::spawn(sleep(Duration::from_millis(500)).then( + move |_| { + s.0.reset(); + future::ok(()) + }, + )); } } diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 373b7ed45..589617fc9 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -567,14 +567,15 @@ where #[cfg(test)] mod tests { - use super::*; - use crate::test::TestRequest; use encoding::all::ISO_8859_2; use encoding::Encoding; use futures::Async; use mime; use serde_derive::Deserialize; + use super::*; + use crate::test::TestRequest; + #[test] fn test_content_type() { let req = TestRequest::with_header("content-type", "text/plain").finish(); diff --git a/src/json.rs b/src/json.rs index bfecf0cc3..d06449cb0 100644 --- a/src/json.rs +++ b/src/json.rs @@ -133,12 +133,12 @@ impl Future for JsonBod #[cfg(test)] mod tests { - use super::*; use bytes::Bytes; use futures::Async; use http::header; use serde_derive::{Deserialize, Serialize}; + use super::*; use crate::test::TestRequest; impl PartialEq for JsonPayloadError { diff --git a/src/lib.rs b/src/lib.rs index 76c3a9679..5adc9236e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,9 +59,6 @@ //! * `session` - enables session support, includes `ring` crate as //! dependency //! -// #![warn(missing_docs)] -#![allow(dead_code)] - pub mod body; pub mod client; mod config; diff --git a/src/test.rs b/src/test.rs index c264ac47a..4b7e30ac3 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,29 +1,14 @@ -//! Various helpers for Actix applications to use during testing. +//! Test Various helpers for Actix applications to use during testing. use std::str::FromStr; -use std::sync::mpsc; -use std::{net, thread}; - -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_rt::{Runtime, System}; -use actix_server::{Server, StreamServiceFactory}; -use actix_service::Service; use bytes::Bytes; use cookie::Cookie; -use futures::future::{lazy, Future}; use http::header::HeaderName; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; -use net2::TcpBuilder; -use crate::body::MessageBody; -use crate::client::{ - ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, - ConnectorError, SendRequestError, -}; use crate::header::{Header, IntoHeaderValue}; use crate::payload::Payload; -use crate::request::Request; -use crate::ws; +use crate::Request; /// Test `Request` builder /// @@ -264,211 +249,3 @@ impl TestRequest { // } // } } - -/// The `TestServer` type. -/// -/// `TestServer` is very simple test server that simplify process of writing -/// integration tests cases for actix web applications. -/// -/// # Examples -/// -/// ```rust -/// # extern crate actix_web; -/// # use actix_web::*; -/// # -/// # fn my_handler(req: &HttpRequest) -> HttpResponse { -/// # HttpResponse::Ok().into() -/// # } -/// # -/// # fn main() { -/// use actix_web::test::TestServer; -/// -/// let mut srv = TestServer::new(|app| app.handler(my_handler)); -/// -/// let req = srv.get().finish().unwrap(); -/// let response = srv.execute(req.send()).unwrap(); -/// assert!(response.status().is_success()); -/// # } -/// ``` -pub struct TestServer; - -/// -pub struct TestServerRuntime { - addr: net::SocketAddr, - conn: T, - rt: Runtime, -} - -impl TestServer { - /// Start new test server with application factory - pub fn with_factory( - factory: F, - ) -> TestServerRuntime< - impl Service + Clone, - > { - let (tx, rx) = mpsc::channel(); - - // run server in separate thread - thread::spawn(move || { - let sys = System::new("actix-test-server"); - let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); - let local_addr = tcp.local_addr().unwrap(); - - Server::build() - .listen("test", tcp, factory) - .workers(1) - .disable_signals() - .start(); - - tx.send((System::current(), local_addr)).unwrap(); - sys.run(); - }); - - let (system, addr) = rx.recv().unwrap(); - System::set_current(system); - - let mut rt = Runtime::new().unwrap(); - let conn = rt - .block_on(lazy(|| Ok::<_, ()>(TestServer::new_connector()))) - .unwrap(); - - TestServerRuntime { addr, conn, rt } - } - - fn new_connector( - ) -> impl Service + Clone - { - #[cfg(feature = "ssl")] - { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - Connector::default().ssl(builder.build()).service() - } - #[cfg(not(feature = "ssl"))] - { - Connector::default().service() - } - } - - /// Get firat available unused address - pub fn unused_addr() -> net::SocketAddr { - let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = TcpBuilder::new_v4().unwrap(); - socket.bind(&addr).unwrap(); - socket.reuse_address(true).unwrap(); - let tcp = socket.to_tcp_listener().unwrap(); - tcp.local_addr().unwrap() - } -} - -impl TestServerRuntime { - /// Execute future on current core - pub fn block_on(&mut self, fut: F) -> Result - where - F: Future, - { - self.rt.block_on(fut) - } - - /// Execute future on current core - pub fn execute(&mut self, fut: F) -> Result - where - F: Future, - { - self.rt.block_on(fut) - } - - /// Construct test server url - pub fn addr(&self) -> net::SocketAddr { - self.addr - } - - /// Construct test server url - pub fn url(&self, uri: &str) -> String { - if uri.starts_with('/') { - format!("http://127.0.0.1:{}{}", self.addr.port(), uri) - } else { - format!("http://127.0.0.1:{}/{}", self.addr.port(), uri) - } - } - - /// Create `GET` request - pub fn get(&self) -> ClientRequestBuilder { - ClientRequest::get(self.url("/").as_str()) - } - - /// Create `POST` request - pub fn post(&self) -> ClientRequestBuilder { - ClientRequest::post(self.url("/").as_str()) - } - - /// Create `HEAD` request - pub fn head(&self) -> ClientRequestBuilder { - ClientRequest::head(self.url("/").as_str()) - } - - /// Connect to test http server - pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder { - ClientRequest::build() - .method(meth) - .uri(self.url(path).as_str()) - .take() - } - - /// Http connector - pub fn connector(&mut self) -> &mut T { - &mut self.conn - } - - /// Http connector - pub fn new_connector(&mut self) -> T - where - T: Clone, - { - self.conn.clone() - } - - /// Stop http server - fn stop(&mut self) { - System::current().stop(); - } -} - -impl TestServerRuntime -where - T: Service + Clone, - T::Response: Connection, -{ - /// Connect to websocket server at a given path - pub fn ws_at( - &mut self, - path: &str, - ) -> Result, ws::ClientError> { - let url = self.url(path); - self.rt - .block_on(lazy(|| ws::Client::default().call(ws::Connect::new(url)))) - } - - /// Connect to a websocket server - pub fn ws( - &mut self, - ) -> Result, ws::ClientError> { - self.ws_at("/") - } - - /// Send request and read response message - pub fn send_request( - &mut self, - req: ClientRequest, - ) -> Result { - self.rt.block_on(req.send(&mut self.conn)) - } -} - -impl Drop for TestServerRuntime { - fn drop(&mut self) { - self.stop() - } -} diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml new file mode 100644 index 000000000..5cede2dc2 --- /dev/null +++ b/test-server/Cargo.toml @@ -0,0 +1,64 @@ +[package] +name = "actix-http-test" +version = "0.1.0" +authors = ["Nikolay Kim "] +description = "Actix http" +readme = "README.md" +keywords = ["http", "web", "framework", "async", "futures"] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web.git" +documentation = "https://actix.rs/api/actix-web/stable/actix_web/" +categories = ["network-programming", "asynchronous", + "web-programming::http-server", + "web-programming::websocket"] +license = "MIT/Apache-2.0" +exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] +edition = "2018" + +[package.metadata.docs.rs] +features = ["session"] + +[lib] +name = "actix_http_test" +path = "src/lib.rs" + +[features] +default = ["session"] + +# sessions feature, session require "ring" crate and c compiler +session = ["cookie/secure"] + +# openssl +ssl = ["openssl", "actix-http/ssl"] + +[dependencies] +actix-codec = "0.1" +actix-service = "0.1.6" +actix-rt = "0.1.0" +actix-server = "0.1.0" +actix-http = { path=".." } +actix-utils = { git = "https://github.com/actix/actix-net.git" } + +# actix-codec = { path="../actix-net/actix-codec/" } +# actix-rt = { path="../actix-net/actix-rt/" } +# actix-server = { path="../actix-net/actix-server/" } + +base64 = "0.10" +bytes = "0.4" +cookie = { version="0.11", features=["percent-encode"] } +futures = "0.1" +http = "0.1.8" +log = "0.4" +env_logger = "0.6" +net2 = "0.2" +serde = "1.0" +serde_json = "1.0" +sha1 = "0.6" +slab = "0.4" +serde_urlencoded = "0.5.3" +time = "0.1" +tokio-tcp = "0.1" +tokio-timer = "0.2" + +# openssl +openssl = { version="0.10", optional = true } diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs new file mode 100644 index 000000000..36c0d7d50 --- /dev/null +++ b/test-server/src/lib.rs @@ -0,0 +1,227 @@ +//! Various helpers for Actix applications to use during testing. +use std::sync::mpsc; +use std::{net, thread}; + +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_rt::{Runtime, System}; +use actix_server::{Server, StreamServiceFactory}; +use actix_service::Service; + +use futures::future::{lazy, Future}; +use http::Method; +use net2::TcpBuilder; + +use actix_http::body::MessageBody; +use actix_http::client::{ + ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, + ConnectorError, SendRequestError, +}; +use actix_http::ws; + +/// The `TestServer` type. +/// +/// `TestServer` is very simple test server that simplify process of writing +/// integration tests cases for actix web applications. +/// +/// # Examples +/// +/// ```rust +/// # extern crate actix_web; +/// # use actix_web::*; +/// # +/// # fn my_handler(req: &HttpRequest) -> HttpResponse { +/// # HttpResponse::Ok().into() +/// # } +/// # +/// # fn main() { +/// use actix_web::test::TestServer; +/// +/// let mut srv = TestServer::new(|app| app.handler(my_handler)); +/// +/// let req = srv.get().finish().unwrap(); +/// let response = srv.execute(req.send()).unwrap(); +/// assert!(response.status().is_success()); +/// # } +/// ``` +pub struct TestServer; + +/// +pub struct TestServerRuntime { + addr: net::SocketAddr, + conn: T, + rt: Runtime, +} + +impl TestServer { + /// Start new test server with application factory + pub fn with_factory( + factory: F, + ) -> TestServerRuntime< + impl Service + Clone, + > { + let (tx, rx) = mpsc::channel(); + + // run server in separate thread + thread::spawn(move || { + let sys = System::new("actix-test-server"); + let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); + let local_addr = tcp.local_addr().unwrap(); + + Server::build() + .listen("test", tcp, factory) + .workers(1) + .disable_signals() + .start(); + + tx.send((System::current(), local_addr)).unwrap(); + sys.run(); + }); + + let (system, addr) = rx.recv().unwrap(); + System::set_current(system); + + let mut rt = Runtime::new().unwrap(); + let conn = rt + .block_on(lazy(|| Ok::<_, ()>(TestServer::new_connector()))) + .unwrap(); + + TestServerRuntime { addr, conn, rt } + } + + fn new_connector( + ) -> impl Service + Clone + { + #[cfg(feature = "ssl")] + { + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + Connector::default().ssl(builder.build()).service() + } + #[cfg(not(feature = "ssl"))] + { + Connector::default().service() + } + } + + /// Get firat available unused address + pub fn unused_addr() -> net::SocketAddr { + let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); + let socket = TcpBuilder::new_v4().unwrap(); + socket.bind(&addr).unwrap(); + socket.reuse_address(true).unwrap(); + let tcp = socket.to_tcp_listener().unwrap(); + tcp.local_addr().unwrap() + } +} + +impl TestServerRuntime { + /// Execute future on current core + pub fn block_on(&mut self, fut: F) -> Result + where + F: Future, + { + self.rt.block_on(fut) + } + + /// Execute future on current core + pub fn execute(&mut self, fut: F) -> Result + where + F: Future, + { + self.rt.block_on(fut) + } + + /// Construct test server url + pub fn addr(&self) -> net::SocketAddr { + self.addr + } + + /// Construct test server url + pub fn url(&self, uri: &str) -> String { + if uri.starts_with('/') { + format!("http://127.0.0.1:{}{}", self.addr.port(), uri) + } else { + format!("http://127.0.0.1:{}/{}", self.addr.port(), uri) + } + } + + /// Create `GET` request + pub fn get(&self) -> ClientRequestBuilder { + ClientRequest::get(self.url("/").as_str()) + } + + /// Create `POST` request + pub fn post(&self) -> ClientRequestBuilder { + ClientRequest::post(self.url("/").as_str()) + } + + /// Create `HEAD` request + pub fn head(&self) -> ClientRequestBuilder { + ClientRequest::head(self.url("/").as_str()) + } + + /// Connect to test http server + pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder { + ClientRequest::build() + .method(meth) + .uri(self.url(path).as_str()) + .take() + } + + /// Http connector + pub fn connector(&mut self) -> &mut T { + &mut self.conn + } + + /// Http connector + pub fn new_connector(&mut self) -> T + where + T: Clone, + { + self.conn.clone() + } + + /// Stop http server + fn stop(&mut self) { + System::current().stop(); + } +} + +impl TestServerRuntime +where + T: Service + Clone, + T::Response: Connection, +{ + /// Connect to websocket server at a given path + pub fn ws_at( + &mut self, + path: &str, + ) -> Result, ws::ClientError> { + let url = self.url(path); + self.rt + .block_on(lazy(|| ws::Client::default().call(ws::Connect::new(url)))) + } + + /// Connect to a websocket server + pub fn ws( + &mut self, + ) -> Result, ws::ClientError> { + self.ws_at("/") + } + + /// Send request and read response message + pub fn send_request( + &mut self, + req: ClientRequest, + ) -> Result { + self.rt.block_on(req.send(&mut self.conn)) + } +} + +impl Drop for TestServerRuntime { + fn drop(&mut self) { + self.stop() + } +} diff --git a/tests/test_client.rs b/tests/test_client.rs index f19edda13..606bac22a 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -3,7 +3,8 @@ use bytes::Bytes; use futures::future::{self, ok}; use actix_http::HttpMessage; -use actix_http::{client, h1, test::TestServer, Request, Response}; +use actix_http::{client, h1, Request, Response}; +use actix_http_test::TestServer; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ diff --git a/tests/test_server.rs b/tests/test_server.rs index cb9cd3f9d..c23840ead 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -2,19 +2,20 @@ use std::io::{Read, Write}; use std::time::Duration; use std::{net, thread}; +use actix_http_test::TestServer; use actix_service::NewService; use bytes::Bytes; use futures::future::{self, ok}; use futures::stream::once; use actix_http::{ - body, client, h1, http, test, Body, Error, HttpMessage as HttpMessage2, KeepAlive, + body, client, h1, http, Body, Error, HttpMessage as HttpMessage2, KeepAlive, Request, Response, }; #[test] fn test_h1_v2() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) @@ -31,7 +32,7 @@ fn test_h1_v2() { #[test] fn test_slow_request() { - let srv = test::TestServer::with_factory(|| { + let srv = TestServer::with_factory(|| { h1::H1Service::build() .client_timeout(100) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) @@ -47,7 +48,7 @@ fn test_slow_request() { #[test] fn test_malformed_request() { - let srv = test::TestServer::with_factory(|| { + let srv = TestServer::with_factory(|| { h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().finish())).map(|_| ()) }); @@ -60,7 +61,7 @@ fn test_malformed_request() { #[test] fn test_keepalive() { - let srv = test::TestServer::with_factory(|| { + let srv = TestServer::with_factory(|| { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .map(|_| ()) @@ -80,7 +81,7 @@ fn test_keepalive() { #[test] fn test_keepalive_timeout() { - let srv = test::TestServer::with_factory(|| { + let srv = TestServer::with_factory(|| { h1::H1Service::build() .keep_alive(1) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) @@ -101,7 +102,7 @@ fn test_keepalive_timeout() { #[test] fn test_keepalive_close() { - let srv = test::TestServer::with_factory(|| { + let srv = TestServer::with_factory(|| { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .map(|_| ()) @@ -121,7 +122,7 @@ fn test_keepalive_close() { #[test] fn test_keepalive_http10_default_close() { - let srv = test::TestServer::with_factory(|| { + let srv = TestServer::with_factory(|| { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .map(|_| ()) @@ -140,7 +141,7 @@ fn test_keepalive_http10_default_close() { #[test] fn test_keepalive_http10() { - let srv = test::TestServer::with_factory(|| { + let srv = TestServer::with_factory(|| { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .map(|_| ()) @@ -166,7 +167,7 @@ fn test_keepalive_http10() { #[test] fn test_keepalive_disabled() { - let srv = test::TestServer::with_factory(|| { + let srv = TestServer::with_factory(|| { h1::H1Service::build() .keep_alive(KeepAlive::Disabled) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) @@ -191,7 +192,7 @@ fn test_content_length() { StatusCode, }; - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|req: Request| { let indx: usize = req.uri().path()[1..].parse().unwrap(); let statuses = [ @@ -240,7 +241,7 @@ fn test_headers() { let data = STR.repeat(10); let data2 = data.clone(); - let mut srv = test::TestServer::with_factory(move || { + let mut srv = TestServer::with_factory(move || { let data = data.clone(); h1::H1Service::new(move |_| { let mut builder = Response::Ok(); @@ -302,7 +303,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_body() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); @@ -317,7 +318,7 @@ fn test_body() { #[test] fn test_head_empty() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); @@ -340,7 +341,7 @@ fn test_head_empty() { #[test] fn test_head_binary() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|_| { ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) }) @@ -366,7 +367,7 @@ fn test_head_binary() { #[test] fn test_head_binary2() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); @@ -385,7 +386,7 @@ fn test_head_binary2() { #[test] fn test_body_length() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( @@ -407,7 +408,7 @@ fn test_body_length() { #[test] fn test_body_chunked_explicit() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) @@ -428,7 +429,7 @@ fn test_body_chunked_explicit() { #[test] fn test_body_chunked_implicit() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) @@ -447,7 +448,7 @@ fn test_body_chunked_implicit() { #[test] fn test_response_http_error_handling() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 11a3f472b..07f857d7a 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -1,6 +1,7 @@ use std::io; use actix_codec::Framed; +use actix_http_test::TestServer; use actix_service::NewService; use actix_utils::framed::IntoFramed; use actix_utils::stream::TakeItem; @@ -9,7 +10,7 @@ use bytes::{Bytes, BytesMut}; use futures::future::{lazy, ok, Either}; use futures::{Future, Sink, Stream}; -use actix_http::{h1, test, ws, ResponseError, SendResponse, ServiceConfig}; +use actix_http::{h1, ws, ResponseError, SendResponse, ServiceConfig}; fn ws_service(req: ws::Frame) -> impl Future { match req { @@ -34,7 +35,7 @@ fn ws_service(req: ws::Frame) -> impl Future)| { From 12fb94204f4eb8923d4830c007923539d1a6b8f5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 27 Jan 2019 11:40:26 -0800 Subject: [PATCH 109/208] use hashbrown instead of std HashMap --- Cargo.toml | 6 ++---- src/client/pool.rs | 5 +++-- src/extensions.rs | 31 ++----------------------------- test-server/Cargo.toml | 10 ---------- 4 files changed, 7 insertions(+), 45 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 426712201..b3adfa823 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,10 +44,6 @@ actix-codec = "0.1.0" actix-connector = { git = "https://github.com/actix/actix-net.git" } actix-utils = { git = "https://github.com/actix/actix-net.git" } -# actix-codec = { path="../actix-net/actix-codec/" } -# actix-connector = { path="../actix-net/actix-connector/" } -# actix-utils = { path="../actix-net/actix-utils/" } - base64 = "0.10" backtrace = "0.3" bitflags = "1.0" @@ -57,6 +53,8 @@ cookie = { version="0.11", features=["percent-encode"] } derive_more = "0.13" encoding = "0.2" futures = "0.1" +hashbrown = "0.1.8" +h2 = "0.1.16" http = "0.1.8" httparse = "1.3" indexmap = "1.0" diff --git a/src/client/pool.rs b/src/client/pool.rs index 11828dcb8..b577587d8 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -1,5 +1,5 @@ use std::cell::RefCell; -use std::collections::{HashMap, VecDeque}; +use std::collections::VecDeque; use std::io; use std::rc::Rc; use std::time::{Duration, Instant}; @@ -7,9 +7,10 @@ use std::time::{Duration, Instant}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::Service; use futures::future::{ok, Either, FutureResult}; -use futures::sync::oneshot; use futures::task::AtomicTask; +use futures::unsync::oneshot; use futures::{Async, Future, Poll}; +use hashbrown::HashMap; use http::uri::Authority; use indexmap::IndexSet; use slab::Slab; diff --git a/src/extensions.rs b/src/extensions.rs index 430b87bda..7bb965c96 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -1,40 +1,13 @@ use std::any::{Any, TypeId}; -use std::collections::HashMap; use std::fmt; use std::hash::{BuildHasherDefault, Hasher}; -struct IdHasher { - id: u64, -} - -impl Default for IdHasher { - fn default() -> IdHasher { - IdHasher { id: 0 } - } -} - -impl Hasher for IdHasher { - fn write(&mut self, bytes: &[u8]) { - for &x in bytes { - self.id.wrapping_add(u64::from(x)); - } - } - - fn write_u64(&mut self, u: u64) { - self.id = u; - } - - fn finish(&self) -> u64 { - self.id - } -} - -type AnyMap = HashMap, BuildHasherDefault>; +use hashbrown::HashMap; #[derive(Default)] /// A type map of request extensions. pub struct Extensions { - map: AnyMap, + map: HashMap>, } impl Extensions { diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 5cede2dc2..851b3efe4 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -28,9 +28,6 @@ default = ["session"] # sessions feature, session require "ring" crate and c compiler session = ["cookie/secure"] -# openssl -ssl = ["openssl", "actix-http/ssl"] - [dependencies] actix-codec = "0.1" actix-service = "0.1.6" @@ -39,10 +36,6 @@ actix-server = "0.1.0" actix-http = { path=".." } actix-utils = { git = "https://github.com/actix/actix-net.git" } -# actix-codec = { path="../actix-net/actix-codec/" } -# actix-rt = { path="../actix-net/actix-rt/" } -# actix-server = { path="../actix-net/actix-server/" } - base64 = "0.10" bytes = "0.4" cookie = { version="0.11", features=["percent-encode"] } @@ -59,6 +52,3 @@ serde_urlencoded = "0.5.3" time = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" - -# openssl -openssl = { version="0.10", optional = true } From 4a388d7ad97ab85a1e42847acca73d9e8ab9baa4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 28 Jan 2019 20:41:09 -0800 Subject: [PATCH 110/208] add client http/2 support --- examples/client.rs | 33 +++ src/client/connection.rs | 138 +++++---- src/client/connector.rs | 229 ++++++-------- src/client/error.rs | 24 +- src/client/{pipeline.rs => h1proto.rs} | 106 +++++-- src/client/h2proto.rs | 151 ++++++++++ src/client/mod.rs | 5 +- src/client/pool.rs | 394 +++++++++++++++---------- src/client/request.rs | 17 +- src/client/response.rs | 2 +- src/error.rs | 5 +- src/extensions.rs | 1 - src/lib.rs | 3 + src/response.rs | 1 + test-server/src/lib.rs | 12 +- 15 files changed, 719 insertions(+), 402 deletions(-) create mode 100644 examples/client.rs rename src/client/{pipeline.rs => h1proto.rs} (67%) create mode 100644 src/client/h2proto.rs diff --git a/examples/client.rs b/examples/client.rs new file mode 100644 index 000000000..06b708e20 --- /dev/null +++ b/examples/client.rs @@ -0,0 +1,33 @@ +use actix_http::{client, Error}; +use actix_rt::System; +use bytes::BytesMut; +use futures::{future::lazy, Future, Stream}; + +fn main() -> Result<(), Error> { + std::env::set_var("RUST_LOG", "actix_http=trace"); + env_logger::init(); + + System::new("test").block_on(lazy(|| { + let mut connector = client::Connector::default().service(); + + client::ClientRequest::get("https://www.rust-lang.org/") // <- Create request builder + .header("User-Agent", "Actix-web") + .finish() + .unwrap() + .send(&mut connector) // <- Send http request + .from_err() + .and_then(|response| { + // <- server http response + println!("Response: {:?}", response); + + // read response body + response + .from_err() + .fold(BytesMut::new(), move |mut acc, chunk| { + acc.extend_from_slice(&chunk); + Ok::<_, Error>(acc) + }) + .map(|body| println!("Downloaded: {:?} bytes", body.len())) + }) + })) +} diff --git a/src/client/connection.rs b/src/client/connection.rs index ed156bf84..b192caaeb 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -1,11 +1,35 @@ -use std::{fmt, io, time}; +use std::{fmt, time}; use actix_codec::{AsyncRead, AsyncWrite}; -use futures::Poll; +use bytes::Bytes; +use futures::Future; +use h2::client::SendRequest; +use crate::body::MessageBody; +use crate::message::RequestHead; + +use super::error::SendRequestError; use super::pool::Acquired; +use super::response::ClientResponse; +use super::{h1proto, h2proto}; -pub trait Connection: AsyncRead + AsyncWrite + 'static { +pub(crate) enum ConnectionType { + H1(Io), + H2(SendRequest), +} + +pub trait RequestSender { + type Future: Future; + + /// Close connection + fn send_request( + self, + head: RequestHead, + body: B, + ) -> Self::Future; +} + +pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static { /// Close connection fn close(&mut self); @@ -16,7 +40,7 @@ pub trait Connection: AsyncRead + AsyncWrite + 'static { #[doc(hidden)] /// HTTP client connection pub struct IoConnection { - io: Option, + io: Option>, created: time::Instant, pool: Option>, } @@ -26,77 +50,83 @@ where T: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Connection {:?}", self.io) + match self.io { + Some(ConnectionType::H1(ref io)) => write!(f, "H1Connection({:?})", io), + Some(ConnectionType::H2(_)) => write!(f, "H2Connection"), + None => write!(f, "Connection(Empty)"), + } } } impl IoConnection { - pub(crate) fn new(io: T, created: time::Instant, pool: Acquired) -> Self { + pub(crate) fn new( + io: ConnectionType, + created: time::Instant, + pool: Option>, + ) -> Self { IoConnection { + pool, created, io: Some(io), - pool: Some(pool), } } - /// Raw IO stream - pub fn get_mut(&mut self) -> &mut T { - self.io.as_mut().unwrap() - } - - pub(crate) fn into_inner(self) -> (T, time::Instant) { + pub(crate) fn into_inner(self) -> (ConnectionType, time::Instant) { (self.io.unwrap(), self.created) } } -impl Connection for IoConnection { - /// Close connection - fn close(&mut self) { - if let Some(mut pool) = self.pool.take() { - if let Some(io) = self.io.take() { - pool.close(IoConnection { - io: Some(io), - created: self.created, - pool: None, - }) - } - } - } +impl RequestSender for IoConnection +where + T: AsyncRead + AsyncWrite + 'static, +{ + type Future = Box>; - /// Release this connection to the connection pool - fn release(&mut self) { - if let Some(mut pool) = self.pool.take() { - if let Some(io) = self.io.take() { - pool.release(IoConnection { - io: Some(io), - created: self.created, - pool: None, - }) - } + fn send_request( + mut self, + head: RequestHead, + body: B, + ) -> Self::Future { + match self.io.take().unwrap() { + ConnectionType::H1(io) => Box::new(h1proto::send_request( + io, + head, + body, + self.created, + self.pool, + )), + ConnectionType::H2(io) => Box::new(h2proto::send_request( + io, + head, + body, + self.created, + self.pool, + )), } } } -impl io::Read for IoConnection { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.io.as_mut().unwrap().read(buf) - } +#[allow(dead_code)] +pub(crate) enum EitherConnection { + A(IoConnection), + B(IoConnection), } -impl AsyncRead for IoConnection {} +impl RequestSender for EitherConnection +where + A: AsyncRead + AsyncWrite + 'static, + B: AsyncRead + AsyncWrite + 'static, +{ + type Future = Box>; -impl io::Write for IoConnection { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.io.as_mut().unwrap().write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.io.as_mut().unwrap().flush() - } -} - -impl AsyncWrite for IoConnection { - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.io.as_mut().unwrap().shutdown() + fn send_request( + self, + head: RequestHead, + body: RB, + ) -> Self::Future { + match self { + EitherConnection::A(con) => con.send_request(head, body), + EitherConnection::B(con) => con.send_request(head, body), + } } } diff --git a/src/client/connector.rs b/src/client/connector.rs index 05caf1ed2..b573181ba 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1,23 +1,22 @@ use std::time::Duration; -use std::{fmt, io}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_connector::{Resolver, TcpConnector}; use actix_service::{Service, ServiceExt}; use actix_utils::timeout::{TimeoutError, TimeoutService}; -use futures::future::Either; -use futures::Poll; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; use super::connect::Connect; -use super::connection::{Connection, IoConnection}; +use super::connection::RequestSender; use super::error::ConnectorError; -use super::pool::ConnectionPool; +use super::pool::{ConnectionPool, Protocol}; #[cfg(feature = "ssl")] use actix_connector::ssl::OpensslConnector; #[cfg(feature = "ssl")] use openssl::ssl::{SslConnector, SslMethod}; +#[cfg(feature = "ssl")] +const H2: &[u8] = b"h2"; #[cfg(not(feature = "ssl"))] type SslConnector = (); @@ -40,7 +39,12 @@ impl Default for Connector { let connector = { #[cfg(feature = "ssl")] { - SslConnector::builder(SslMethod::tls()).unwrap().build() + use log::error; + let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); + let _ = ssl + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| error!("Can not set alpn protocol: {:?}", e)); + ssl.build() } #[cfg(not(feature = "ssl"))] { @@ -133,15 +137,17 @@ impl Connector { /// Finish configuration process and create connector service. pub fn service( self, - ) -> impl Service + Clone + ) -> impl Service + Clone { #[cfg(not(feature = "ssl"))] { let connector = TimeoutService::new( self.timeout, - self.resolver - .map_err(ConnectorError::from) - .and_then(TcpConnector::default().from_err()), + self.resolver.map_err(ConnectorError::from).and_then( + TcpConnector::default() + .from_err() + .map(|(msg, io)| (msg, io, Protocol::Http1)), + ), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -168,7 +174,20 @@ impl Connector { .and_then(TcpConnector::default().from_err()) .and_then( OpensslConnector::service(self.connector) - .map_err(ConnectorError::from), + .map_err(ConnectorError::from) + .map(|(msg, io)| { + let h2 = io + .get_ref() + .ssl() + .selected_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (msg, io, Protocol::Http2) + } else { + (msg, io, Protocol::Http1) + } + }), ), ) .map_err(|e| match e { @@ -178,9 +197,11 @@ impl Connector { let tcp_service = TimeoutService::new( self.timeout, - self.resolver - .map_err(ConnectorError::from) - .and_then(TcpConnector::default().from_err()), + self.resolver.map_err(ConnectorError::from).and_then( + TcpConnector::default() + .from_err() + .map(|(msg, io)| (msg, io, Protocol::Http1)), + ), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -209,13 +230,16 @@ impl Connector { #[cfg(not(feature = "ssl"))] mod connect_impl { + use futures::future::{err, Either, FutureResult}; + use futures::Poll; + use super::*; - use futures::future::{err, FutureResult}; + use crate::client::connection::IoConnection; pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) tcp_pool: ConnectionPool, } @@ -223,7 +247,8 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service + Clone, + T: Service + + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -235,7 +260,7 @@ mod connect_impl { impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { type Response = IoConnection; type Error = ConnectorError; @@ -264,17 +289,26 @@ mod connect_impl { mod connect_impl { use std::marker::PhantomData; - use futures::future::{err, FutureResult}; + use futures::future::{err, Either, FutureResult}; use futures::{Async, Future, Poll}; use super::*; + use crate::client::connection::EitherConnection; pub(crate) struct InnerConnector where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service, - T2: Service, + T1: Service< + Connect, + Response = (Connect, Io1, Protocol), + Error = ConnectorError, + >, + T2: Service< + Connect, + Response = (Connect, Io2, Protocol), + Error = ConnectorError, + >, { pub(crate) tcp_pool: ConnectionPool, pub(crate) ssl_pool: ConnectionPool, @@ -284,8 +318,16 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service + Clone, - T2: Service + Clone, + T1: Service< + Connect, + Response = (Connect, Io1, Protocol), + Error = ConnectorError, + > + Clone, + T2: Service< + Connect, + Response = (Connect, Io2, Protocol), + Error = ConnectorError, + > + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -299,10 +341,18 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service, - T2: Service, + T1: Service< + Connect, + Response = (Connect, Io1, Protocol), + Error = ConnectorError, + >, + T2: Service< + Connect, + Response = (Connect, Io2, Protocol), + Error = ConnectorError, + >, { - type Response = IoEither, IoConnection>; + type Response = EitherConnection; type Error = ConnectorError; type Future = Either< FutureResult, @@ -336,7 +386,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -344,17 +394,17 @@ mod connect_impl { impl Future for InnerConnectorResponseA where - T: Service, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { - type Item = IoEither, IoConnection>; + type Item = EitherConnection; type Error = ConnectorError; fn poll(&mut self) -> Poll { match self.fut.poll()? { Async::NotReady => Ok(Async::NotReady), - Async::Ready(res) => Ok(Async::Ready(IoEither::A(res))), + Async::Ready(res) => Ok(Async::Ready(EitherConnection::A(res))), } } } @@ -362,7 +412,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -370,129 +420,18 @@ mod connect_impl { impl Future for InnerConnectorResponseB where - T: Service, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { - type Item = IoEither, IoConnection>; + type Item = EitherConnection; type Error = ConnectorError; fn poll(&mut self) -> Poll { match self.fut.poll()? { Async::NotReady => Ok(Async::NotReady), - Async::Ready(res) => Ok(Async::Ready(IoEither::B(res))), + Async::Ready(res) => Ok(Async::Ready(EitherConnection::B(res))), } } } } - -pub(crate) enum IoEither { - A(Io1), - B(Io2), -} - -impl Connection for IoEither -where - Io1: Connection, - Io2: Connection, -{ - fn close(&mut self) { - match self { - IoEither::A(ref mut io) => io.close(), - IoEither::B(ref mut io) => io.close(), - } - } - - fn release(&mut self) { - match self { - IoEither::A(ref mut io) => io.release(), - IoEither::B(ref mut io) => io.release(), - } - } -} - -impl io::Read for IoEither -where - Io1: Connection, - Io2: Connection, -{ - fn read(&mut self, buf: &mut [u8]) -> io::Result { - match self { - IoEither::A(ref mut io) => io.read(buf), - IoEither::B(ref mut io) => io.read(buf), - } - } -} - -impl AsyncRead for IoEither -where - Io1: Connection, - Io2: Connection, -{ - unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { - match self { - IoEither::A(ref io) => io.prepare_uninitialized_buffer(buf), - IoEither::B(ref io) => io.prepare_uninitialized_buffer(buf), - } - } -} - -impl AsyncWrite for IoEither -where - Io1: Connection, - Io2: Connection, -{ - fn shutdown(&mut self) -> Poll<(), io::Error> { - match self { - IoEither::A(ref mut io) => io.shutdown(), - IoEither::B(ref mut io) => io.shutdown(), - } - } - - fn poll_write(&mut self, buf: &[u8]) -> Poll { - match self { - IoEither::A(ref mut io) => io.poll_write(buf), - IoEither::B(ref mut io) => io.poll_write(buf), - } - } - - fn poll_flush(&mut self) -> Poll<(), io::Error> { - match self { - IoEither::A(ref mut io) => io.poll_flush(), - IoEither::B(ref mut io) => io.poll_flush(), - } - } -} - -impl io::Write for IoEither -where - Io1: Connection, - Io2: Connection, -{ - fn flush(&mut self) -> io::Result<()> { - match self { - IoEither::A(ref mut io) => io.flush(), - IoEither::B(ref mut io) => io.flush(), - } - } - - fn write(&mut self, buf: &[u8]) -> io::Result { - match self { - IoEither::A(ref mut io) => io.write(buf), - IoEither::B(ref mut io) => io.write(buf), - } - } -} - -impl fmt::Debug for IoEither -where - Io1: fmt::Debug, - Io2: fmt::Debug, -{ - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - match self { - IoEither::A(ref io) => io.fmt(fmt), - IoEither::B(ref io) => io.fmt(fmt), - } - } -} diff --git a/src/client/error.rs b/src/client/error.rs index 2a5df9c97..e27a83d85 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -6,7 +6,8 @@ use trust_dns_resolver::error::ResolveError; #[cfg(feature = "ssl")] use openssl::ssl::{Error as SslError, HandshakeError}; -use crate::error::{Error, ParseError}; +use crate::error::{Error, ParseError, ResponseError}; +use crate::response::Response; /// A set of errors that can occur while connecting to an HTTP host #[derive(Debug, Display, From)] @@ -32,6 +33,10 @@ pub enum ConnectorError { #[display(fmt = "No dns records found for the input")] NoRecords, + /// Http2 error + #[display(fmt = "{}", _0)] + H2(h2::Error), + /// Connecting took too long #[display(fmt = "Timeout out while establishing connection")] Timeout, @@ -80,6 +85,23 @@ pub enum SendRequestError { Send(io::Error), /// Error parsing response Response(ParseError), + /// Http2 error + #[display(fmt = "{}", _0)] + H2(h2::Error), /// Error sending request body Body(Error), } + +/// Convert `SendRequestError` to a server `Response` +impl ResponseError for SendRequestError { + fn error_response(&self) -> Response { + match *self { + SendRequestError::Connector(ConnectorError::Timeout) => { + Response::GatewayTimeout() + } + SendRequestError::Connector(_) => Response::BadGateway(), + _ => Response::InternalServerError(), + } + .into() + } +} diff --git a/src/client/pipeline.rs b/src/client/h1proto.rs similarity index 67% rename from src/client/pipeline.rs rename to src/client/h1proto.rs index 8d946d644..ed3c66d62 100644 --- a/src/client/pipeline.rs +++ b/src/client/h1proto.rs @@ -1,38 +1,42 @@ -use std::collections::VecDeque; +use std::{io, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_service::Service; use bytes::Bytes; use futures::future::{err, ok, Either}; use futures::{Async, Future, Poll, Sink, Stream}; +use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; use super::error::{ConnectorError, SendRequestError}; +use super::pool::Acquired; use super::response::ClientResponse; -use super::{Connect, Connection}; use crate::body::{BodyLength, MessageBody, PayloadStream}; use crate::error::PayloadError; use crate::h1; use crate::message::RequestHead; -pub(crate) fn send_request( +pub(crate) fn send_request( + io: T, head: RequestHead, body: B, - connector: &mut T, + created: time::Instant, + pool: Option>, ) -> impl Future where - T: Service, + T: AsyncRead + AsyncWrite + 'static, B: MessageBody, - I: Connection, { + let io = H1Connection { + io: Some(io), + created: created, + pool: pool, + }; + let len = body.length(); - connector - // connect to the host - .call(Connect::new(head.uri.clone())) + // create Framed and send reqest + Framed::new(io, h1::ClientCodec::default()) + .send((head, len).into()) .from_err() - // create Framed and send reqest - .map(|io| Framed::new(io, h1::ClientCodec::default())) - .and_then(move |framed| framed.send((head, len).into()).from_err()) // send request body .and_then(move |framed| match body.length() { BodyLength::None | BodyLength::Empty | BodyLength::Sized(0) => { @@ -64,11 +68,70 @@ where }) } +#[doc(hidden)] +/// HTTP client connection +pub struct H1Connection { + io: Option, + created: time::Instant, + pool: Option>, +} + +impl ConnectionLifetime for H1Connection { + /// Close connection + fn close(&mut self) { + if let Some(mut pool) = self.pool.take() { + if let Some(io) = self.io.take() { + pool.close(IoConnection::new( + ConnectionType::H1(io), + self.created, + None, + )); + } + } + } + + /// Release this connection to the connection pool + fn release(&mut self) { + if let Some(mut pool) = self.pool.take() { + if let Some(io) = self.io.take() { + pool.release(IoConnection::new( + ConnectionType::H1(io), + self.created, + None, + )); + } + } + } +} + +impl io::Read for H1Connection { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.io.as_mut().unwrap().read(buf) + } +} + +impl AsyncRead for H1Connection {} + +impl io::Write for H1Connection { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.io.as_mut().unwrap().write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.io.as_mut().unwrap().flush() + } +} + +impl AsyncWrite for H1Connection { + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.io.as_mut().unwrap().shutdown() + } +} + /// Future responsible for sending request body to the peer -struct SendBody { +pub(crate) struct SendBody { body: Option, framed: Option>, - write_buf: VecDeque>, flushed: bool, } @@ -77,11 +140,10 @@ where I: AsyncRead + AsyncWrite + 'static, B: MessageBody, { - fn new(body: B, framed: Framed) -> Self { + pub(crate) fn new(body: B, framed: Framed) -> Self { SendBody { body: Some(body), framed: Some(framed), - write_buf: VecDeque::new(), flushed: true, } } @@ -89,7 +151,7 @@ where impl Future for SendBody where - I: Connection, + I: ConnectionLifetime, B: MessageBody, { type Item = Framed; @@ -158,15 +220,15 @@ impl Payload<()> { } } -impl Payload { - fn stream(framed: Framed) -> PayloadStream { +impl Payload { + pub fn stream(framed: Framed) -> PayloadStream { Box::new(Payload { framed: Some(framed.map_codec(|codec| codec.into_payload_codec())), }) } } -impl Stream for Payload { +impl Stream for Payload { type Item = Bytes; type Error = PayloadError; @@ -190,7 +252,7 @@ impl Stream for Payload { fn release_connection(framed: Framed, force_close: bool) where - T: Connection, + T: ConnectionLifetime, { let mut parts = framed.into_parts(); if !force_close && parts.read_buf.is_empty() && parts.write_buf.is_empty() { diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs new file mode 100644 index 000000000..fe42b909c --- /dev/null +++ b/src/client/h2proto.rs @@ -0,0 +1,151 @@ +use std::cell::RefCell; +use std::time; + +use actix_codec::{AsyncRead, AsyncWrite}; +use bytes::Bytes; +use futures::future::{err, Either}; +use futures::{Async, Future, Poll, Stream}; +use h2::{client::SendRequest, SendStream}; +use http::{request::Request, Version}; + +use super::connection::{ConnectionType, IoConnection}; +use super::error::SendRequestError; +use super::pool::Acquired; +use super::response::ClientResponse; +use crate::body::{BodyLength, MessageBody}; +use crate::message::{RequestHead, ResponseHead}; + +pub(crate) fn send_request( + io: SendRequest, + head: RequestHead, + body: B, + created: time::Instant, + pool: Option>, +) -> impl Future +where + T: AsyncRead + AsyncWrite + 'static, + B: MessageBody, +{ + trace!("Sending client request: {:?} {:?}", head, body.length()); + let eof = match body.length() { + BodyLength::None | BodyLength::Empty | BodyLength::Sized(0) => true, + _ => false, + }; + + io.ready() + .map_err(SendRequestError::from) + .and_then(move |mut io| { + let mut req = Request::new(()); + *req.uri_mut() = head.uri; + *req.method_mut() = head.method; + *req.headers_mut() = head.headers; + *req.version_mut() = Version::HTTP_2; + + match io.send_request(req, eof) { + Ok((resp, send)) => { + release(io, pool, created, false); + + if !eof { + Either::A(Either::B( + SendBody { + body, + send, + buf: None, + } + .and_then(move |_| resp.map_err(SendRequestError::from)), + )) + } else { + Either::B(resp.map_err(SendRequestError::from)) + } + } + Err(e) => { + release(io, pool, created, e.is_io()); + Either::A(Either::A(err(e.into()))) + } + } + }) + .and_then(|resp| { + let (parts, body) = resp.into_parts(); + + let mut head = ResponseHead::default(); + head.version = parts.version; + head.status = parts.status; + head.headers = parts.headers; + + Ok(ClientResponse { + head, + payload: RefCell::new(Some(Box::new(body.from_err()))), + }) + }) + .from_err() +} + +struct SendBody { + body: B, + send: SendStream, + buf: Option, +} + +impl Future for SendBody { + type Item = (); + type Error = SendRequestError; + + fn poll(&mut self) -> Poll { + if self.buf.is_none() { + match self.body.poll_next() { + Ok(Async::Ready(Some(buf))) => { + self.send.reserve_capacity(buf.len()); + self.buf = Some(buf); + } + Ok(Async::Ready(None)) => { + if let Err(e) = self.send.send_data(Bytes::new(), true) { + return Err(e.into()); + } + self.send.reserve_capacity(0); + return Ok(Async::Ready(())); + } + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(e) => return Err(e.into()), + } + } + + loop { + match self.send.poll_capacity() { + Ok(Async::NotReady) => return Ok(Async::NotReady), + Ok(Async::Ready(None)) => return Ok(Async::Ready(())), + Ok(Async::Ready(Some(cap))) => { + let mut buf = self.buf.take().unwrap(); + let len = buf.len(); + let bytes = buf.split_to(std::cmp::min(cap, len)); + + if let Err(e) = self.send.send_data(bytes, false) { + return Err(e.into()); + } else { + if !buf.is_empty() { + self.send.reserve_capacity(buf.len()); + self.buf = Some(buf); + } + return self.poll(); + } + } + Err(e) => return Err(e.into()), + } + } + } +} + +// release SendRequest object +fn release( + io: SendRequest, + pool: Option>, + created: time::Instant, + close: bool, +) { + if let Some(mut pool) = pool { + if close { + pool.close(IoConnection::new(ConnectionType::H2(io), created, None)); + } else { + pool.release(IoConnection::new(ConnectionType::H2(io), created, None)); + } + } +} diff --git a/src/client/mod.rs b/src/client/mod.rs index 76c3f8b88..c6498f371 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -3,13 +3,14 @@ mod connect; mod connection; mod connector; mod error; -mod pipeline; +mod h1proto; +mod h2proto; mod pool; mod request; mod response; pub use self::connect::Connect; -pub use self::connection::Connection; +pub use self::connection::RequestSender; pub use self::connector::Connector; pub use self::error::{ConnectorError, InvalidUrlKind, SendRequestError}; pub use self::request::{ClientRequest, ClientRequestBuilder}; diff --git a/src/client/pool.rs b/src/client/pool.rs index b577587d8..089c2627a 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -6,10 +6,12 @@ use std::time::{Duration, Instant}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::Service; +use bytes::Bytes; use futures::future::{ok, Either, FutureResult}; use futures::task::AtomicTask; use futures::unsync::oneshot; use futures::{Async, Future, Poll}; +use h2::client::{handshake, Handshake}; use hashbrown::HashMap; use http::uri::Authority; use indexmap::IndexSet; @@ -17,9 +19,15 @@ use slab::Slab; use tokio_timer::{sleep, Delay}; use super::connect::Connect; -use super::connection::IoConnection; +use super::connection::{ConnectionType, IoConnection}; use super::error::ConnectorError; +#[derive(Clone, Copy, PartialEq)] +pub enum Protocol { + Http1, + Http2, +} + #[derive(Hash, Eq, PartialEq, Clone, Debug)] pub(crate) struct Key { authority: Authority, @@ -31,13 +39,6 @@ impl From for Key { } } -#[derive(Debug)] -struct AvailableConnection { - io: T, - used: Instant, - created: Instant, -} - /// Connections pool pub(crate) struct ConnectionPool( T, @@ -47,7 +48,7 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) fn new( connector: T, @@ -86,7 +87,7 @@ where impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { type Response = IoConnection; type Error = ConnectorError; @@ -109,7 +110,7 @@ where Either::A(ok(IoConnection::new( io, created, - Acquired(key, Some(self.1.clone())), + Some(Acquired(key, Some(self.1.clone()))), ))) } Acquire::NotAvailable => { @@ -190,12 +191,13 @@ where { fut: F, key: Key, + h2: Option>, inner: Option>>>, } impl OpenConnection where - F: Future, + F: Future, Io: AsyncRead + AsyncWrite + 'static, { fn new(key: Key, inner: Rc>>, fut: F) -> Self { @@ -203,6 +205,7 @@ where key, fut, inner: Some(inner), + h2: None, } } } @@ -222,110 +225,165 @@ where impl Future for OpenConnection where - F: Future, + F: Future, Io: AsyncRead + AsyncWrite, { type Item = IoConnection; type Error = ConnectorError; fn poll(&mut self) -> Poll { + if let Some(ref mut h2) = self.h2 { + return match h2.poll() { + Ok(Async::Ready((snd, connection))) => { + tokio_current_thread::spawn(connection.map_err(|_| ())); + Ok(Async::Ready(IoConnection::new( + ConnectionType::H2(snd), + Instant::now(), + Some(Acquired(self.key.clone(), self.inner.clone())), + ))) + } + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(e) => Err(e.into()), + }; + } + match self.fut.poll() { Err(err) => Err(err.into()), - Ok(Async::Ready((_, io))) => { + Ok(Async::Ready((_, io, proto))) => { let _ = self.inner.take(); - Ok(Async::Ready(IoConnection::new( - io, - Instant::now(), - Acquired(self.key.clone(), self.inner.clone()), - ))) - } - Ok(Async::NotReady) => Ok(Async::NotReady), - } - } -} - -struct OpenWaitingConnection -where - Io: AsyncRead + AsyncWrite + 'static, -{ - fut: F, - key: Key, - rx: Option, ConnectorError>>>, - inner: Option>>>, -} - -impl OpenWaitingConnection -where - F: Future + 'static, - Io: AsyncRead + AsyncWrite + 'static, -{ - fn spawn( - key: Key, - rx: oneshot::Sender, ConnectorError>>, - inner: Rc>>, - fut: F, - ) { - tokio_current_thread::spawn(OpenWaitingConnection { - key, - fut, - rx: Some(rx), - inner: Some(inner), - }) - } -} - -impl Drop for OpenWaitingConnection -where - Io: AsyncRead + AsyncWrite + 'static, -{ - fn drop(&mut self) { - if let Some(inner) = self.inner.take() { - let mut inner = inner.as_ref().borrow_mut(); - inner.release(); - inner.check_availibility(); - } - } -} - -impl Future for OpenWaitingConnection -where - F: Future, - Io: AsyncRead + AsyncWrite, -{ - type Item = (); - type Error = (); - - fn poll(&mut self) -> Poll { - match self.fut.poll() { - Err(err) => { - let _ = self.inner.take(); - if let Some(rx) = self.rx.take() { - let _ = rx.send(Err(err)); - } - Err(()) - } - Ok(Async::Ready((_, io))) => { - let _ = self.inner.take(); - if let Some(rx) = self.rx.take() { - let _ = rx.send(Ok(IoConnection::new( - io, + if proto == Protocol::Http1 { + Ok(Async::Ready(IoConnection::new( + ConnectionType::H1(io), Instant::now(), - Acquired(self.key.clone(), self.inner.clone()), - ))); + Some(Acquired(self.key.clone(), self.inner.clone())), + ))) + } else { + self.h2 = Some(handshake(io)); + return self.poll(); } - Ok(Async::Ready(())) } Ok(Async::NotReady) => Ok(Async::NotReady), } } } +// struct OpenWaitingConnection +// where +// Io: AsyncRead + AsyncWrite + 'static, +// { +// fut: F, +// key: Key, +// h2: Option>, +// rx: Option, ConnectorError>>>, +// inner: Option>>>, +// } + +// impl OpenWaitingConnection +// where +// F: Future + 'static, +// Io: AsyncRead + AsyncWrite + 'static, +// { +// fn spawn( +// key: Key, +// rx: oneshot::Sender, ConnectorError>>, +// inner: Rc>>, +// fut: F, +// ) { +// tokio_current_thread::spawn(OpenWaitingConnection { +// key, +// fut, +// h2: None, +// rx: Some(rx), +// inner: Some(inner), +// }) +// } +// } + +// impl Drop for OpenWaitingConnection +// where +// Io: AsyncRead + AsyncWrite + 'static, +// { +// fn drop(&mut self) { +// if let Some(inner) = self.inner.take() { +// let mut inner = inner.as_ref().borrow_mut(); +// inner.release(); +// inner.check_availibility(); +// } +// } +// } + +// impl Future for OpenWaitingConnection +// where +// F: Future, +// Io: AsyncRead + AsyncWrite, +// { +// type Item = (); +// type Error = (); + +// fn poll(&mut self) -> Poll { +// if let Some(ref mut h2) = self.h2 { +// return match h2.poll() { +// Ok(Async::Ready((snd, connection))) => { +// tokio_current_thread::spawn(connection.map_err(|_| ())); +// let _ = self.rx.take().unwrap().send(Ok(IoConnection::new( +// ConnectionType::H2(snd), +// Instant::now(), +// Some(Acquired(self.key.clone(), self.inner.clone())), +// ))); +// Ok(Async::Ready(())) +// } +// Ok(Async::NotReady) => Ok(Async::NotReady), +// Err(e) => { +// let _ = self.inner.take(); +// if let Some(rx) = self.rx.take() { +// let _ = rx.send(Err(e.into())); +// } + +// Err(()) +// } +// }; +// } + +// match self.fut.poll() { +// Err(err) => { +// let _ = self.inner.take(); +// if let Some(rx) = self.rx.take() { +// let _ = rx.send(Err(err)); +// } +// Err(()) +// } +// Ok(Async::Ready((_, io, proto))) => { +// let _ = self.inner.take(); +// if proto == Protocol::Http1 { +// let _ = self.rx.take().unwrap().send(Ok(IoConnection::new( +// ConnectionType::H1(io), +// Instant::now(), +// Some(Acquired(self.key.clone(), self.inner.clone())), +// ))); +// } else { +// self.h2 = Some(handshake(io)); +// return self.poll(); +// } +// Ok(Async::Ready(())) +// } +// Ok(Async::NotReady) => Ok(Async::NotReady), +// } +// } +// } + enum Acquire { - Acquired(T, Instant), + Acquired(ConnectionType, Instant), Available, NotAvailable, } +// #[derive(Debug)] +struct AvailableConnection { + io: ConnectionType, + used: Instant, + created: Instant, +} + pub(crate) struct Inner { conn_lifetime: Duration, conn_keep_alive: Duration, @@ -355,7 +413,7 @@ impl Inner { self.waiters_queue.remove(&(key.clone(), token)); } - fn release_conn(&mut self, key: &Key, io: Io, created: Instant) { + fn release_conn(&mut self, key: &Key, io: ConnectionType, created: Instant) { self.acquired -= 1; self.available .entry(key.clone()) @@ -408,24 +466,30 @@ where || (now - conn.created) > self.conn_lifetime { if let Some(timeout) = self.disconnect_timeout { - tokio_current_thread::spawn(CloseConnection::new( - conn.io, timeout, - )) + if let ConnectionType::H1(io) = conn.io { + tokio_current_thread::spawn(CloseConnection::new( + io, timeout, + )) + } } } else { let mut io = conn.io; let mut buf = [0; 2]; - match io.read(&mut buf) { - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), - Ok(n) if n > 0 => { - if let Some(timeout) = self.disconnect_timeout { - tokio_current_thread::spawn(CloseConnection::new( - io, timeout, - )) + if let ConnectionType::H1(ref mut s) = io { + match s.read(&mut buf) { + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), + Ok(n) if n > 0 => { + if let Some(timeout) = self.disconnect_timeout { + if let ConnectionType::H1(io) = io { + tokio_current_thread::spawn( + CloseConnection::new(io, timeout), + ) + } + } + continue; } - continue; + Ok(_) | Err(_) => continue, } - Ok(_) | Err(_) => continue, } return Acquire::Acquired(io, conn.created); } @@ -434,10 +498,12 @@ where Acquire::Available } - fn release_close(&mut self, io: Io) { + fn release_close(&mut self, io: ConnectionType) { self.acquired -= 1; if let Some(timeout) = self.disconnect_timeout { - tokio_current_thread::spawn(CloseConnection::new(io, timeout)) + if let ConnectionType::H1(io) = io { + tokio_current_thread::spawn(CloseConnection::new(io, timeout)) + } } } @@ -448,65 +514,65 @@ where } } -struct ConnectorPoolSupport -where - Io: AsyncRead + AsyncWrite + 'static, -{ - connector: T, - inner: Rc>>, -} +// struct ConnectorPoolSupport +// where +// Io: AsyncRead + AsyncWrite + 'static, +// { +// connector: T, +// inner: Rc>>, +// } -impl Future for ConnectorPoolSupport -where - Io: AsyncRead + AsyncWrite + 'static, - T: Service, - T::Future: 'static, -{ - type Item = (); - type Error = (); +// impl Future for ConnectorPoolSupport +// where +// Io: AsyncRead + AsyncWrite + 'static, +// T: Service, +// T::Future: 'static, +// { +// type Item = (); +// type Error = (); - fn poll(&mut self) -> Poll { - let mut inner = self.inner.as_ref().borrow_mut(); - inner.task.register(); +// fn poll(&mut self) -> Poll { +// let mut inner = self.inner.as_ref().borrow_mut(); +// inner.task.register(); - // check waiters - loop { - let (key, token) = { - if let Some((key, token)) = inner.waiters_queue.get_index(0) { - (key.clone(), *token) - } else { - break; - } - }; - match inner.acquire(&key) { - Acquire::NotAvailable => break, - Acquire::Acquired(io, created) => { - let (_, tx) = inner.waiters.remove(token); - if let Err(conn) = tx.send(Ok(IoConnection::new( - io, - created, - Acquired(key.clone(), Some(self.inner.clone())), - ))) { - let (io, created) = conn.unwrap().into_inner(); - inner.release_conn(&key, io, created); - } - } - Acquire::Available => { - let (connect, tx) = inner.waiters.remove(token); - OpenWaitingConnection::spawn( - key.clone(), - tx, - self.inner.clone(), - self.connector.call(connect), - ); - } - } - let _ = inner.waiters_queue.swap_remove_index(0); - } +// // check waiters +// loop { +// let (key, token) = { +// if let Some((key, token)) = inner.waiters_queue.get_index(0) { +// (key.clone(), *token) +// } else { +// break; +// } +// }; +// match inner.acquire(&key) { +// Acquire::NotAvailable => break, +// Acquire::Acquired(io, created) => { +// let (_, tx) = inner.waiters.remove(token); +// if let Err(conn) = tx.send(Ok(IoConnection::new( +// io, +// created, +// Some(Acquired(key.clone(), Some(self.inner.clone()))), +// ))) { +// let (io, created) = conn.unwrap().into_inner(); +// inner.release_conn(&key, io, created); +// } +// } +// Acquire::Available => { +// let (connect, tx) = inner.waiters.remove(token); +// OpenWaitingConnection::spawn( +// key.clone(), +// tx, +// self.inner.clone(), +// self.connector.call(connect), +// ); +// } +// } +// let _ = inner.waiters_queue.swap_remove_index(0); +// } - Ok(Async::NotReady) - } -} +// Ok(Async::NotReady) +// } +// } struct CloseConnection { io: T, diff --git a/src/client/request.rs b/src/client/request.rs index fbb1e840b..a2233a2f1 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -17,8 +17,9 @@ use crate::http::{ }; use crate::message::{ConnectionType, Head, RequestHead}; +use super::connection::RequestSender; use super::response::ClientResponse; -use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; +use super::{Connect, ConnectorError, SendRequestError}; /// An HTTP Client Request /// @@ -37,7 +38,6 @@ use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; /// .map_err(|_| ()) /// .and_then(|response| { // <- server http response /// println!("Response: {:?}", response); -/// # actix_rt::System::current().stop(); /// Ok(()) /// }) /// })); @@ -175,10 +175,18 @@ where connector: &mut T, ) -> impl Future where + B: 'static, T: Service, - I: Connection, + I: RequestSender, { - pipeline::send_request(self.head, self.body, connector) + let Self { head, body } = self; + + connector + // connect to the host + .call(Connect::new(head.uri.clone())) + .from_err() + // send request + .and_then(move |connection| connection.send_request(head, body)) } } @@ -273,7 +281,6 @@ impl ClientRequestBuilder { /// .unwrap(); /// } /// ``` - #[doc(hidden)] pub fn set(&mut self, hdr: H) -> &mut Self { if let Some(parts) = parts(&mut self.head, &self.err) { match hdr.try_into() { diff --git a/src/client/response.rs b/src/client/response.rs index 6bfdfc321..005c0875b 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -10,7 +10,7 @@ use crate::error::PayloadError; use crate::httpmessage::HttpMessage; use crate::message::{Head, ResponseHead}; -use super::pipeline::Payload; +use super::h1proto::Payload; /// Client Response pub struct ClientResponse { diff --git a/src/error.rs b/src/error.rs index 8af422fc8..5470534f7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -327,7 +327,7 @@ impl From for ParseError { } } -#[derive(Display, Debug)] +#[derive(Display, Debug, From)] /// A set of errors that can occur during payload parsing pub enum PayloadError { /// A payload reached EOF, but is not complete. @@ -342,6 +342,9 @@ pub enum PayloadError { /// A payload length is unknown. #[display(fmt = "A payload length is unknown.")] UnknownLength, + /// Http2 payload error + #[display(fmt = "{}", _0)] + H2Payload(h2::Error), } impl From for PayloadError { diff --git a/src/extensions.rs b/src/extensions.rs index 7bb965c96..f7805641b 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -1,6 +1,5 @@ use std::any::{Any, TypeId}; use std::fmt; -use std::hash::{BuildHasherDefault, Hasher}; use hashbrown::HashMap; diff --git a/src/lib.rs b/src/lib.rs index 5adc9236e..0dbaee6aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,9 @@ //! * `session` - enables session support, includes `ring` crate as //! dependency //! +#[macro_use] +extern crate log; + pub mod body; pub mod client; mod config; diff --git a/src/response.rs b/src/response.rs index 49f2b63fb..5e1c0d076 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] //! Http response use std::cell::RefCell; use std::collections::VecDeque; diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 36c0d7d50..cd74d456b 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -13,8 +13,8 @@ use net2::TcpBuilder; use actix_http::body::MessageBody; use actix_http::client::{ - ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, - ConnectorError, SendRequestError, + ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connector, + ConnectorError, RequestSender, SendRequestError, }; use actix_http::ws; @@ -57,7 +57,7 @@ impl TestServer { pub fn with_factory( factory: F, ) -> TestServerRuntime< - impl Service + Clone, + impl Service + Clone, > { let (tx, rx) = mpsc::channel(); @@ -89,7 +89,7 @@ impl TestServer { } fn new_connector( - ) -> impl Service + Clone + ) -> impl Service + Clone { #[cfg(feature = "ssl")] { @@ -192,7 +192,7 @@ impl TestServerRuntime { impl TestServerRuntime where T: Service + Clone, - T::Response: Connection, + T::Response: RequestSender, { /// Connect to websocket server at a given path pub fn ws_at( @@ -212,7 +212,7 @@ where } /// Send request and read response message - pub fn send_request( + pub fn send_request( &mut self, req: ClientRequest, ) -> Result { From 4217894d48d6572b24c329e2e9166a39f31b004b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 29 Jan 2019 10:14:00 -0800 Subject: [PATCH 111/208] cleaup warnings --- .travis.yml | 2 +- Cargo.toml | 6 +-- README.md | 7 ++- src/body.rs | 2 +- src/client/connector.rs | 4 +- src/client/error.rs | 8 +-- src/client/h1proto.rs | 4 +- src/client/h2proto.rs | 38 +++++++------- src/client/pool.rs | 4 +- src/client/response.rs | 1 + src/error.rs | 4 +- src/h1/decoder.rs | 81 +++--------------------------- src/h1/dispatcher.rs | 106 +++++++++++++++++++++++++++++++++++----- src/h1/encoder.rs | 8 +-- src/h1/service.rs | 1 + src/lib.rs | 6 +++ src/payload.rs | 16 +++--- src/response.rs | 4 +- src/service.rs | 4 +- src/test.rs | 3 +- src/ws/mask.rs | 6 +-- 21 files changed, 166 insertions(+), 149 deletions(-) diff --git a/.travis.yml b/.travis.yml index feae30596..fd7dfd4e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin - RUST_BACKTRACE=1 cargo tarpaulin --out Xml + RUST_BACKTRACE=1 cargo tarpaulin --features="ssl" --out Xml bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi diff --git a/Cargo.toml b/Cargo.toml index b3adfa823..af19cacaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,8 @@ description = "Actix http" readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web.git" -documentation = "https://actix.rs/api/actix-web/stable/actix_web/" +repository = "https://github.com/actix/actix-http.git" +documentation = "https://actix.rs/api/actix-http/stable/actix_http/" categories = ["network-programming", "asynchronous", "web-programming::http-server", "web-programming::websocket"] @@ -20,7 +20,7 @@ features = ["session"] [badges] travis-ci = { repository = "actix/actix-web", branch = "master" } -appveyor = { repository = "fafhrd91/actix-web-hdy9d" } +appveyor = { repository = "actix/actix-http-hdy9d" } codecov = { repository = "actix/actix-web", branch = "master", service = "github" } [lib] diff --git a/README.md b/README.md index be8160968..4f9e44b90 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,13 @@ -# Actix http [![Build Status](https://travis-ci.org/fafhrd91/actix-http.svg?branch=master)](https://travis-ci.org/fafhrd91/actix-http) [![Build status](https://ci.appveyor.com/api/projects/status/bwq6923pblqg55gk/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-http/branch/master) [![codecov](https://codecov.io/gh/fafhrd91/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/fafhrd91/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/actix/actix-http.svg?branch=master)](https://travis-ci.org/actix/actix-http) [![Build status](https://ci.appveyor.com/api/projects/status/bwq6923pblqg55gk/branch/master?svg=true)](https://ci.appveyor.com/project/actix/actix-http/branch/master) [![codecov](https://codecov.io/gh/actix/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix http ## Documentation & community resources * [User Guide](https://actix.rs/docs/) -* [API Documentation (Development)](https://actix.rs/actix-http/actix_http/) -* [API Documentation (Releases)](https://actix.rs/api/actix-http/stable/actix_http/) +* [API Documentation](https://docs.rs/actix-http/) * [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-http](https://crates.io/crates/actix-web) +* Cargo package: [actix-http](https://crates.io/crates/actix-http) * Minimum supported Rust version: 1.26 or later ## Example diff --git a/src/body.rs b/src/body.rs index 4b71e9bb9..12e2d0345 100644 --- a/src/body.rs +++ b/src/body.rs @@ -158,7 +158,7 @@ impl From<&'static str> for Body { impl From<&'static [u8]> for Body { fn from(s: &'static [u8]) -> Body { - Body::Bytes(Bytes::from_static(s.as_ref())) + Body::Bytes(Bytes::from_static(s)) } } diff --git a/src/client/connector.rs b/src/client/connector.rs index b573181ba..5b5356c75 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -47,9 +47,7 @@ impl Default for Connector { ssl.build() } #[cfg(not(feature = "ssl"))] - { - () - } + {} }; Connector { diff --git a/src/client/error.rs b/src/client/error.rs index e27a83d85..6c91ff976 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -65,12 +65,8 @@ impl From> for ConnectorError { fn from(err: HandshakeError) -> ConnectorError { match err { HandshakeError::SetupFailure(stack) => SslError::from(stack).into(), - HandshakeError::Failure(stream) => { - SslError::from(stream.into_error()).into() - } - HandshakeError::WouldBlock(stream) => { - SslError::from(stream.into_error()).into() - } + HandshakeError::Failure(stream) => stream.into_error().into(), + HandshakeError::WouldBlock(stream) => stream.into_error().into(), } } } diff --git a/src/client/h1proto.rs b/src/client/h1proto.rs index ed3c66d62..86fc10b84 100644 --- a/src/client/h1proto.rs +++ b/src/client/h1proto.rs @@ -26,9 +26,9 @@ where B: MessageBody, { let io = H1Connection { + created, + pool, io: Some(io), - created: created, - pool: pool, }; let len = body.length(); diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index fe42b909c..e3d5be0b0 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -91,25 +91,25 @@ impl Future for SendBody { type Error = SendRequestError; fn poll(&mut self) -> Poll { - if self.buf.is_none() { - match self.body.poll_next() { - Ok(Async::Ready(Some(buf))) => { - self.send.reserve_capacity(buf.len()); - self.buf = Some(buf); - } - Ok(Async::Ready(None)) => { - if let Err(e) = self.send.send_data(Bytes::new(), true) { - return Err(e.into()); - } - self.send.reserve_capacity(0); - return Ok(Async::Ready(())); - } - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(e) => return Err(e.into()), - } - } - loop { + if self.buf.is_none() { + match self.body.poll_next() { + Ok(Async::Ready(Some(buf))) => { + self.send.reserve_capacity(buf.len()); + self.buf = Some(buf); + } + Ok(Async::Ready(None)) => { + if let Err(e) = self.send.send_data(Bytes::new(), true) { + return Err(e.into()); + } + self.send.reserve_capacity(0); + return Ok(Async::Ready(())); + } + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(e) => return Err(e.into()), + } + } + match self.send.poll_capacity() { Ok(Async::NotReady) => return Ok(Async::NotReady), Ok(Async::Ready(None)) => return Ok(Async::Ready(())), @@ -125,7 +125,7 @@ impl Future for SendBody { self.send.reserve_capacity(buf.len()); self.buf = Some(buf); } - return self.poll(); + continue; } } Err(e) => return Err(e.into()), diff --git a/src/client/pool.rs b/src/client/pool.rs index 089c2627a..425e89395 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -248,7 +248,7 @@ where } match self.fut.poll() { - Err(err) => Err(err.into()), + Err(err) => Err(err), Ok(Async::Ready((_, io, proto))) => { let _ = self.inner.take(); if proto == Protocol::Http1 { @@ -259,7 +259,7 @@ where ))) } else { self.h2 = Some(handshake(io)); - return self.poll(); + self.poll() } } Ok(Async::NotReady) => Ok(Async::NotReady), diff --git a/src/client/response.rs b/src/client/response.rs index 005c0875b..9010a3c56 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -13,6 +13,7 @@ use crate::message::{Head, ResponseHead}; use super::h1proto::Payload; /// Client Response +#[derive(Default)] pub struct ClientResponse { pub(crate) head: ResponseHead, pub(crate) payload: RefCell>, diff --git a/src/error.rs b/src/error.rs index 5470534f7..43bf7cfb8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -837,7 +837,7 @@ mod tests { match ParseError::from($from) { e @ $error => { let desc = format!("{}", e); - assert_eq!(desc, $from.description().to_owned()); + assert_eq!(desc, format!("IO error: {}", $from.description())); } _ => unreachable!("{:?}", $from), } @@ -846,7 +846,7 @@ mod tests { #[test] fn test_from() { - // from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => ParseError::Io(..)); + from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => ParseError::Io(..)); from!(httparse::Error::HeaderName => ParseError::Header); from!(httparse::Error::HeaderName => ParseError::Header); from!(httparse::Error::HeaderValue => ParseError::Header); diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 7c17e909a..480864787 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -273,16 +273,14 @@ impl MessageType for ClientResponse { // message payload let decoder = if let PayloadLength::Payload(pl) = len { pl + } else if status == StatusCode::SWITCHING_PROTOCOLS { + // switching protocol or connect + PayloadType::Stream(PayloadDecoder::eof()) + } else if src.len() >= MAX_BUFFER_SIZE { + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + return Err(ParseError::TooLarge); } else { - if status == StatusCode::SWITCHING_PROTOCOLS { - // switching protocol or connect - PayloadType::Stream(PayloadDecoder::eof()) - } else if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ParseError::TooLarge); - } else { - PayloadType::None - } + PayloadType::None }; msg.head.status = status; @@ -670,71 +668,6 @@ mod tests { }}; } - struct Buffer { - buf: Bytes, - err: Option, - } - - impl Buffer { - fn new(data: &'static str) -> Buffer { - Buffer { - buf: Bytes::from(data), - err: None, - } - } - } - - impl AsyncRead for Buffer {} - impl io::Read for Buffer { - fn read(&mut self, dst: &mut [u8]) -> Result { - if self.buf.is_empty() { - if self.err.is_some() { - Err(self.err.take().unwrap()) - } else { - Err(io::Error::new(io::ErrorKind::WouldBlock, "")) - } - } else { - let size = cmp::min(self.buf.len(), dst.len()); - let b = self.buf.split_to(size); - dst[..size].copy_from_slice(&b); - Ok(size) - } - } - } - - impl io::Write for Buffer { - fn write(&mut self, buf: &[u8]) -> io::Result { - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } - } - impl AsyncWrite for Buffer { - fn shutdown(&mut self) -> Poll<(), io::Error> { - Ok(Async::Ready(())) - } - fn write_buf(&mut self, _: &mut B) -> Poll { - Ok(Async::NotReady) - } - } - - // #[test] - // fn test_req_parse_err() { - // let mut sys = System::new("test"); - // let _ = sys.block_on(future::lazy(|| { - // let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); - // let readbuf = BytesMut::new(); - - // let mut h1 = Dispatcher::new(buf, |req| ok(Response::Ok().finish())); - // assert!(h1.poll_io().is_ok()); - // assert!(h1.poll_io().is_ok()); - // assert!(h1.flags.contains(Flags::READ_DISCONNECTED)); - // assert_eq!(h1.tasks.len(), 1); - // future::ok::<_, ()>(()) - // })); - // } - #[test] fn test_parse() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 569b1deeb..f66955af1 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -312,7 +312,7 @@ where Message::Item(req) => { match self.framed.get_codec().message_type() { MessageType::Payload => { - let (ps, pl) = Payload::new(false); + let (ps, pl) = Payload::create(false); *req.inner.payload.borrow_mut() = Some(pl); self.payload = Some(ps); } @@ -417,10 +417,10 @@ where // start shutdown timer if let Some(deadline) = self.config.client_disconnect_timer() { - self.ka_timer.as_mut().map(|timer| { + if let Some(timer) = self.ka_timer.as_mut() { timer.reset(deadline); let _ = timer.poll(); - }); + } } else { return Ok(()); } @@ -439,17 +439,14 @@ where self.state = State::None; } } else if let Some(deadline) = self.config.keep_alive_expire() { - self.ka_timer.as_mut().map(|timer| { + if let Some(timer) = self.ka_timer.as_mut() { timer.reset(deadline); let _ = timer.poll(); - }); + } } - } else { - let expire = self.ka_expire; - self.ka_timer.as_mut().map(|timer| { - timer.reset(expire); - let _ = timer.poll(); - }); + } else if let Some(timer) = self.ka_timer.as_mut() { + timer.reset(self.ka_expire); + let _ = timer.poll(); } } Async::NotReady => (), @@ -526,3 +523,90 @@ where } } } + +#[cfg(test)] +mod tests { + use std::{cmp, io}; + + use actix_codec::{AsyncRead, AsyncWrite}; + use actix_service::IntoService; + use bytes::{Buf, Bytes, BytesMut}; + use futures::future::{lazy, ok}; + + use super::*; + use crate::error::Error; + + struct Buffer { + buf: Bytes, + err: Option, + } + + impl Buffer { + fn new(data: &'static str) -> Buffer { + Buffer { + buf: Bytes::from(data), + err: None, + } + } + } + + impl AsyncRead for Buffer {} + impl io::Read for Buffer { + fn read(&mut self, dst: &mut [u8]) -> Result { + if self.buf.is_empty() { + if self.err.is_some() { + Err(self.err.take().unwrap()) + } else { + Err(io::Error::new(io::ErrorKind::WouldBlock, "")) + } + } else { + let size = cmp::min(self.buf.len(), dst.len()); + let b = self.buf.split_to(size); + dst[..size].copy_from_slice(&b); + Ok(size) + } + } + } + + impl io::Write for Buffer { + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + impl AsyncWrite for Buffer { + fn shutdown(&mut self) -> Poll<(), io::Error> { + Ok(Async::Ready(())) + } + fn write_buf(&mut self, _: &mut B) -> Poll { + Ok(Async::NotReady) + } + } + + #[test] + fn test_req_parse_err() { + let mut sys = actix_rt::System::new("test"); + let _ = sys.block_on(lazy(|| { + let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); + let readbuf = BytesMut::new(); + + let mut h1 = Dispatcher::new( + buf, + ServiceConfig::default(), + (|req| ok::<_, Error>(Response::Ok().finish())).into_service(), + ); + assert!(h1.poll().is_ok()); + assert!(h1.poll().is_ok()); + assert!(h1 + .inner + .as_ref() + .unwrap() + .flags + .contains(Flags::DISCONNECTED)); + // assert_eq!(h1.tasks.len(), 1); + ok::<_, ()>(()) + })); + } +} diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 92456520a..32c8f9c48 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -104,10 +104,10 @@ pub(crate) trait MessageType: Sized { let mut remaining = dst.remaining_mut(); let mut buf = unsafe { &mut *(dst.bytes_mut() as *mut [u8]) }; for (key, value) in self.headers() { - match key { - &CONNECTION => continue, - &TRANSFER_ENCODING | &CONTENT_LENGTH if skip_len => continue, - &DATE => { + match *key { + CONNECTION => continue, + TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue, + DATE => { has_date = true; } _ => (), diff --git a/src/h1/service.rs b/src/h1/service.rs index 7c2589cc8..d8f63a323 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -272,6 +272,7 @@ where } /// `NewService` implementation for `OneRequestService` service +#[derive(Default)] pub struct OneRequest { config: ServiceConfig, _t: PhantomData, diff --git a/src/lib.rs b/src/lib.rs index 0dbaee6aa..442637251 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,12 @@ //! * `session` - enables session support, includes `ring` crate as //! dependency //! +#![allow( + clippy::type_complexity, + clippy::new_without_default, + clippy::new_without_default_derive +)] + #[macro_use] extern crate log; diff --git a/src/payload.rs b/src/payload.rs index ea266f70b..6665a0e41 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -42,7 +42,7 @@ impl Payload { /// * `PayloadSender` - *Sender* side of the stream /// /// * `Payload` - *Receiver* side of the stream - pub fn new(eof: bool) -> (PayloadSender, Payload) { + pub fn create(eof: bool) -> (PayloadSender, Payload) { let shared = Rc::new(RefCell::new(Inner::new(eof))); ( @@ -540,7 +540,7 @@ mod tests { Runtime::new() .unwrap() .block_on(lazy(|| { - let (_, payload) = Payload::new(false); + let (_, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); assert_eq!(payload.len, 0); @@ -557,7 +557,7 @@ mod tests { Runtime::new() .unwrap() .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); + let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); @@ -582,7 +582,7 @@ mod tests { Runtime::new() .unwrap() .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); + let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); @@ -600,7 +600,7 @@ mod tests { Runtime::new() .unwrap() .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); + let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); sender.feed_data(Bytes::from("line1")); @@ -629,7 +629,7 @@ mod tests { Runtime::new() .unwrap() .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); + let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap()); @@ -663,7 +663,7 @@ mod tests { Runtime::new() .unwrap() .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); + let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap()); @@ -697,7 +697,7 @@ mod tests { Runtime::new() .unwrap() .block_on(lazy(|| { - let (_, mut payload) = Payload::new(false); + let (_, mut payload) = Payload::create(false); payload.unread_data(Bytes::from("data")); assert!(!payload.is_empty()); diff --git a/src/response.rs b/src/response.rs index 5e1c0d076..a4b65f2b4 100644 --- a/src/response.rs +++ b/src/response.rs @@ -109,7 +109,7 @@ impl Response { /// Constructs a response with body #[inline] pub fn with_body(status: StatusCode, body: B) -> Response { - ResponsePool::with_body(status, body.into()) + ResponsePool::with_body(status, body) } /// The source `error` for this response @@ -644,7 +644,7 @@ impl ResponseBuilder { } #[inline] -#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))] +#[allow(clippy::borrowed_box)] fn parts<'a>( parts: &'a mut Option>, err: &Option, diff --git a/src/service.rs b/src/service.rs index f98234e76..57828187d 100644 --- a/src/service.rs +++ b/src/service.rs @@ -85,7 +85,7 @@ where fn poll(&mut self) -> Poll { if let Some(res) = self.res.take() { - if let Err(_) = self.framed.as_mut().unwrap().force_send(res) { + if self.framed.as_mut().unwrap().force_send(res).is_err() { return Err((self.err.take().unwrap(), self.framed.take().unwrap())); } } @@ -232,6 +232,6 @@ where break; } } - return Ok(Async::Ready(self.framed.take().unwrap())); + Ok(Async::Ready(self.framed.take().unwrap())) } } diff --git a/src/test.rs b/src/test.rs index 4b7e30ac3..a26f31e59 100644 --- a/src/test.rs +++ b/src/test.rs @@ -144,9 +144,8 @@ impl TestRequest { uri, version, headers, - _cookies: _, payload, - prefix: _, + .. } = self; let mut req = Request::new(); diff --git a/src/ws/mask.rs b/src/ws/mask.rs index e9bfb3d56..157375417 100644 --- a/src/ws/mask.rs +++ b/src/ws/mask.rs @@ -1,5 +1,5 @@ //! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs) -#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))] +#![allow(clippy::cast_ptr_alignment)] use std::ptr::copy_nonoverlapping; use std::slice; @@ -19,7 +19,7 @@ impl<'a> ShortSlice<'a> { /// Faster version of `apply_mask()` which operates on 8-byte blocks. #[inline] -#[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))] +#[allow(clippy::cast_lossless)] pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { // Extend the mask to 64 bits let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64); @@ -50,7 +50,7 @@ pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { // TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so // inefficient, it could be done better. The compiler does not understand that // a `ShortSlice` must be smaller than a u64. -#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] +#[allow(clippy::needless_pass_by_value)] fn xor_short(buf: ShortSlice, mask: u64) { // Unsafe: we know that a `ShortSlice` fits in a u64 unsafe { From 9a4eb5a848bce9ede1452cba385e2083abdae7f0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 29 Jan 2019 10:17:38 -0800 Subject: [PATCH 112/208] update readme --- CHANGES.md | 2 +- Cargo.toml | 6 +++--- README.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c3c38011c..74fa4a22e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,5 @@ # Changes -## [0.1.0] - 2018-09-x +## [0.1.0] - 2019-01-x * Initial impl diff --git a/Cargo.toml b/Cargo.toml index af19cacaf..01aa1a0cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,9 @@ edition = "2018" features = ["session"] [badges] -travis-ci = { repository = "actix/actix-web", branch = "master" } -appveyor = { repository = "actix/actix-http-hdy9d" } -codecov = { repository = "actix/actix-web", branch = "master", service = "github" } +travis-ci = { repository = "actix/actix-http", branch = "master" } +appveyor = { repository = "fafhrd91/actix-http-b1qsn" } +codecov = { repository = "actix/actix-http", branch = "master", service = "github" } [lib] name = "actix_http" diff --git a/README.md b/README.md index 4f9e44b90..a5a255672 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Actix http [![Build Status](https://travis-ci.org/actix/actix-http.svg?branch=master)](https://travis-ci.org/actix/actix-http) [![Build status](https://ci.appveyor.com/api/projects/status/bwq6923pblqg55gk/branch/master?svg=true)](https://ci.appveyor.com/project/actix/actix-http/branch/master) [![codecov](https://codecov.io/gh/actix/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/actix/actix-http.svg?branch=master)](https://travis-ci.org/actix/actix-http) [![Build status](https://ci.appveyor.com/api/projects/status/i51p65u2bukstgwg/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-http-b1qsn/branch/master) [![codecov](https://codecov.io/gh/actix/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix http From 3e6bdbd9eee2b965eb97dea30e0e45c71ceffce6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 29 Jan 2019 10:34:27 -0800 Subject: [PATCH 113/208] rename trait --- Cargo.toml | 2 +- src/client/connection.rs | 6 +++--- src/client/connector.rs | 4 ++-- src/client/mod.rs | 2 +- src/client/request.rs | 4 ++-- src/h1/decoder.rs | 5 +---- test-server/src/lib.rs | 10 +++++----- 7 files changed, 15 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 01aa1a0cc..88a9ba88f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ serde_urlencoded = "0.5.3" time = "0.1" tokio-timer = "0.2" tokio-current-thread = "0.1" -trust-dns-resolver = { version="0.11.0-alpha.1", default-features = false } +trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false } # openssl openssl = { version="0.10", optional = true } diff --git a/src/client/connection.rs b/src/client/connection.rs index b192caaeb..683738e28 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -18,7 +18,7 @@ pub(crate) enum ConnectionType { H2(SendRequest), } -pub trait RequestSender { +pub trait Connection { type Future: Future; /// Close connection @@ -76,7 +76,7 @@ impl IoConnection { } } -impl RequestSender for IoConnection +impl Connection for IoConnection where T: AsyncRead + AsyncWrite + 'static, { @@ -112,7 +112,7 @@ pub(crate) enum EitherConnection { B(IoConnection), } -impl RequestSender for EitherConnection +impl Connection for EitherConnection where A: AsyncRead + AsyncWrite + 'static, B: AsyncRead + AsyncWrite + 'static, diff --git a/src/client/connector.rs b/src/client/connector.rs index 5b5356c75..05e24e51c 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -7,7 +7,7 @@ use actix_utils::timeout::{TimeoutError, TimeoutService}; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; use super::connect::Connect; -use super::connection::RequestSender; +use super::connection::Connection; use super::error::ConnectorError; use super::pool::{ConnectionPool, Protocol}; @@ -135,7 +135,7 @@ impl Connector { /// Finish configuration process and create connector service. pub fn service( self, - ) -> impl Service + Clone + ) -> impl Service + Clone { #[cfg(not(feature = "ssl"))] { diff --git a/src/client/mod.rs b/src/client/mod.rs index c6498f371..8d041827f 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -10,7 +10,7 @@ mod request; mod response; pub use self::connect::Connect; -pub use self::connection::RequestSender; +pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectorError, InvalidUrlKind, SendRequestError}; pub use self::request::{ClientRequest, ClientRequestBuilder}; diff --git a/src/client/request.rs b/src/client/request.rs index a2233a2f1..b62ebaf3c 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -17,7 +17,7 @@ use crate::http::{ }; use crate::message::{ConnectionType, Head, RequestHead}; -use super::connection::RequestSender; +use super::connection::Connection; use super::response::ClientResponse; use super::{Connect, ConnectorError, SendRequestError}; @@ -177,7 +177,7 @@ where where B: 'static, T: Service, - I: RequestSender, + I: Connection, { let Self { head, body } = self; diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 480864787..74e1fb68c 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -604,10 +604,7 @@ impl ChunkedState { #[cfg(test)] mod tests { - use std::{cmp, io}; - - use actix_codec::{AsyncRead, AsyncWrite}; - use bytes::{Buf, Bytes, BytesMut}; + use bytes::{Bytes, BytesMut}; use http::{Method, Version}; use super::*; diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index cd74d456b..1ef452044 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -13,8 +13,8 @@ use net2::TcpBuilder; use actix_http::body::MessageBody; use actix_http::client::{ - ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connector, - ConnectorError, RequestSender, SendRequestError, + ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, + ConnectorError, SendRequestError, }; use actix_http::ws; @@ -57,7 +57,7 @@ impl TestServer { pub fn with_factory( factory: F, ) -> TestServerRuntime< - impl Service + Clone, + impl Service + Clone, > { let (tx, rx) = mpsc::channel(); @@ -89,7 +89,7 @@ impl TestServer { } fn new_connector( - ) -> impl Service + Clone + ) -> impl Service + Clone { #[cfg(feature = "ssl")] { @@ -192,7 +192,7 @@ impl TestServerRuntime { impl TestServerRuntime where T: Service + Clone, - T::Response: RequestSender, + T::Response: Connection, { /// Connect to websocket server at a given path pub fn ws_at( From 76866f054f16231d9e271dd5bac85abc9698ec1b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 30 Jan 2019 10:29:15 -0800 Subject: [PATCH 114/208] move service to submodule; update travis config --- .travis.yml | 10 ++++++++++ src/service/mod.rs | 3 +++ src/{service.rs => service/senderror.rs} | 0 3 files changed, 13 insertions(+) create mode 100644 src/service/mod.rs rename src/{service.rs => service/senderror.rs} (100%) diff --git a/.travis.yml b/.travis.yml index fd7dfd4e5..b7b43895e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,16 @@ matrix: allow_failures: - rust: nightly +env: + global: + - RUSTFLAGS="-C link-dead-code" + - OPENSSL_VERSION=openssl-1.0.2 + +before_install: + - sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl + - sudo apt-get update -qq + - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev + before_script: - export PATH=$PATH:~/.cargo/bin diff --git a/src/service/mod.rs b/src/service/mod.rs new file mode 100644 index 000000000..83a40bd12 --- /dev/null +++ b/src/service/mod.rs @@ -0,0 +1,3 @@ +mod senderror; + +pub use self::senderror::{SendError, SendResponse}; diff --git a/src/service.rs b/src/service/senderror.rs similarity index 100% rename from src/service.rs rename to src/service/senderror.rs From 3269e3572295496ea5fd9f2115f243088c54b623 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Feb 2019 20:18:44 -0800 Subject: [PATCH 115/208] migrate to actix-service 0.2 --- Cargo.toml | 10 +- src/client/connector.rs | 74 +++++++--- src/client/pool.rs | 15 +- src/client/request.rs | 2 +- src/h1/dispatcher.rs | 14 +- src/h1/service.rs | 30 ++-- src/h2/mod.rs | 20 +++ src/h2/service.rs | 310 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/service/senderror.rs | 12 +- src/ws/client/service.rs | 11 +- src/ws/frame.rs | 2 +- src/ws/service.rs | 6 +- src/ws/transport.rs | 10 +- test-server/Cargo.toml | 6 +- test-server/src/lib.rs | 12 +- 16 files changed, 460 insertions(+), 75 deletions(-) create mode 100644 src/h2/mod.rs create mode 100644 src/h2/service.rs diff --git a/Cargo.toml b/Cargo.toml index 88a9ba88f..f41147819 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,12 +37,10 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix-service = "0.1.6" +actix-service = "0.2.0" actix-codec = "0.1.0" -# actix-connector = "0.1.0" -# actix-utils = "0.1.0" -actix-connector = { git = "https://github.com/actix/actix-net.git" } -actix-utils = { git = "https://github.com/actix/actix-net.git" } +actix-connector = "0.2.0" +actix-utils = "0.2.0" base64 = "0.10" backtrace = "0.3" @@ -78,7 +76,7 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.1.0" actix-web = "0.7" -actix-server = "0.1" +actix-server = "0.2" actix-http-test = { path="test-server" } env_logger = "0.6" serde_derive = "1.0" diff --git a/src/client/connector.rs b/src/client/connector.rs index 05e24e51c..fea9b9c0a 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -135,8 +135,11 @@ impl Connector { /// Finish configuration process and create connector service. pub fn service( self, - ) -> impl Service + Clone - { + ) -> impl Service< + Request = Connect, + Response = impl Connection, + Error = ConnectorError, + > + Clone { #[cfg(not(feature = "ssl"))] { let connector = TimeoutService::new( @@ -237,7 +240,11 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + >, { pub(crate) tcp_pool: ConnectionPool, } @@ -245,8 +252,11 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service - + Clone, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + > + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -255,15 +265,20 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + >, { + type Request = Connect; type Response = IoConnection; type Error = ConnectorError; type Future = Either< - as Service>::Future, + as Service>::Future, FutureResult, ConnectorError>, >; @@ -298,12 +313,12 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Connect, + Request = Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, >, T2: Service< - Connect, + Request = Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, >, @@ -317,12 +332,12 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Connect, + Request = Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, > + Clone, T2: Service< - Connect, + Request = Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, > + Clone, @@ -335,21 +350,22 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Connect, + Request = Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, >, T2: Service< - Connect, + Request = Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, >, { + type Request = Connect; type Response = EitherConnection; type Error = ConnectorError; type Future = Either< @@ -384,15 +400,23 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io1, Protocol), + Error = ConnectorError, + >, { - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } impl Future for InnerConnectorResponseA where - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io1, Protocol), + Error = ConnectorError, + >, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { @@ -410,15 +434,23 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io2, Protocol), + Error = ConnectorError, + >, { - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } impl Future for InnerConnectorResponseB where - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io2, Protocol), + Error = ConnectorError, + >, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { diff --git a/src/client/pool.rs b/src/client/pool.rs index 425e89395..188980cb3 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -48,7 +48,11 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + >, { pub(crate) fn new( connector: T, @@ -84,11 +88,16 @@ where } } -impl Service for ConnectionPool +impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + >, { + type Request = Connect; type Response = IoConnection; type Error = ConnectorError; type Future = Either< diff --git a/src/client/request.rs b/src/client/request.rs index b62ebaf3c..b80f0e6dc 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -176,7 +176,7 @@ where ) -> impl Future where B: 'static, - T: Service, + T: Service, I: Connection, { let Self { head, body } = self; diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index f66955af1..7780223f2 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -35,14 +35,14 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher, B: MessageBody> +pub struct Dispatcher where S::Error: Debug, { inner: Option>, } -struct InnerDispatcher, B: MessageBody> +struct InnerDispatcher where S::Error: Debug, { @@ -66,13 +66,13 @@ enum DispatcherMessage { Error(Response<()>), } -enum State, B: MessageBody> { +enum State { None, ServiceCall(S::Future), SendPayload(ResponseBody), } -impl, B: MessageBody> State { +impl State { fn is_empty(&self) -> bool { if let State::None = self { true @@ -85,7 +85,7 @@ impl, B: MessageBody> State { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service>, S::Error: Debug, B: MessageBody, { @@ -139,7 +139,7 @@ where impl InnerDispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service>, S::Error: Debug, B: MessageBody, { @@ -459,7 +459,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service>, S::Error: Debug, B: MessageBody, { diff --git a/src/h1/service.rs b/src/h1/service.rs index d8f63a323..c35d18714 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -27,13 +27,13 @@ pub struct H1Service { impl H1Service where - S: NewService> + Clone, + S: NewService> + Clone, S::Service: Clone, S::Error: Debug, B: MessageBody, { /// Create new `HttpService` instance. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H1Service { @@ -49,14 +49,15 @@ where } } -impl NewService for H1Service +impl NewService for H1Service where T: AsyncRead + AsyncWrite, - S: NewService> + Clone, + S: NewService> + Clone, S::Service: Clone, S::Error: Debug, B: MessageBody, { + type Request = T; type Response = H1ServiceResult; type Error = DispatchError; type InitError = S::InitError; @@ -88,7 +89,7 @@ pub struct H1ServiceBuilder { impl H1ServiceBuilder where - S: NewService, + S: NewService, S::Service: Clone, S::Error: Debug, { @@ -187,7 +188,7 @@ where pub fn finish(self, service: F) -> H1Service where B: MessageBody, - F: IntoNewService, + F: IntoNewService, { let cfg = ServiceConfig::new( self.keep_alive, @@ -203,7 +204,7 @@ where } #[doc(hidden)] -pub struct H1ServiceResponse, B> { +pub struct H1ServiceResponse { fut: S::Future, cfg: Option, _t: PhantomData<(T, B)>, @@ -212,7 +213,7 @@ pub struct H1ServiceResponse, B> { impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService>, + S: NewService>, S::Service: Clone, S::Error: Debug, B: MessageBody, @@ -238,7 +239,7 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where - S: Service> + Clone, + S: Service> + Clone, S::Error: Debug, B: MessageBody, { @@ -251,13 +252,14 @@ where } } -impl Service for H1ServiceHandler +impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service> + Clone, + S: Service> + Clone, S::Error: Debug, B: MessageBody, { + type Request = T; type Response = H1ServiceResult; type Error = DispatchError; type Future = Dispatcher; @@ -291,10 +293,11 @@ where } } -impl NewService for OneRequest +impl NewService for OneRequest where T: AsyncRead + AsyncWrite, { + type Request = T; type Response = (Request, Framed); type Error = ParseError; type InitError = (); @@ -316,10 +319,11 @@ pub struct OneRequestService { _t: PhantomData, } -impl Service for OneRequestService +impl Service for OneRequestService where T: AsyncRead + AsyncWrite, { + type Request = T; type Response = (Request, Framed); type Error = ParseError; type Future = OneRequestServiceResponse; diff --git a/src/h2/mod.rs b/src/h2/mod.rs new file mode 100644 index 000000000..4a54ec9fe --- /dev/null +++ b/src/h2/mod.rs @@ -0,0 +1,20 @@ +use std::fmt; + +mod service; + +/// H1 service response type +pub enum H2ServiceResult { + Disconnected, + Shutdown(T), +} + +impl fmt::Debug for H2ServiceResult { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + H2ServiceResult::Disconnected => write!(f, "H2ServiceResult::Disconnected"), + H2ServiceResult::Shutdown(ref v) => { + write!(f, "H2ServiceResult::Shutdown({:?})", v) + } + } + } +} diff --git a/src/h2/service.rs b/src/h2/service.rs new file mode 100644 index 000000000..827f84488 --- /dev/null +++ b/src/h2/service.rs @@ -0,0 +1,310 @@ +use std::fmt::Debug; +use std::marker::PhantomData; +use std::net; + +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_service::{IntoNewService, NewService, Service}; +use bytes::Bytes; +use futures::future::{ok, FutureResult}; +use futures::{try_ready, Async, Future, Poll, Stream}; +use h2::server::{self, Connection, Handshake}; +use log::error; + +use crate::body::MessageBody; +use crate::config::{KeepAlive, ServiceConfig}; +use crate::error::{DispatchError, ParseError}; +use crate::request::Request; +use crate::response::Response; + +// use super::dispatcher::Dispatcher; +use super::H2ServiceResult; + +/// `NewService` implementation for HTTP2 transport +pub struct H2Service { + srv: S, + cfg: ServiceConfig, + _t: PhantomData<(T, B)>, +} + +impl H2Service +where + S: NewService> + Clone, + S::Service: Clone, + S::Error: Debug, + B: MessageBody, +{ + /// Create new `HttpService` instance. + pub fn new>(service: F) -> Self { + let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); + + H2Service { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } + + /// Create builder for `HttpService` instance. + pub fn build() -> H2ServiceBuilder { + H2ServiceBuilder::new() + } +} + +impl NewService for H2Service +where + T: AsyncRead + AsyncWrite, + S: NewService> + Clone, + S::Service: Clone, + S::Error: Debug, + B: MessageBody, +{ + type Request = T; + type Response = H2ServiceResult; + type Error = (); //DispatchError; + type InitError = S::InitError; + type Service = H2ServiceHandler; + type Future = H2ServiceResponse; + + fn new_service(&self) -> Self::Future { + H2ServiceResponse { + fut: self.srv.new_service(), + cfg: Some(self.cfg.clone()), + _t: PhantomData, + } + } +} + +/// A http/2 new service builder +/// +/// This type can be used to construct an instance of `ServiceConfig` through a +/// builder-like pattern. +pub struct H2ServiceBuilder { + keep_alive: KeepAlive, + client_timeout: u64, + client_disconnect: u64, + host: String, + addr: net::SocketAddr, + secure: bool, + _t: PhantomData<(T, S)>, +} + +impl H2ServiceBuilder +where + S: NewService, + S::Service: Clone, + S::Error: Debug, +{ + /// Create instance of `H2ServiceBuilder` + pub fn new() -> H2ServiceBuilder { + H2ServiceBuilder { + keep_alive: KeepAlive::Timeout(5), + client_timeout: 5000, + client_disconnect: 0, + secure: false, + host: "localhost".to_owned(), + addr: "127.0.0.1:8080".parse().unwrap(), + _t: PhantomData, + } + } + + /// Enable secure flag for current server. + /// This flags also enables `client disconnect timeout`. + /// + /// By default this flag is set to false. + pub fn secure(mut self) -> Self { + self.secure = true; + if self.client_disconnect == 0 { + self.client_disconnect = 3000; + } + self + } + + /// Set server keep-alive setting. + /// + /// By default keep alive is set to a 5 seconds. + pub fn keep_alive>(mut self, val: U) -> Self { + self.keep_alive = val.into(); + self + } + + /// Set server client timeout in milliseconds for first request. + /// + /// Defines a timeout for reading client request header. If a client does not transmit + /// the entire set headers within this time, the request is terminated with + /// the 408 (Request Time-out) error. + /// + /// To disable timeout set value to 0. + /// + /// By default client timeout is set to 5000 milliseconds. + pub fn client_timeout(mut self, val: u64) -> Self { + self.client_timeout = val; + self + } + + /// Set server connection disconnect timeout in milliseconds. + /// + /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete + /// within this time, the request get dropped. This timeout affects secure connections. + /// + /// To disable timeout set value to 0. + /// + /// By default disconnect timeout is set to 3000 milliseconds. + pub fn client_disconnect(mut self, val: u64) -> Self { + self.client_disconnect = val; + self + } + + /// Set server host name. + /// + /// Host name is used by application router aa a hostname for url + /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. + /// html#method.host) documentation for more information. + /// + /// By default host name is set to a "localhost" value. + pub fn server_hostname(mut self, val: &str) -> Self { + self.host = val.to_owned(); + self + } + + /// Set server ip address. + /// + /// Host name is used by application router aa a hostname for url + /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. + /// html#method.host) documentation for more information. + /// + /// By default server address is set to a "127.0.0.1:8080" + pub fn server_address(mut self, addr: U) -> Self { + match addr.to_socket_addrs() { + Err(err) => error!("Can not convert to SocketAddr: {}", err), + Ok(mut addrs) => { + if let Some(addr) = addrs.next() { + self.addr = addr; + } + } + } + self + } + + /// Finish service configuration and create `H1Service` instance. + pub fn finish(self, service: F) -> H2Service + where + B: MessageBody, + F: IntoNewService, + { + let cfg = ServiceConfig::new( + self.keep_alive, + self.client_timeout, + self.client_disconnect, + ); + H2Service { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } +} + +#[doc(hidden)] +pub struct H2ServiceResponse { + fut: S::Future, + cfg: Option, + _t: PhantomData<(T, B)>, +} + +impl Future for H2ServiceResponse +where + T: AsyncRead + AsyncWrite, + S: NewService>, + S::Service: Clone, + S::Error: Debug, + B: MessageBody, +{ + type Item = H2ServiceHandler; + type Error = S::InitError; + + fn poll(&mut self) -> Poll { + let service = try_ready!(self.fut.poll()); + Ok(Async::Ready(H2ServiceHandler::new( + self.cfg.take().unwrap(), + service, + ))) + } +} + +/// `Service` implementation for http/2 transport +pub struct H2ServiceHandler { + srv: S, + cfg: ServiceConfig, + _t: PhantomData<(T, B)>, +} + +impl H2ServiceHandler +where + S: Service> + Clone, + S::Error: Debug, + B: MessageBody, +{ + fn new(cfg: ServiceConfig, srv: S) -> H2ServiceHandler { + H2ServiceHandler { + srv, + cfg, + _t: PhantomData, + } + } +} + +impl Service for H2ServiceHandler +where + T: AsyncRead + AsyncWrite, + S: Service> + Clone, + S::Error: Debug, + B: MessageBody, +{ + type Request = T; + type Response = H2ServiceResult; + type Error = (); // DispatchError; + type Future = H2ServiceHandlerResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.srv.poll_ready().map_err(|_| ()) + } + + fn call(&mut self, req: T) -> Self::Future { + H2ServiceHandlerResponse { + state: State::Handshake(server::handshake(req)), + _t: PhantomData, + } + } +} + +enum State { + Handshake(Handshake), + Connection(Connection), + Empty, +} + +pub struct H2ServiceHandlerResponse +where + T: AsyncRead + AsyncWrite, + S: Service> + Clone, + S::Error: Debug, + B: MessageBody, +{ + state: State, + _t: PhantomData, +} + +impl Future for H2ServiceHandlerResponse +where + T: AsyncRead + AsyncWrite, + S: Service> + Clone, + S::Error: Debug, + B: MessageBody, +{ + type Item = H2ServiceResult; + type Error = (); + + fn poll(&mut self) -> Poll { + unimplemented!() + } +} diff --git a/src/lib.rs b/src/lib.rs index 442637251..cdb4f0382 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,7 @@ mod service; pub mod error; pub mod h1; +pub mod h2; pub mod test; pub mod ws; diff --git a/src/service/senderror.rs b/src/service/senderror.rs index 57828187d..b469a61e6 100644 --- a/src/service/senderror.rs +++ b/src/service/senderror.rs @@ -22,11 +22,12 @@ where } } -impl NewService)>> for SendError +impl NewService for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, { + type Request = Result)>; type Response = R; type Error = (E, Framed); type InitError = (); @@ -38,11 +39,12 @@ where } } -impl Service)>> for SendError +impl Service for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, { + type Request = Result)>; type Response = R; type Error = (E, Framed); type Future = Either)>, SendErrorFut>; @@ -128,11 +130,12 @@ where } } -impl NewService<(Response, Framed)> for SendResponse +impl NewService for SendResponse where T: AsyncRead + AsyncWrite, B: MessageBody, { + type Request = (Response, Framed); type Response = Framed; type Error = Error; type InitError = (); @@ -144,11 +147,12 @@ where } } -impl Service<(Response, Framed)> for SendResponse +impl Service for SendResponse where T: AsyncRead + AsyncWrite, B: MessageBody, { + type Request = (Response, Framed); type Response = Framed; type Error = Error; type Future = SendResponseFut; diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index c48b6e0c1..586873d19 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -26,7 +26,7 @@ pub type DefaultClient = Client; /// WebSocket's client pub struct Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { connector: T, @@ -34,7 +34,7 @@ where impl Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { /// Create new websocket's client factory @@ -51,7 +51,7 @@ impl Default for Client { impl Clone for Client where - T: Service + Clone, + T: Service + Clone, T::Response: AsyncRead + AsyncWrite, { fn clone(&self) -> Self { @@ -61,12 +61,13 @@ where } } -impl Service for Client +impl Service for Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite + 'static, T::Future: 'static, { + type Request = Connect; type Response = Framed; type Error = ClientError; type Future = Either< diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 32ad4ef4f..d4c15627f 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -117,7 +117,7 @@ impl Parser { // control frames must have length <= 125 match opcode { OpCode::Ping | OpCode::Pong if length > 125 => { - return Err(ProtocolError::InvalidLength(length)) + return Err(ProtocolError::InvalidLength(length)); } OpCode::Close if length > 125 => { debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); diff --git a/src/ws/service.rs b/src/ws/service.rs index 8189b1955..137d41d43 100644 --- a/src/ws/service.rs +++ b/src/ws/service.rs @@ -20,7 +20,8 @@ impl Default for VerifyWebSockets { } } -impl NewService<(Request, Framed)> for VerifyWebSockets { +impl NewService for VerifyWebSockets { + type Request = (Request, Framed); type Response = (Request, Framed); type Error = (HandshakeError, Framed); type InitError = (); @@ -32,7 +33,8 @@ impl NewService<(Request, Framed)> for VerifyWebSockets { } } -impl Service<(Request, Framed)> for VerifyWebSockets { +impl Service for VerifyWebSockets { + type Request = (Request, Framed); type Response = (Request, Framed); type Error = (HandshakeError, Framed); type Future = FutureResult; diff --git a/src/ws/transport.rs b/src/ws/transport.rs index 6a4f4d227..da7782be5 100644 --- a/src/ws/transport.rs +++ b/src/ws/transport.rs @@ -7,7 +7,7 @@ use super::{Codec, Frame, Message}; pub struct Transport where - S: Service + 'static, + S: Service + 'static, T: AsyncRead + AsyncWrite, { inner: FramedTransport, @@ -16,17 +16,17 @@ where impl Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { - pub fn new>(io: T, service: F) -> Self { + pub fn new>(io: T, service: F) -> Self { Transport { inner: FramedTransport::new(Framed::new(io, Codec::new()), service), } } - pub fn with>(framed: Framed, service: F) -> Self { + pub fn with>(framed: Framed, service: F) -> Self { Transport { inner: FramedTransport::new(framed, service), } @@ -36,7 +36,7 @@ where impl Future for Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 851b3efe4..81a3d909c 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -30,11 +30,11 @@ session = ["cookie/secure"] [dependencies] actix-codec = "0.1" -actix-service = "0.1.6" +actix-service = "0.2.0" actix-rt = "0.1.0" -actix-server = "0.1.0" +actix-server = "0.2.0" +actix-utils = "0.2.0" actix-http = { path=".." } -actix-utils = { git = "https://github.com/actix/actix-net.git" } base64 = "0.10" bytes = "0.4" diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 1ef452044..8083ebb15 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -57,7 +57,8 @@ impl TestServer { pub fn with_factory( factory: F, ) -> TestServerRuntime< - impl Service + Clone, + impl Service + + Clone, > { let (tx, rx) = mpsc::channel(); @@ -89,8 +90,11 @@ impl TestServer { } fn new_connector( - ) -> impl Service + Clone - { + ) -> impl Service< + Request = Connect, + Response = impl Connection, + Error = ConnectorError, + > + Clone { #[cfg(feature = "ssl")] { use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; @@ -191,7 +195,7 @@ impl TestServerRuntime { impl TestServerRuntime where - T: Service + Clone, + T: Service + Clone, T::Response: Connection, { /// Connect to websocket server at a given path From e70c7f2a5d7d5dac8e66fa7c1e180a58f09bad14 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Feb 2019 20:22:43 -0800 Subject: [PATCH 116/208] upgrade derive-more --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f41147819..a039b4557 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ bitflags = "1.0" bytes = "0.4" byteorder = "1.2" cookie = { version="0.11", features=["percent-encode"] } -derive_more = "0.13" +derive_more = "0.14" encoding = "0.2" futures = "0.1" hashbrown = "0.1.8" From c9bb2116feb8ac6c6c40a7f8f63e03dff8c973d5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Feb 2019 10:50:29 -0800 Subject: [PATCH 117/208] update actix-utils --- Cargo.toml | 4 ++++ src/client/connector.rs | 14 +++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a039b4557..ff19a4f96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,3 +80,7 @@ actix-server = "0.2" actix-http-test = { path="test-server" } env_logger = "0.6" serde_derive = "1.0" + +[patch.crates-io] +actix-utils = { git = "https://github.com/actix/actix-net.git" } +actix-service = { git = "https://github.com/actix/actix-net.git" } diff --git a/src/client/connector.rs b/src/client/connector.rs index fea9b9c0a..8e3c4b5ae 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -2,7 +2,7 @@ use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; use actix_connector::{Resolver, TcpConnector}; -use actix_service::{Service, ServiceExt}; +use actix_service::{Apply, Service, ServiceExt}; use actix_utils::timeout::{TimeoutError, TimeoutService}; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; @@ -142,8 +142,8 @@ impl Connector { > + Clone { #[cfg(not(feature = "ssl"))] { - let connector = TimeoutService::new( - self.timeout, + let connector = Apply::new( + TimeoutService::new(self.timeout), self.resolver.map_err(ConnectorError::from).and_then( TcpConnector::default() .from_err() @@ -167,8 +167,8 @@ impl Connector { } #[cfg(feature = "ssl")] { - let ssl_service = TimeoutService::new( - self.timeout, + let ssl_service = Apply::new( + TimeoutService::new(self.timeout), self.resolver .clone() .map_err(ConnectorError::from) @@ -196,8 +196,8 @@ impl Connector { TimeoutError::Timeout => ConnectorError::Timeout, }); - let tcp_service = TimeoutService::new( - self.timeout, + let tcp_service = Apply::new( + TimeoutService::new(self.timeout), self.resolver.map_err(ConnectorError::from).and_then( TcpConnector::default() .from_err() From ef5b54a48151f457a8ca47c6683b44feb3dd8525 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Feb 2019 14:05:44 -0800 Subject: [PATCH 118/208] use released service crate --- Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ff19a4f96..37e6a066c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix-service = "0.2.0" +actix-service = "0.2.1" actix-codec = "0.1.0" actix-connector = "0.2.0" actix-utils = "0.2.0" @@ -83,4 +83,3 @@ serde_derive = "1.0" [patch.crates-io] actix-utils = { git = "https://github.com/actix/actix-net.git" } -actix-service = { git = "https://github.com/actix/actix-net.git" } From 55a29d37782443ccf7485caf3c7a7dd270bd60f7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Feb 2019 11:44:15 -0800 Subject: [PATCH 119/208] add h2 server support --- .travis.yml | 2 +- Cargo.toml | 7 +- src/body.rs | 21 +++ src/client/connector.rs | 11 +- src/client/h2proto.rs | 57 +++++-- src/client/response.rs | 2 +- src/config.rs | 4 + src/error.rs | 10 ++ src/h1/dispatcher.rs | 4 +- src/h2/dispatcher.rs | 325 ++++++++++++++++++++++++++++++++++++++++ src/h2/mod.rs | 42 ++++++ src/h2/service.rs | 136 +++++++++++------ src/httpmessage.rs | 20 +-- src/json.rs | 2 +- src/message.rs | 4 - src/request.rs | 73 ++++++--- src/test.rs | 21 +-- test-server/Cargo.toml | 5 + test-server/src/lib.rs | 25 +++- tests/cert.pem | 43 ++---- tests/key.pem | 79 ++++------ tests/test_server.rs | 85 ++++++++++- 22 files changed, 774 insertions(+), 204 deletions(-) create mode 100644 src/h2/dispatcher.rs diff --git a/.travis.yml b/.travis.yml index b7b43895e..c9c9db14f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then cargo clean - cargo test + cargo test --features="ssl" fi - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then diff --git a/Cargo.toml b/Cargo.toml index 37e6a066c..bbb31c161 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,13 +34,13 @@ default = ["session"] session = ["cookie/secure"] # openssl -ssl = ["openssl", "actix-connector/ssl"] +ssl = ["openssl", "actix-connector/ssl", "actix-server/ssl", "actix-http-test/ssl"] [dependencies] actix-service = "0.2.1" actix-codec = "0.1.0" actix-connector = "0.2.0" -actix-utils = "0.2.0" +actix-utils = "0.2.1" base64 = "0.10" backtrace = "0.3" @@ -80,6 +80,3 @@ actix-server = "0.2" actix-http-test = { path="test-server" } env_logger = "0.6" serde_derive = "1.0" - -[patch.crates-io] -actix-utils = { git = "https://github.com/actix/actix-net.git" } diff --git a/src/body.rs b/src/body.rs index 12e2d0345..1c54d4ce7 100644 --- a/src/body.rs +++ b/src/body.rs @@ -20,6 +20,18 @@ pub enum BodyLength { Stream, } +impl BodyLength { + pub fn is_eof(&self) -> bool { + match self { + BodyLength::None + | BodyLength::Empty + | BodyLength::Sized(0) + | BodyLength::Sized64(0) => true, + _ => false, + } + } +} + /// Type that provides this trait can be streamed to a peer. pub trait MessageBody { fn length(&self) -> BodyLength; @@ -42,6 +54,15 @@ pub enum ResponseBody { Other(Body), } +impl ResponseBody { + pub fn into_body(self) -> ResponseBody { + match self { + ResponseBody::Body(b) => ResponseBody::Other(b), + ResponseBody::Other(b) => ResponseBody::Other(b), + } + } +} + impl ResponseBody { pub fn as_ref(&self) -> Option<&B> { if let ResponseBody::Body(ref b) = self { diff --git a/src/client/connector.rs b/src/client/connector.rs index 8e3c4b5ae..32ba50121 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -12,11 +12,7 @@ use super::error::ConnectorError; use super::pool::{ConnectionPool, Protocol}; #[cfg(feature = "ssl")] -use actix_connector::ssl::OpensslConnector; -#[cfg(feature = "ssl")] -use openssl::ssl::{SslConnector, SslMethod}; -#[cfg(feature = "ssl")] -const H2: &[u8] = b"h2"; +use openssl::ssl::SslConnector; #[cfg(not(feature = "ssl"))] type SslConnector = (); @@ -40,6 +36,8 @@ impl Default for Connector { #[cfg(feature = "ssl")] { use log::error; + use openssl::ssl::{SslConnector, SslMethod}; + let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); let _ = ssl .set_alpn_protos(b"\x02h2\x08http/1.1") @@ -167,6 +165,9 @@ impl Connector { } #[cfg(feature = "ssl")] { + const H2: &[u8] = b"h2"; + use actix_connector::ssl::OpensslConnector; + let ssl_service = Apply::new( TimeoutService::new(self.timeout), self.resolver diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index e3d5be0b0..ecd18cf82 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -4,16 +4,19 @@ use std::time; use actix_codec::{AsyncRead, AsyncWrite}; use bytes::Bytes; use futures::future::{err, Either}; -use futures::{Async, Future, Poll, Stream}; +use futures::{Async, Future, Poll}; use h2::{client::SendRequest, SendStream}; -use http::{request::Request, Version}; +use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; +use http::{request::Request, HttpTryFrom, Version}; + +use crate::body::{BodyLength, MessageBody}; +use crate::h2::Payload; +use crate::message::{RequestHead, ResponseHead}; use super::connection::{ConnectionType, IoConnection}; use super::error::SendRequestError; use super::pool::Acquired; use super::response::ClientResponse; -use crate::body::{BodyLength, MessageBody}; -use crate::message::{RequestHead, ResponseHead}; pub(crate) fn send_request( io: SendRequest, @@ -27,7 +30,8 @@ where B: MessageBody, { trace!("Sending client request: {:?} {:?}", head, body.length()); - let eof = match body.length() { + let length = body.length(); + let eof = match length { BodyLength::None | BodyLength::Empty | BodyLength::Sized(0) => true, _ => false, }; @@ -38,11 +42,44 @@ where let mut req = Request::new(()); *req.uri_mut() = head.uri; *req.method_mut() = head.method; - *req.headers_mut() = head.headers; *req.version_mut() = Version::HTTP_2; + let mut skip_len = true; + let mut has_date = false; + + // Content length + let _ = match length { + BodyLength::Chunked | BodyLength::None => None, + BodyLength::Stream => { + skip_len = false; + None + } + BodyLength::Empty => req + .headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), + BodyLength::Sized(len) => req.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::try_from(format!("{}", len)).unwrap(), + ), + BodyLength::Sized64(len) => req.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::try_from(format!("{}", len)).unwrap(), + ), + }; + + // copy headers + for (key, value) in head.headers.iter() { + match *key { + CONNECTION | TRANSFER_ENCODING => continue, // http2 specific + CONTENT_LENGTH if skip_len => continue, + DATE => has_date = true, + _ => (), + } + req.headers_mut().append(key, value.clone()); + } + match io.send_request(req, eof) { - Ok((resp, send)) => { + Ok((res, send)) => { release(io, pool, created, false); if !eof { @@ -52,10 +89,10 @@ where send, buf: None, } - .and_then(move |_| resp.map_err(SendRequestError::from)), + .and_then(move |_| res.map_err(SendRequestError::from)), )) } else { - Either::B(resp.map_err(SendRequestError::from)) + Either::B(res.map_err(SendRequestError::from)) } } Err(e) => { @@ -74,7 +111,7 @@ where Ok(ClientResponse { head, - payload: RefCell::new(Some(Box::new(body.from_err()))), + payload: RefCell::new(Some(Box::new(Payload::new(body)))), }) }) .from_err() diff --git a/src/client/response.rs b/src/client/response.rs index 9010a3c56..6224d3cb5 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -27,7 +27,7 @@ impl HttpMessage for ClientResponse { } #[inline] - fn payload(&self) -> Self::Stream { + fn payload(self) -> Self::Stream { if let Some(payload) = self.payload.borrow_mut().take() { payload } else { diff --git a/src/config.rs b/src/config.rs index c37601dbe..960f13706 100644 --- a/src/config.rs +++ b/src/config.rs @@ -171,6 +171,10 @@ impl ServiceConfig { buf[35..].copy_from_slice(b"\r\n\r\n"); dst.extend_from_slice(&buf); } + + pub(crate) fn set_date_header(&self, dst: &mut BytesMut) { + dst.extend_from_slice(&self.0.timer.date().bytes); + } } /// A service config builder diff --git a/src/error.rs b/src/error.rs index 43bf7cfb8..03224b558 100644 --- a/src/error.rs +++ b/src/error.rs @@ -389,6 +389,10 @@ pub enum DispatchError { #[display(fmt = "Parse error: {}", _0)] Parse(ParseError), + /// Http/2 error + #[display(fmt = "{}", _0)] + H2(h2::Error), + /// The first request did not complete within the specified timeout. #[display(fmt = "The first request did not complete within the specified timeout")] SlowRequestTimeout, @@ -426,6 +430,12 @@ impl From for DispatchError { } } +impl From for DispatchError { + fn from(err: h2::Error) -> Self { + DispatchError::H2(err) + } +} + /// A set of error that can occure during parsing content type #[derive(PartialEq, Debug, Display)] pub enum ContentTypeError { diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 7780223f2..1295dfddf 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -309,11 +309,11 @@ where self.flags.insert(Flags::STARTED); match msg { - Message::Item(req) => { + Message::Item(mut req) => { match self.framed.get_codec().message_type() { MessageType::Payload => { let (ps, pl) = Payload::create(false); - *req.inner.payload.borrow_mut() = Some(pl); + req = req.set_payload(pl); self.payload = Some(ps); } MessageType::Stream => { diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs new file mode 100644 index 000000000..2994d0a3d --- /dev/null +++ b/src/h2/dispatcher.rs @@ -0,0 +1,325 @@ +use std::collections::VecDeque; +use std::marker::PhantomData; +use std::time::Instant; +use std::{fmt, mem}; + +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_service::Service; +use bitflags::bitflags; +use bytes::{Bytes, BytesMut}; +use futures::{try_ready, Async, Future, Poll, Sink, Stream}; +use h2::server::{Connection, SendResponse}; +use h2::{RecvStream, SendStream}; +use http::header::{ + HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, +}; +use http::HttpTryFrom; +use log::{debug, error, trace}; +use tokio_timer::Delay; + +use crate::body::{Body, BodyLength, MessageBody, ResponseBody}; +use crate::config::ServiceConfig; +use crate::error::{DispatchError, Error, ParseError, PayloadError, ResponseError}; +use crate::message::ResponseHead; +use crate::request::Request; +use crate::response::Response; + +use super::{H2ServiceResult, Payload}; + +const CHUNK_SIZE: usize = 16_384; + +bitflags! { + struct Flags: u8 { + const DISCONNECTED = 0b0000_0001; + const SHUTDOWN = 0b0000_0010; + } +} + +/// Dispatcher for HTTP/2 protocol +pub struct Dispatcher { + flags: Flags, + service: S, + connection: Connection, + config: ServiceConfig, + ka_expire: Instant, + ka_timer: Option, + _t: PhantomData, +} + +impl Dispatcher +where + T: AsyncRead + AsyncWrite, + S: Service, Response = Response> + 'static, + S::Error: Into + fmt::Debug, + B: MessageBody + 'static, +{ + pub fn new( + service: S, + connection: Connection, + config: ServiceConfig, + timeout: Option, + ) -> Self { + let keepalive = config.keep_alive_enabled(); + // let flags = if keepalive { + // Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED + // } else { + // Flags::empty() + // }; + + // keep-alive timer + let (ka_expire, ka_timer) = if let Some(delay) = timeout { + (delay.deadline(), Some(delay)) + } else if let Some(delay) = config.keep_alive_timer() { + (delay.deadline(), Some(delay)) + } else { + (config.now(), None) + }; + + Dispatcher { + service, + config, + ka_expire, + ka_timer, + connection, + flags: Flags::empty(), + _t: PhantomData, + } + } +} + +impl Future for Dispatcher +where + T: AsyncRead + AsyncWrite, + S: Service, Response = Response> + 'static, + S::Error: Into + fmt::Debug, + B: MessageBody + 'static, +{ + type Item = (); + type Error = DispatchError<()>; + + #[inline] + fn poll(&mut self) -> Poll { + loop { + match self.connection.poll()? { + Async::Ready(None) => { + self.flags.insert(Flags::DISCONNECTED); + } + Async::Ready(Some((req, res))) => { + // update keep-alive expire + if self.ka_timer.is_some() { + if let Some(expire) = self.config.keep_alive_expire() { + self.ka_expire = expire; + } + } + + let (parts, body) = req.into_parts(); + let mut req = Request::with_payload(Payload::new(body)); + + let head = &mut req.inner_mut().head; + head.uri = parts.uri; + head.method = parts.method; + head.version = parts.version; + head.headers = parts.headers; + tokio_current_thread::spawn(ServiceResponse:: { + state: ServiceResponseState::ServiceCall( + self.service.call(req), + Some(res), + ), + config: self.config.clone(), + buffer: None, + }) + } + Async::NotReady => return Ok(Async::NotReady), + } + } + } +} + +struct ServiceResponse { + state: ServiceResponseState, + config: ServiceConfig, + buffer: Option, +} + +enum ServiceResponseState { + ServiceCall(S::Future, Option>), + SendPayload(SendStream, ResponseBody), +} + +impl ServiceResponse +where + S: Service, Response = Response> + 'static, + S::Error: Into + fmt::Debug, + B: MessageBody + 'static, +{ + fn prepare_response( + &self, + head: &ResponseHead, + length: &mut BodyLength, + ) -> http::Response<()> { + let mut has_date = false; + let mut skip_len = length != &BodyLength::Stream; + + let mut res = http::Response::new(()); + *res.status_mut() = head.status; + *res.version_mut() = http::Version::HTTP_2; + + // Content length + match head.status { + http::StatusCode::NO_CONTENT + | http::StatusCode::CONTINUE + | http::StatusCode::PROCESSING => *length = BodyLength::None, + http::StatusCode::SWITCHING_PROTOCOLS => { + skip_len = true; + *length = BodyLength::Stream; + } + _ => (), + } + let _ = match length { + BodyLength::Chunked | BodyLength::None | BodyLength::Stream => None, + BodyLength::Empty => res + .headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), + BodyLength::Sized(len) => res.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::try_from(format!("{}", len)).unwrap(), + ), + BodyLength::Sized64(len) => res.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::try_from(format!("{}", len)).unwrap(), + ), + }; + + // copy headers + for (key, value) in head.headers.iter() { + match *key { + CONNECTION | TRANSFER_ENCODING => continue, // http2 specific + CONTENT_LENGTH if skip_len => continue, + DATE => has_date = true, + _ => (), + } + res.headers_mut().append(key, value.clone()); + } + + // set date header + if !has_date { + let mut bytes = BytesMut::with_capacity(29); + self.config.set_date_header(&mut bytes); + res.headers_mut() + .insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); + } + + res + } +} + +impl Future for ServiceResponse +where + S: Service, Response = Response> + 'static, + S::Error: Into + fmt::Debug, + B: MessageBody + 'static, +{ + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + match self.state { + ServiceResponseState::ServiceCall(ref mut call, ref mut send) => { + match call.poll() { + Ok(Async::Ready(res)) => { + let (res, body) = res.replace_body(()); + + let mut send = send.take().unwrap(); + let mut length = body.length(); + let h2_res = self.prepare_response(res.head(), &mut length); + + let stream = send + .send_response(h2_res, length.is_eof()) + .map_err(|e| { + trace!("Error sending h2 response: {:?}", e); + })?; + + if length.is_eof() { + Ok(Async::Ready(())) + } else { + self.state = ServiceResponseState::SendPayload(stream, body); + self.poll() + } + } + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(e) => { + let res: Response = e.into().into(); + let (res, body) = res.replace_body(()); + + let mut send = send.take().unwrap(); + let mut length = body.length(); + let h2_res = self.prepare_response(res.head(), &mut length); + + let stream = send + .send_response(h2_res, length.is_eof()) + .map_err(|e| { + trace!("Error sending h2 response: {:?}", e); + })?; + + if length.is_eof() { + Ok(Async::Ready(())) + } else { + self.state = ServiceResponseState::SendPayload( + stream, + body.into_body(), + ); + self.poll() + } + } + } + } + ServiceResponseState::SendPayload(ref mut stream, ref mut body) => loop { + loop { + if let Some(ref mut buffer) = self.buffer { + match stream.poll_capacity().map_err(|e| warn!("{:?}", e))? { + Async::NotReady => return Ok(Async::NotReady), + Async::Ready(None) => return Ok(Async::Ready(())), + Async::Ready(Some(cap)) => { + let len = buffer.len(); + let bytes = buffer.split_to(std::cmp::min(cap, len)); + + if let Err(e) = stream.send_data(bytes, false) { + warn!("{:?}", e); + return Err(()); + } else if !buffer.is_empty() { + let cap = std::cmp::min(buffer.len(), CHUNK_SIZE); + stream.reserve_capacity(cap); + } else { + self.buffer.take(); + } + } + } + } else { + match body.poll_next() { + Ok(Async::NotReady) => return Ok(Async::NotReady), + Ok(Async::Ready(None)) => { + if let Err(e) = stream.send_data(Bytes::new(), true) { + warn!("{:?}", e); + return Err(()); + } else { + return Ok(Async::Ready(())); + } + } + Ok(Async::Ready(Some(chunk))) => { + stream.reserve_capacity(std::cmp::min( + chunk.len(), + CHUNK_SIZE, + )); + self.buffer = Some(chunk); + } + Err(e) => { + error!("Response payload stream error: {:?}", e); + return Err(()); + } + } + } + } + }, + } + } +} diff --git a/src/h2/mod.rs b/src/h2/mod.rs index 4a54ec9fe..55e057607 100644 --- a/src/h2/mod.rs +++ b/src/h2/mod.rs @@ -1,7 +1,17 @@ +#![allow(dead_code, unused_imports)] + use std::fmt; +use bytes::Bytes; +use futures::{Async, Poll, Stream}; +use h2::RecvStream; + +mod dispatcher; mod service; +pub use self::service::H2Service; +use crate::error::PayloadError; + /// H1 service response type pub enum H2ServiceResult { Disconnected, @@ -18,3 +28,35 @@ impl fmt::Debug for H2ServiceResult { } } } + +/// H2 receive stream +pub struct Payload { + pl: RecvStream, +} + +impl Payload { + pub(crate) fn new(pl: RecvStream) -> Self { + Self { pl } + } +} + +impl Stream for Payload { + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll, Self::Error> { + match self.pl.poll() { + Ok(Async::Ready(Some(chunk))) => { + let len = chunk.len(); + if let Err(err) = self.pl.release_capacity().release_capacity(len) { + Err(err.into()) + } else { + Ok(Async::Ready(Some(chunk))) + } + } + Ok(Async::Ready(None)) => Ok(Async::Ready(None)), + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(err) => Err(err.into()), + } + } +} diff --git a/src/h2/service.rs b/src/h2/service.rs index 827f84488..b598b0a6d 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; use std::marker::PhantomData; -use std::net; +use std::{io, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::{IntoNewService, NewService, Service}; @@ -8,16 +8,17 @@ use bytes::Bytes; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, Poll, Stream}; use h2::server::{self, Connection, Handshake}; +use h2::RecvStream; use log::error; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; -use crate::error::{DispatchError, ParseError}; +use crate::error::{DispatchError, Error, ParseError, ResponseError}; use crate::request::Request; use crate::response::Response; -// use super::dispatcher::Dispatcher; -use super::H2ServiceResult; +use super::dispatcher::Dispatcher; +use super::{H2ServiceResult, Payload}; /// `NewService` implementation for HTTP2 transport pub struct H2Service { @@ -28,10 +29,10 @@ pub struct H2Service { impl H2Service where - S: NewService> + Clone, - S::Service: Clone, - S::Error: Debug, - B: MessageBody, + S: NewService, Response = Response> + Clone, + S::Service: Clone + 'static, + S::Error: Into + Debug + 'static, + B: MessageBody + 'static, { /// Create new `HttpService` instance. pub fn new>(service: F) -> Self { @@ -53,14 +54,14 @@ where impl NewService for H2Service where T: AsyncRead + AsyncWrite, - S: NewService> + Clone, - S::Service: Clone, - S::Error: Debug, - B: MessageBody, + S: NewService, Response = Response> + Clone, + S::Service: Clone + 'static, + S::Error: Into + Debug, + B: MessageBody + 'static, { type Request = T; - type Response = H2ServiceResult; - type Error = (); //DispatchError; + type Response = (); + type Error = DispatchError<()>; type InitError = S::InitError; type Service = H2ServiceHandler; type Future = H2ServiceResponse; @@ -90,9 +91,9 @@ pub struct H2ServiceBuilder { impl H2ServiceBuilder where - S: NewService, - S::Service: Clone, - S::Error: Debug, + S: NewService>, + S::Service: Clone + 'static, + S::Error: Into + Debug + 'static, { /// Create instance of `H2ServiceBuilder` pub fn new() -> H2ServiceBuilder { @@ -185,6 +186,25 @@ where self } + // #[cfg(feature = "ssl")] + // /// Configure alpn protocols for SslAcceptorBuilder. + // pub fn configure_openssl( + // builder: &mut openssl::ssl::SslAcceptorBuilder, + // ) -> io::Result<()> { + // let protos: &[u8] = b"\x02h2"; + // builder.set_alpn_select_callback(|_, protos| { + // const H2: &[u8] = b"\x02h2"; + // if protos.windows(3).any(|window| window == H2) { + // Ok(b"h2") + // } else { + // Err(openssl::ssl::AlpnError::NOACK) + // } + // }); + // builder.set_alpn_protos(&protos)?; + + // Ok(()) + // } + /// Finish service configuration and create `H1Service` instance. pub fn finish(self, service: F) -> H2Service where @@ -214,10 +234,10 @@ pub struct H2ServiceResponse { impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService>, - S::Service: Clone, - S::Error: Debug, - B: MessageBody, + S: NewService, Response = Response>, + S::Service: Clone + 'static, + S::Error: Into + Debug, + B: MessageBody + 'static, { type Item = H2ServiceHandler; type Error = S::InitError; @@ -240,9 +260,9 @@ pub struct H2ServiceHandler { impl H2ServiceHandler where - S: Service> + Clone, - S::Error: Debug, - B: MessageBody, + S: Service, Response = Response> + Clone + 'static, + S::Error: Into + Debug, + B: MessageBody + 'static, { fn new(cfg: ServiceConfig, srv: S) -> H2ServiceHandler { H2ServiceHandler { @@ -256,55 +276,79 @@ where impl Service for H2ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service> + Clone, - S::Error: Debug, - B: MessageBody, + S: Service, Response = Response> + Clone + 'static, + S::Error: Into + Debug, + B: MessageBody + 'static, { type Request = T; - type Response = H2ServiceResult; - type Error = (); // DispatchError; + type Response = (); + type Error = DispatchError<()>; type Future = H2ServiceHandlerResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.srv.poll_ready().map_err(|_| ()) + self.srv.poll_ready().map_err(|e| { + error!("Service readiness error: {:?}", e); + DispatchError::Service(()) + }) } fn call(&mut self, req: T) -> Self::Future { H2ServiceHandlerResponse { - state: State::Handshake(server::handshake(req)), - _t: PhantomData, + state: State::Handshake( + Some(self.srv.clone()), + Some(self.cfg.clone()), + server::handshake(req), + ), } } } -enum State { - Handshake(Handshake), - Connection(Connection), - Empty, +enum State { + Incoming(Dispatcher), + Handshake(Option, Option, Handshake), } pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service> + Clone, - S::Error: Debug, - B: MessageBody, + S: Service, Response = Response> + Clone + 'static, + S::Error: Into + Debug, + B: MessageBody + 'static, { - state: State, - _t: PhantomData, + state: State, } impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service> + Clone, - S::Error: Debug, + S: Service, Response = Response> + Clone, + S::Error: Into + Debug, B: MessageBody, { - type Item = H2ServiceResult; - type Error = (); + type Item = (); + type Error = DispatchError<()>; fn poll(&mut self) -> Poll { - unimplemented!() + match self.state { + State::Incoming(ref mut disp) => disp.poll(), + State::Handshake(ref mut srv, ref mut config, ref mut handshake) => { + match handshake.poll() { + Ok(Async::Ready(conn)) => { + self.state = State::Incoming(Dispatcher::new( + srv.take().unwrap(), + conn, + config.take().unwrap(), + None, + )); + self.poll() + } + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(err) => { + trace!("H2 handshake error: {}", err); + return Err(err.into()); + } + } + } + } } } diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 589617fc9..c50de2e94 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -25,7 +25,7 @@ pub trait HttpMessage: Sized { fn headers(&self) -> &HeaderMap; /// Message payload stream - fn payload(&self) -> Self::Stream; + fn payload(self) -> Self::Stream; #[doc(hidden)] /// Get a header @@ -128,7 +128,7 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn body(&self) -> MessageBody { + fn body(self) -> MessageBody { MessageBody::new(self) } @@ -162,7 +162,7 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn urlencoded(&self) -> UrlEncoded { + fn urlencoded(self) -> UrlEncoded { UrlEncoded::new(self) } @@ -198,12 +198,12 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn json(&self) -> JsonBody { + fn json(self) -> JsonBody { JsonBody::new(self) } /// Return stream of lines. - fn readlines(&self) -> Readlines { + fn readlines(self) -> Readlines { Readlines::new(self) } } @@ -220,7 +220,7 @@ pub struct Readlines { impl Readlines { /// Create a new stream to read request line by line. - fn new(req: &T) -> Self { + fn new(req: T) -> Self { let encoding = match req.encoding() { Ok(enc) => enc, Err(err) => return Self::err(req, err.into()), @@ -242,7 +242,7 @@ impl Readlines { self } - fn err(req: &T, err: ReadlinesError) -> Self { + fn err(req: T, err: ReadlinesError) -> Self { Readlines { stream: req.payload(), buff: BytesMut::new(), @@ -362,7 +362,7 @@ pub struct MessageBody { impl MessageBody { /// Create `MessageBody` for request. - pub fn new(req: &T) -> MessageBody { + pub fn new(req: T) -> MessageBody { let mut len = None; if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { @@ -457,7 +457,7 @@ pub struct UrlEncoded { impl UrlEncoded { /// Create a new future to URL encode a request - pub fn new(req: &T) -> UrlEncoded { + pub fn new(req: T) -> UrlEncoded { // check content type if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { return Self::err(UrlencodedError::ContentType); @@ -800,7 +800,7 @@ mod tests { Contrary to popular belief, Lorem Ipsum is not simply random text.", )) .finish(); - let mut r = Readlines::new(&req); + let mut r = Readlines::new(req); match r.poll().ok().unwrap() { Async::Ready(Some(s)) => assert_eq!( s, diff --git a/src/json.rs b/src/json.rs index d06449cb0..fc1ab4d25 100644 --- a/src/json.rs +++ b/src/json.rs @@ -50,7 +50,7 @@ pub struct JsonBody { impl JsonBody { /// Create `JsonBody` for request. - pub fn new(req: &T) -> Self { + pub fn new(req: T) -> Self { // check content-type let json = if let Ok(Some(mime)) = req.mime_type() { mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) diff --git a/src/message.rs b/src/message.rs index 31d61f63d..a73392221 100644 --- a/src/message.rs +++ b/src/message.rs @@ -5,7 +5,6 @@ use std::rc::Rc; use http::{HeaderMap, Method, StatusCode, Uri, Version}; use crate::extensions::Extensions; -use crate::payload::Payload; /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] @@ -149,7 +148,6 @@ impl ResponseHead { pub struct Message { pub head: T, pub extensions: RefCell, - pub payload: RefCell>, pub(crate) pool: &'static MessagePool, } @@ -159,7 +157,6 @@ impl Message { pub fn reset(&mut self) { self.head.clear(); self.extensions.borrow_mut().clear(); - *self.payload.borrow_mut() = None; } } @@ -168,7 +165,6 @@ impl Default for Message { Message { pool: T::pool(), head: T::default(), - payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), } } diff --git a/src/request.rs b/src/request.rs index 60ddee19b..b60a772e1 100644 --- a/src/request.rs +++ b/src/request.rs @@ -2,43 +2,76 @@ use std::cell::{Ref, RefMut}; use std::fmt; use std::rc::Rc; +use bytes::Bytes; +use futures::Stream; use http::{header, HeaderMap, Method, Uri, Version}; +use crate::error::PayloadError; use crate::extensions::Extensions; use crate::httpmessage::HttpMessage; use crate::message::{Message, MessagePool, RequestHead}; use crate::payload::Payload; /// Request -pub struct Request { +pub struct Request

    { + pub(crate) payload: Option

    , pub(crate) inner: Rc>, } -impl HttpMessage for Request { - type Stream = Payload; +impl

    HttpMessage for Request

    +where + P: Stream, +{ + type Stream = P; fn headers(&self) -> &HeaderMap { &self.inner.head.headers } #[inline] - fn payload(&self) -> Payload { - if let Some(payload) = self.inner.payload.borrow_mut().take() { - payload - } else { - Payload::empty() + fn payload(mut self) -> P { + self.payload.take().unwrap() + } +} + +impl Request { + /// Create new Request instance + pub fn new() -> Request { + Request { + payload: Some(Payload::empty()), + inner: MessagePool::get_message(), } } } -impl Request { +impl Request { /// Create new Request instance - pub fn new() -> Request { + pub fn with_payload(payload: Payload) -> Request { Request { + payload: Some(payload), inner: MessagePool::get_message(), } } + /// Create new Request instance + pub fn set_payload

    (self, payload: P) -> Request

    { + Request { + payload: Some(payload), + inner: self.inner.clone(), + } + } + + /// Take request's payload + pub fn take_payload(mut self) -> (Payload, Request<()>) { + ( + self.payload.take().unwrap(), + Request { + payload: Some(()), + inner: self.inner.clone(), + }, + ) + } + // /// Create new Request instance with pool // pub(crate) fn with_pool(pool: &'static MessagePool) -> Request { // Request { @@ -143,17 +176,17 @@ impl Request { self.inner().head.method == Method::CONNECT } - #[doc(hidden)] - /// Note: this method should be called only as part of clone operation - /// of wrapper type. - pub fn clone_request(&self) -> Self { - Request { - inner: self.inner.clone(), - } - } + // #[doc(hidden)] + // /// Note: this method should be called only as part of clone operation + // /// of wrapper type. + // pub fn clone_request(&self) -> Self { + // Request { + // inner: self.inner.clone(), + // } + // } } -impl Drop for Request { +impl Drop for Request { fn drop(&mut self) { if Rc::strong_count(&self.inner) == 1 { self.inner.pool.release(self.inner.clone()); @@ -161,7 +194,7 @@ impl Drop for Request { } } -impl fmt::Debug for Request { +impl fmt::Debug for Request { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!( f, diff --git a/src/test.rs b/src/test.rs index a26f31e59..852dd3c0b 100644 --- a/src/test.rs +++ b/src/test.rs @@ -148,15 +148,18 @@ impl TestRequest { .. } = self; - let mut req = Request::new(); - { - let inner = req.inner_mut(); - inner.head.uri = uri; - inner.head.method = method; - inner.head.version = version; - inner.head.headers = headers; - *inner.payload.borrow_mut() = payload; - } + let mut req = if let Some(pl) = payload { + Request::with_payload(pl) + } else { + Request::with_payload(Payload::empty()) + }; + + let inner = req.inner_mut(); + inner.head.uri = uri; + inner.head.method = method; + inner.head.version = version; + inner.head.headers = headers; + // req.set_cookies(cookies); req } diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 81a3d909c..9c71a25c0 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -28,6 +28,9 @@ default = ["session"] # sessions feature, session require "ring" crate and c compiler session = ["cookie/secure"] +# openssl +ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] + [dependencies] actix-codec = "0.1" actix-service = "0.2.0" @@ -52,3 +55,5 @@ serde_urlencoded = "0.5.3" time = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" + +openssl = { version="0.10", optional = true } diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 8083ebb15..3d6d917e5 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -3,6 +3,12 @@ use std::sync::mpsc; use std::{net, thread}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_http::body::MessageBody; +use actix_http::client::{ + ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, + ConnectorError, SendRequestError, +}; +use actix_http::ws; use actix_rt::{Runtime, System}; use actix_server::{Server, StreamServiceFactory}; use actix_service::Service; @@ -11,13 +17,6 @@ use futures::future::{lazy, Future}; use http::Method; use net2::TcpBuilder; -use actix_http::body::MessageBody; -use actix_http::client::{ - ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, - ConnectorError, SendRequestError, -}; -use actix_http::ws; - /// The `TestServer` type. /// /// `TestServer` is very simple test server that simplify process of writing @@ -101,6 +100,9 @@ impl TestServer { let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); builder.set_verify(SslVerifyMode::NONE); + let _ = builder + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); Connector::default().ssl(builder.build()).service() } #[cfg(not(feature = "ssl"))] @@ -151,6 +153,15 @@ impl TestServerRuntime { } } + /// Construct test https server url + pub fn surl(&self, uri: &str) -> String { + if uri.starts_with('/') { + format!("https://127.0.0.1:{}{}", self.addr.port(), uri) + } else { + format!("https://127.0.0.1:{}/{}", self.addr.port(), uri) + } + } + /// Create `GET` request pub fn get(&self) -> ClientRequestBuilder { ClientRequest::get(self.url("/").as_str()) diff --git a/tests/cert.pem b/tests/cert.pem index db04fbfae..5e195d98d 100644 --- a/tests/cert.pem +++ b/tests/cert.pem @@ -1,31 +1,16 @@ -----BEGIN CERTIFICATE----- -MIIFXTCCA0WgAwIBAgIJAJ3tqfd0MLLNMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNV -BAYTAlVTMQswCQYDVQQIDAJDRjELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBh -bnkxDDAKBgNVBAsMA09yZzEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMB4XDTE4 -MDcyOTE4MDgzNFoXDTE5MDcyOTE4MDgzNFowYTELMAkGA1UEBhMCVVMxCzAJBgNV -BAgMAkNGMQswCQYDVQQHDAJTRjEQMA4GA1UECgwHQ29tcGFueTEMMAoGA1UECwwD -T3JnMRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQDZbMgDYilVH1Nv0QWEhOXG6ETmtjZrdLqrNg3NBWBIWCDF -cQ+fyTWxARx6vkF8A/3zpJyTcfQW8HgG38jw/A61QKaHBxzwq0HlNwY9Hh+Neeuk -L4wgrlQ0uTC7IEMrOJjNN0GPyRQVfVbGa8QcSCpOg85l8GCxLvVwkBH/M5atoMtJ -EzniNfK+gtk3hOL2tBqBCu9NDjhXPnJwNDLtTG1tQaHUJW/r281Wvv9I46H83DkU -05lYtauh0bKh5znCH2KpFmBGqJNRzou3tXZFZzZfaCPBJPZR8j5TjoinehpDtkPh -4CSio0PF2eIFkDKRUbdz/327HgEARJMXx+w1yHpS2JwHFgy5O76i68/Smx8j3DDA -2WIkOYAJFRMH0CBHKdsvUDOGpCgN+xv3whl+N806nCfC4vCkwA+FuB3ko11logng -dvr+y0jIUSU4THF3dMDEXYayF3+WrUlw0cBnUNJdXky85ZP81aBfBsjNSBDx4iL4 -e4NhfZRS5oHpHy1t3nYfuttS/oet+Ke5KUpaqNJguSIoeTBSmgzDzL1TJxFLOzUT -2c/A9M69FdvSY0JB4EJX0W9K01Vd0JRNPwsY+/zvFIPama3suKOUTqYcsbwxx9xa -TMDr26cIQcgUAUOKZO43sQGWNzXX3FYVNwczKhkB8UX6hOrBJsEYiau4LGdokQID -AQABoxgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIB -AIX+Qb4QRBxHl5X2UjRyLfWVkimtGlwI8P+eJZL3DrHBH/TpqAaCvTf0EbRC32nm -ASDMwIghaMvyrW40QN6V/CWRRi25cXUfsIZr1iHAHK0eZJV8SWooYtt4iNrcUs3g -4OTvDxhNmDyNwV9AXhJsBKf80dCW6/84jItqVAj20/OO4Rkd2tEeI8NomiYBc6a1 -hgwvv02myYF5hG/xZ9YSqeroBCZHwGYoJJnSpMPqJsxbCVnx2/U9FzGwcRmNHFCe -0g7EJZd3//8Plza6nkTBjJ/V7JnLqMU+ltx4mAgZO8rfzIr84qZdt0YN33VJQhYq -seuMySxrsuaAoxAmm8IoK9cW4IPzx1JveBQiroNlq5YJGf2UW7BTc3gz6c2tINZi -7ailBVdhlMnDXAf3/9xiiVlRAHOxgZh/7sRrKU7kDEHM4fGoc0YyZBTQKndPYMwO -3Bd82rlQ4sd46XYutTrB+mBYClVrJs+OzbNedTsR61DVNKKsRG4mNPyKSAIgOfM5 -XmSvCMPN5JK9U0DsNIV2/SnVsmcklQczT35FLTxl9ntx8ys7ZYK+SppD7XuLfWMq -GT9YMWhlpw0aRDg/aayeeOcnsNBhzAFMcOpQj1t6Fgv4+zbS9BM2bT0hbX86xjkr -E6wWgkuCslMgQlEJ+TM5RhYrI5/rVZQhvmgcob/9gPZv +MIICljCCAX4CCQDFdWu66640QjANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJ1 +czAeFw0xOTAyMDQyMzEyNTBaFw0yMDAyMDQyMzEyNTBaMA0xCzAJBgNVBAYTAnVz +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzZUXMnS5X8HWxTvHAc82 +Q2d32fiPQGtD+fp3OV90l6RC9jgMdH4yTVUgX5mYYcW0k89RaP8g61H6b76F9gcd +yZ1idqKI1AU9aeBUPV8wkrouhR/6Omv8fA7yr9tVmNo53jPN7WyKoBoU0r7Yj9Ez +g3qjv/808Jlgby3EhduruyyfdvSt5ZFXnOz2D3SF9DS4yrM2jSw4ZTuoVMfZ8vZe +FVzLo/+sV8qokU6wBTEOAmZQ7e/zZV4qAoH2Z3Vj/uD1Zr/MXYyh81RdXpDqIXwV +Z29LEOa2eTGFEdvfG+tdvvuIvSdF3+WbLrwn2ECfwJ8zmKyTauPRV4pj7ks+wkBI +EQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQB6dmuWBOpFfDdu0mdsDb8XnJY1svjH +4kbztXhjQJ/WuhCUIwvXFyz9dqQCq+TbJUbUEzZJEfaq1uaI3iB5wd35ArSoAGJA +k0lonzyeSM+cmNOe/5BPqWhd1qPwbsfgMoCCkZUoTT5Rvw6yt00XIqZzMqrsvRBX +hAcUW3zBtFQNP6aQqsMdn4ClZE0WHf+LzWy2NQh+Sf46tSYBHELfdUawgR789PB4 +/gNjAeklq06JmE/3gELijwaijVIuUsMC9ua//ITk4YIFpqanPtka+7BpfTegPGNs +HCj1g7Jot97oQMuvDOJeso91aiSA+gutepCClZICT8LxNRkY3ZlXYp92 -----END CERTIFICATE----- diff --git a/tests/key.pem b/tests/key.pem index aac387c64..50ded0ce0 100644 --- a/tests/key.pem +++ b/tests/key.pem @@ -1,51 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIJKAIBAAKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEP -n8k1sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+M -IK5UNLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM5 -4jXyvoLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZ -WLWrodGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAk -oqNDxdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNli -JDmACRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6 -/stIyFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuD -YX2UUuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nP -wPTOvRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA -69unCEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEA -AQKCAgAME3aoeXNCPxMrSri7u4Xnnk71YXl0Tm9vwvjRQlMusXZggP8VKN/KjP0/ -9AE/GhmoxqPLrLCZ9ZE1EIjgmZ9Xgde9+C8rTtfCG2RFUL7/5J2p6NonlocmxoJm -YkxYwjP6ce86RTjQWL3RF3s09u0inz9/efJk5O7M6bOWMQ9VZXDlBiRY5BYvbqUR -6FeSzD4MnMbdyMRoVBeXE88gTvZk8xhB6DJnLzYgc0tKiRoeKT0iYv5JZw25VyRM -ycLzfTrFmXCPfB1ylb483d9Ly4fBlM8nkx37PzEnAuukIawDxsPOb9yZC+hfvNJI -7NFiMN+3maEqG2iC00w4Lep4skHY7eHUEUMl+Wjr+koAy2YGLWAwHZQTm7iXn9Ab -L6adL53zyCKelRuEQOzbeosJAqS+5fpMK0ekXyoFIuskj7bWuIoCX7K/kg6q5IW+ -vC2FrlsrbQ79GztWLVmHFO1I4J9M5r666YS0qdh8c+2yyRl4FmSiHfGxb3eOKpxQ -b6uI97iZlkxPF9LYUCSc7wq0V2gGz+6LnGvTHlHrOfVXqw/5pLAKhXqxvnroDTwz -0Ay/xFF6ei/NSxBY5t8ztGCBm45wCU3l8pW0X6dXqwUipw5b4MRy1VFRu6rqlmbL -OPSCuLxqyqsigiEYsBgS/icvXz9DWmCQMPd2XM9YhsHvUq+R4QKCAQEA98EuMMXI -6UKIt1kK2t/3OeJRyDd4iv/fCMUAnuPjLBvFE4cXD/SbqCxcQYqb+pue3PYkiTIC -71rN8OQAc5yKhzmmnCE5N26br/0pG4pwEjIr6mt8kZHmemOCNEzvhhT83nfKmV0g -9lNtuGEQMiwmZrpUOF51JOMC39bzcVjYX2Cmvb7cFbIq3lR0zwM+aZpQ4P8LHCIu -bgHmwbdlkLyIULJcQmHIbo6nPFB3ZZE4mqmjwY+rA6Fh9rgBa8OFCfTtrgeYXrNb -IgZQ5U8GoYRPNC2ot0vpTinraboa/cgm6oG4M7FW1POCJTl+/ktHEnKuO5oroSga -/BSg7hCNFVaOhwKCAQEA4Kkys0HtwEbV5mY/NnvUD5KwfXX7BxoXc9lZ6seVoLEc -KjgPYxqYRVrC7dB2YDwwp3qcRTi/uBAgFNm3iYlDzI4xS5SeaudUWjglj7BSgXE2 -iOEa7EwcvVPluLaTgiWjlzUKeUCNNHWSeQOt+paBOT+IgwRVemGVpAgkqQzNh/nP -tl3p9aNtgzEm1qVlPclY/XUCtf3bcOR+z1f1b4jBdn0leu5OhnxkC+Htik+2fTXD -jt6JGrMkanN25YzsjnD3Sn+v6SO26H99wnYx5oMSdmb8SlWRrKtfJHnihphjG/YY -l1cyorV6M/asSgXNQfGJm4OuJi0I4/FL2wLUHnU+JwKCAQEAzh4WipcRthYXXcoj -gMKRkMOb3GFh1OpYqJgVExtudNTJmZxq8GhFU51MR27Eo7LycMwKy2UjEfTOnplh -Us2qZiPtW7k8O8S2m6yXlYUQBeNdq9IuuYDTaYD94vsazscJNSAeGodjE+uGvb1q -1wLqE87yoE7dUInYa1cOA3+xy2/CaNuviBFJHtzOrSb6tqqenQEyQf6h9/12+DTW -t5pSIiixHrzxHiFqOoCLRKGToQB+71rSINwTf0nITNpGBWmSj5VcC3VV3TG5/XxI -fPlxV2yhD5WFDPVNGBGvwPDSh4jSMZdZMSNBZCy4XWFNSKjGEWoK4DFYed3DoSt9 -5IG1YwKCAQA63ntHl64KJUWlkwNbboU583FF3uWBjee5VqoGKHhf3CkKMxhtGqnt -+oN7t5VdUEhbinhqdx1dyPPvIsHCS3K1pkjqii4cyzNCVNYa2dQ00Qq+QWZBpwwc -3GAkz8rFXsGIPMDa1vxpU6mnBjzPniKMcsZ9tmQDppCEpBGfLpio2eAA5IkK8eEf -cIDB3CM0Vo94EvI76CJZabaE9IJ+0HIJb2+jz9BJ00yQBIqvJIYoNy9gP5Xjpi+T -qV/tdMkD5jwWjHD3AYHLWKUGkNwwkAYFeqT/gX6jpWBP+ZRPOp011X3KInJFSpKU -DT5GQ1Dux7EMTCwVGtXqjO8Ym5wjwwsfAoIBAEcxlhIW1G6BiNfnWbNPWBdh3v/K -5Ln98Rcrz8UIbWyl7qNPjYb13C1KmifVG1Rym9vWMO3KuG5atK3Mz2yLVRtmWAVc -fxzR57zz9MZFDun66xo+Z1wN3fVxQB4CYpOEI4Lb9ioX4v85hm3D6RpFukNtRQEc -Gfr4scTjJX4jFWDp0h6ffMb8mY+quvZoJ0TJqV9L9Yj6Ksdvqez/bdSraev97bHQ -4gbQxaTZ6WjaD4HjpPQefMdWp97Metg0ZQSS8b8EzmNFgyJ3XcjirzwliKTAQtn6 -I2sd0NCIooelrKRD8EJoDUwxoOctY7R97wpZ7/wEHU45cBCbRV3H4JILS5c= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDNlRcydLlfwdbF +O8cBzzZDZ3fZ+I9Aa0P5+nc5X3SXpEL2OAx0fjJNVSBfmZhhxbSTz1Fo/yDrUfpv +voX2Bx3JnWJ2oojUBT1p4FQ9XzCSui6FH/o6a/x8DvKv21WY2jneM83tbIqgGhTS +vtiP0TODeqO//zTwmWBvLcSF26u7LJ929K3lkVec7PYPdIX0NLjKszaNLDhlO6hU +x9ny9l4VXMuj/6xXyqiRTrAFMQ4CZlDt7/NlXioCgfZndWP+4PVmv8xdjKHzVF1e +kOohfBVnb0sQ5rZ5MYUR298b612++4i9J0Xf5ZsuvCfYQJ/AnzOYrJNq49FXimPu +Sz7CQEgRAgMBAAECggEBALC547EaKmko5wmyM4dYq9sRzTPxuqO0EkGIkIkfh8j8 +ChxDXmGeQnu8HBJSpW4XWP5fkCpkd9YTKOh6rgorX+37f7NgUaOBxaOIlqITfFwF +9Qu3y5IBVpEHAJUwRcsaffiILBRX5GtxQElSijRHsLLr8GySZN4X25B3laNEjcJe +NWJrDaxOn0m0MMGRvBpM8PaZu1Mn9NWxt04b/fteVLdN4TAcuY9TgvVZBq92S2FM +qvZcnJCQckNOuMOptVdP45qPkerKUohpOcqBfIiWFaalC378jE3Dm68p7slt3R6y +I1wVqCI4+MZfM3CtKcYJV0fdqklJCvXORvRiT8OZKakCgYEA5YnhgXOu4CO4DR1T +Lacv716DPyHeKVa6TbHhUhWw4bLwNLUsEL98jeU9SZ6VH8enBlDm5pCsp2i//t9n +8hoykN4L0rS4EyAGENouTRkLhtHfjTAKTKDK8cNvEaS8NOBJWrI0DTiHtFbCRBvI +zRx5VhrB5H4DDbqn7QV9g+GBKvMCgYEA5Ug3bN0RNUi3KDoIRcnWc06HsX307su7 +tB4cGqXJqVOJCrkk5sefGF502+W8m3Ldjaakr+Q9BoOdZX6boZnFtVetT8Hyzk1C +Rkiyz3GcwovOkQK//UmljsuRjgHF+PuQGX5ol4YlJtXU21k5bCsi1Tmyp7IufiGV +AQRMVZVbeesCgYA/QBZGwKTgmJcf7gO8ocRAto999wwr4f0maazIHLgICXHNZFsH +JmzhANk5jxxSjIaG5AYsZJNe8ittxQv0l6l1Z+pkHm5Wvs1NGYIGtq8JcI2kbyd3 +ZBtoMU1K1FUUUPWFq3NSbVBfrkSL1ggoFP+ObYMePmcDAntBgfDLRXl9ZwKBgQCt +/dh5l2UIn27Gawt+EkXX6L8WVTQ6xoZhj/vZyPe4tDip14gGTXQQ5RUfDj7LZCZ2 +6P/OrpAU0mnt7F8kCfI7xBY0EUU1gvGJLn/q5heElt2hs4mIJ4woSZjiP7xBTn2y +qveqDNVCnEBUWGg4Cp/7WTaXBaM8ejV9uQpIY/gwEwKBgQCCYnd9fD8L4nGyOLoD +eUzMV7G8TZfinlxCNMVXfpn4Z8OaYHOk5NiujHK55w4ghx06NQw038qhnX0ogbjU +caWOwCIbrYgx2fwYuOZbJFXdjWlpjIK3RFOcbNCNgCRLT6Lgz4uZYZ9RVftADvMi +zR1QsLWnIvARbTtOPfZqizT2gQ== +-----END PRIVATE KEY----- diff --git a/tests/test_server.rs b/tests/test_server.rs index c23840ead..9fa27e71b 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -5,16 +5,16 @@ use std::{net, thread}; use actix_http_test::TestServer; use actix_service::NewService; use bytes::Bytes; -use futures::future::{self, ok}; +use futures::future::{self, ok, Future}; use futures::stream::once; use actix_http::{ - body, client, h1, http, Body, Error, HttpMessage as HttpMessage2, KeepAlive, + body, client, h1, h2, http, Body, Error, HttpMessage as HttpMessage2, KeepAlive, Request, Response, }; #[test] -fn test_h1_v2() { +fn test_h1() { let mut srv = TestServer::with_factory(|| { h1::H1Service::build() .keep_alive(KeepAlive::Disabled) @@ -30,6 +30,85 @@ fn test_h1_v2() { assert!(response.status().is_success()); } +#[cfg(feature = "ssl")] +fn ssl_acceptor() -> std::io::Result> { + use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; + // load ssl keys + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder + .set_private_key_file("tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("tests/cert.pem") + .unwrap(); + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(openssl::ssl::AlpnError::NOACK) + } + }); + builder.set_alpn_protos(b"\x02h2")?; + Ok(actix_server::ssl::OpensslAcceptor::new(builder.build())) +} + +#[cfg(feature = "ssl")] +#[test] +fn test_h2() -> std::io::Result<()> { + let openssl = ssl_acceptor()?; + let mut srv = TestServer::with_factory(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + h2::H2Service::build() + .finish(|_| future::ok::<_, Error>(Response::Ok().finish())) + .map_err(|_| ()), + ) + }); + + let req = client::ClientRequest::get(srv.surl("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); + println!("RES: {:?}", response); + assert!(response.status().is_success()); + Ok(()) +} + +#[cfg(feature = "ssl")] +#[test] +fn test_h2_body() -> std::io::Result<()> { + // std::env::set_var("RUST_LOG", "actix_http=trace"); + // env_logger::init(); + + let data = "HELLOWORLD".to_owned().repeat(64 * 1024); + let openssl = ssl_acceptor()?; + let mut srv = TestServer::with_factory(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + h2::H2Service::build() + .finish(|req: Request<_>| { + req.body() + .limit(1024 * 1024) + .and_then(|body| Ok(Response::Ok().body(body))) + }) + .map_err(|_| ()), + ) + }); + + let req = client::ClientRequest::get(srv.surl("/")) + .body(data.clone()) + .unwrap(); + let response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); + + let body = srv.block_on(response.body().limit(1024 * 1024)).unwrap(); + assert_eq!(&body, data.as_bytes()); + Ok(()) +} + #[test] fn test_slow_request() { let srv = TestServer::with_factory(|| { From fcace161c7bd2fdf81b59b0b8ccefd16219105af Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Feb 2019 12:22:40 -0800 Subject: [PATCH 120/208] fix manifest features --- Cargo.toml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bbb31c161..8cc74446e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ default = ["session"] session = ["cookie/secure"] # openssl -ssl = ["openssl", "actix-connector/ssl", "actix-server/ssl", "actix-http-test/ssl"] +ssl = ["openssl"] [dependencies] actix-service = "0.2.1" @@ -76,7 +76,10 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.1.0" actix-web = "0.7" -actix-server = "0.2" -actix-http-test = { path="test-server" } +actix-server = { version="0.2", features=["ssl"] } +actix-connector = { version="0.2.0", features=["ssl"] } +actix-utils = "0.2.1" +actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" +openssl = { version="0.10" } From cd83553db7a04f60d54f09ddc0e56c7e89a5fcc0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Feb 2019 11:06:05 -0800 Subject: [PATCH 121/208] simplify payload api; add missing http error helper functions --- Cargo.toml | 1 - examples/echo.rs | 4 +- examples/echo2.rs | 4 +- src/client/h1proto.rs | 21 +-- src/client/h2proto.rs | 3 +- src/client/request.rs | 19 +-- src/client/response.rs | 22 ++- src/error.rs | 314 +++++++++++++++++++++++++++++++++++++++- src/h1/dispatcher.rs | 2 +- src/h1/mod.rs | 2 + src/{ => h1}/payload.rs | 0 src/httpcodes.rs | 1 + src/httpmessage.rs | 137 ++++++++++-------- src/json.rs | 12 +- src/lib.rs | 2 - src/request.rs | 11 +- src/service/mod.rs | 2 + src/test.rs | 2 +- tests/test_client.rs | 4 +- tests/test_server.rs | 20 +-- tests/test_ws.rs | 34 ----- 21 files changed, 442 insertions(+), 175 deletions(-) rename src/{ => h1}/payload.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 8cc74446e..23938f2fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,6 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.1.0" -actix-web = "0.7" actix-server = { version="0.2", features=["ssl"] } actix-connector = { version="0.2.0", features=["ssl"] } actix-utils = "0.2.1" diff --git a/examples/echo.rs b/examples/echo.rs index 3bfb04d7c..03d5b4706 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -18,8 +18,8 @@ fn main() { .client_timeout(1000) .client_disconnect(1000) .server_hostname("localhost") - .finish(|_req: Request| { - _req.body().limit(512).and_then(|bytes: Bytes| { + .finish(|mut req: Request| { + req.body().limit(512).and_then(|bytes: Bytes| { info!("request body: {:?}", bytes); let mut res = Response::Ok(); res.header("x-head", HeaderValue::from_static("dummy value!")); diff --git a/examples/echo2.rs b/examples/echo2.rs index 0e2bc9d52..2fd9cbcff 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -8,8 +8,8 @@ use futures::Future; use log::info; use std::env; -fn handle_request(_req: Request) -> impl Future { - _req.body().limit(512).from_err().and_then(|bytes: Bytes| { +fn handle_request(mut req: Request) -> impl Future { + req.body().limit(512).from_err().and_then(|bytes: Bytes| { info!("request body: {:?}", bytes); let mut res = Response::Ok(); res.header("x-head", HeaderValue::from_static("dummy value!")); diff --git a/src/client/h1proto.rs b/src/client/h1proto.rs index 86fc10b84..59a03ef48 100644 --- a/src/client/h1proto.rs +++ b/src/client/h1proto.rs @@ -50,14 +50,14 @@ where .into_future() .map_err(|(e, _)| SendRequestError::from(e)) .and_then(|(item, framed)| { - if let Some(res) = item { + if let Some(mut res) = item { match framed.get_codec().message_type() { h1::MessageType::None => { let force_close = !framed.get_codec().keepalive(); release_connection(framed, force_close) } _ => { - *res.payload.borrow_mut() = Some(Payload::stream(framed)) + res.set_payload(Payload::stream(framed)); } } ok(res) @@ -199,27 +199,10 @@ where } } -struct EmptyPayload; - -impl Stream for EmptyPayload { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll, Self::Error> { - Ok(Async::Ready(None)) - } -} - pub(crate) struct Payload { framed: Option>, } -impl Payload<()> { - pub fn empty() -> PayloadStream { - Box::new(EmptyPayload) - } -} - impl Payload { pub fn stream(framed: Framed) -> PayloadStream { Box::new(Payload { diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index ecd18cf82..f2f18d935 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::time; use actix_codec::{AsyncRead, AsyncWrite}; @@ -111,7 +110,7 @@ where Ok(ClientResponse { head, - payload: RefCell::new(Some(Box::new(Payload::new(body)))), + payload: Some(Box::new(Payload::new(body))), }) }) .from_err() diff --git a/src/client/request.rs b/src/client/request.rs index b80f0e6dc..7e971756d 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -268,10 +268,9 @@ impl ClientRequestBuilder { /// /// ```rust /// # extern crate mime; - /// # extern crate actix_web; - /// # use actix_web::client::*; + /// # extern crate actix_http; /// # - /// use actix_web::{client, http}; + /// use actix_http::{client, http}; /// /// fn main() { /// let req = client::ClientRequest::build() @@ -299,16 +298,14 @@ impl ClientRequestBuilder { /// To override header use `set_header()` method. /// /// ```rust - /// # extern crate http; - /// # extern crate actix_web; - /// # use actix_web::client::*; + /// # extern crate actix_http; /// # - /// use http::header; + /// use actix_http::{client, http}; /// /// fn main() { - /// let req = ClientRequest::build() + /// let req = client::ClientRequest::build() /// .header("X-TEST", "value") - /// .header(header::CONTENT_TYPE, "application/json") + /// .header(http::header::CONTENT_TYPE, "application/json") /// .finish() /// .unwrap(); /// } @@ -427,8 +424,8 @@ impl ClientRequestBuilder { /// Set a cookie /// /// ```rust - /// # extern crate actix_web; - /// use actix_web::{client, http}; + /// # extern crate actix_http; + /// use actix_http::{client, http}; /// /// fn main() { /// let req = client::ClientRequest::build() diff --git a/src/client/response.rs b/src/client/response.rs index 6224d3cb5..65c59f2a6 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::fmt; use bytes::Bytes; @@ -10,13 +9,11 @@ use crate::error::PayloadError; use crate::httpmessage::HttpMessage; use crate::message::{Head, ResponseHead}; -use super::h1proto::Payload; - /// Client Response #[derive(Default)] pub struct ClientResponse { pub(crate) head: ResponseHead, - pub(crate) payload: RefCell>, + pub(crate) payload: Option, } impl HttpMessage for ClientResponse { @@ -27,12 +24,8 @@ impl HttpMessage for ClientResponse { } #[inline] - fn payload(self) -> Self::Stream { - if let Some(payload) = self.payload.borrow_mut().take() { - payload - } else { - Payload::empty() - } + fn payload(&mut self) -> Option { + self.payload.take() } } @@ -41,7 +34,7 @@ impl ClientResponse { pub fn new() -> ClientResponse { ClientResponse { head: ResponseHead::default(), - payload: RefCell::new(None), + payload: None, } } @@ -84,6 +77,11 @@ impl ClientResponse { pub fn keep_alive(&self) -> bool { self.head().keep_alive() } + + /// Set response payload + pub fn set_payload(&mut self, payload: PayloadStream) { + self.payload = Some(payload); + } } impl Stream for ClientResponse { @@ -91,7 +89,7 @@ impl Stream for ClientResponse { type Error = PayloadError; fn poll(&mut self) -> Poll, Self::Error> { - if let Some(ref mut payload) = &mut *self.payload.borrow_mut() { + if let Some(ref mut payload) = self.payload { payload.poll() } else { Ok(Async::Ready(None)) diff --git a/src/error.rs b/src/error.rs index 03224b558..f71b429fd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -344,7 +344,7 @@ pub enum PayloadError { UnknownLength, /// Http2 payload error #[display(fmt = "{}", _0)] - H2Payload(h2::Error), + Http2Payload(h2::Error), } impl From for PayloadError { @@ -642,6 +642,16 @@ where InternalError::new(err, StatusCode::UNAUTHORIZED).into() } +/// Helper function that creates wrapper of any error and generate +/// *PAYMENT_REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorPaymentRequired(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::PAYMENT_REQUIRED).into() +} + /// Helper function that creates wrapper of any error and generate *FORBIDDEN* /// response. #[allow(non_snake_case)] @@ -672,6 +682,26 @@ where InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into() } +/// Helper function that creates wrapper of any error and generate *NOT +/// ACCEPTABLE* response. +#[allow(non_snake_case)] +pub fn ErrorNotAcceptable(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::NOT_ACCEPTABLE).into() +} + +/// Helper function that creates wrapper of any error and generate *PROXY +/// AUTHENTICATION REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorProxyAuthenticationRequired(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::PROXY_AUTHENTICATION_REQUIRED).into() +} + /// Helper function that creates wrapper of any error and generate *REQUEST /// TIMEOUT* response. #[allow(non_snake_case)] @@ -702,6 +732,116 @@ where InternalError::new(err, StatusCode::GONE).into() } +/// Helper function that creates wrapper of any error and generate *LENGTH +/// REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorLengthRequired(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::LENGTH_REQUIRED).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *PAYLOAD TOO LARGE* response. +#[allow(non_snake_case)] +pub fn ErrorPayloadTooLarge(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::PAYLOAD_TOO_LARGE).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *URI TOO LONG* response. +#[allow(non_snake_case)] +pub fn ErrorUriTooLong(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::URI_TOO_LONG).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *UNSUPPORTED MEDIA TYPE* response. +#[allow(non_snake_case)] +pub fn ErrorUnsupportedMediaType(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::UNSUPPORTED_MEDIA_TYPE).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *RANGE NOT SATISFIABLE* response. +#[allow(non_snake_case)] +pub fn ErrorRangeNotSatisfiable(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::RANGE_NOT_SATISFIABLE).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *IM A TEAPOT* response. +#[allow(non_snake_case)] +pub fn ErrorImATeapot(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::IM_A_TEAPOT).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *MISDIRECTED REQUEST* response. +#[allow(non_snake_case)] +pub fn ErrorMisdirectedRequest(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::MISDIRECTED_REQUEST).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *UNPROCESSABLE ENTITY* response. +#[allow(non_snake_case)] +pub fn ErrorUnprocessableEntity(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::UNPROCESSABLE_ENTITY).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *LOCKED* response. +#[allow(non_snake_case)] +pub fn ErrorLocked(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::LOCKED).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *FAILED DEPENDENCY* response. +#[allow(non_snake_case)] +pub fn ErrorFailedDependency(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::FAILED_DEPENDENCY).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *UPGRADE REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorUpgradeRequired(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::UPGRADE_REQUIRED).into() +} + /// Helper function that creates wrapper of any error and generate /// *PRECONDITION FAILED* response. #[allow(non_snake_case)] @@ -712,6 +852,46 @@ where InternalError::new(err, StatusCode::PRECONDITION_FAILED).into() } +/// Helper function that creates wrapper of any error and generate +/// *PRECONDITION REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorPreconditionRequired(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::PRECONDITION_REQUIRED).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *TOO MANY REQUESTS* response. +#[allow(non_snake_case)] +pub fn ErrorTooManyRequests(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::TOO_MANY_REQUESTS).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *REQUEST HEADER FIELDS TOO LARGE* response. +#[allow(non_snake_case)] +pub fn ErrorRequestHeaderFieldsTooLarge(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *UNAVAILABLE FOR LEGAL REASONS* response. +#[allow(non_snake_case)] +pub fn ErrorUnavailableForLegalReasons(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS).into() +} + /// Helper function that creates wrapper of any error and generate /// *EXPECTATION FAILED* response. #[allow(non_snake_case)] @@ -772,6 +952,66 @@ where InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into() } +/// Helper function that creates wrapper of any error and +/// generate *HTTP VERSION NOT SUPPORTED* response. +#[allow(non_snake_case)] +pub fn ErrorHttpVersionNotSupported(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::HTTP_VERSION_NOT_SUPPORTED).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *VARIANT ALSO NEGOTIATES* response. +#[allow(non_snake_case)] +pub fn ErrorVariantAlsoNegotiates(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::VARIANT_ALSO_NEGOTIATES).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *INSUFFICIENT STORAGE* response. +#[allow(non_snake_case)] +pub fn ErrorInsufficientStorage(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::INSUFFICIENT_STORAGE).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *LOOP DETECTED* response. +#[allow(non_snake_case)] +pub fn ErrorLoopDetected(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::LOOP_DETECTED).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *NOT EXTENDED* response. +#[allow(non_snake_case)] +pub fn ErrorNotExtended(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::NOT_EXTENDED).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *NETWORK AUTHENTICATION REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorNetworkAuthenticationRequired(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into() +} + #[cfg(test)] mod tests { use super::*; @@ -926,6 +1166,9 @@ mod tests { let r: Response = ErrorUnauthorized("err").into(); assert_eq!(r.status(), StatusCode::UNAUTHORIZED); + let r: Response = ErrorPaymentRequired("err").into(); + assert_eq!(r.status(), StatusCode::PAYMENT_REQUIRED); + let r: Response = ErrorForbidden("err").into(); assert_eq!(r.status(), StatusCode::FORBIDDEN); @@ -935,6 +1178,12 @@ mod tests { let r: Response = ErrorMethodNotAllowed("err").into(); assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED); + let r: Response = ErrorNotAcceptable("err").into(); + assert_eq!(r.status(), StatusCode::NOT_ACCEPTABLE); + + let r: Response = ErrorProxyAuthenticationRequired("err").into(); + assert_eq!(r.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED); + let r: Response = ErrorRequestTimeout("err").into(); assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT); @@ -944,12 +1193,57 @@ mod tests { let r: Response = ErrorGone("err").into(); assert_eq!(r.status(), StatusCode::GONE); + let r: Response = ErrorLengthRequired("err").into(); + assert_eq!(r.status(), StatusCode::LENGTH_REQUIRED); + let r: Response = ErrorPreconditionFailed("err").into(); assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED); + let r: Response = ErrorPayloadTooLarge("err").into(); + assert_eq!(r.status(), StatusCode::PAYLOAD_TOO_LARGE); + + let r: Response = ErrorUriTooLong("err").into(); + assert_eq!(r.status(), StatusCode::URI_TOO_LONG); + + let r: Response = ErrorUnsupportedMediaType("err").into(); + assert_eq!(r.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE); + + let r: Response = ErrorRangeNotSatisfiable("err").into(); + assert_eq!(r.status(), StatusCode::RANGE_NOT_SATISFIABLE); + let r: Response = ErrorExpectationFailed("err").into(); assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED); + let r: Response = ErrorImATeapot("err").into(); + assert_eq!(r.status(), StatusCode::IM_A_TEAPOT); + + let r: Response = ErrorMisdirectedRequest("err").into(); + assert_eq!(r.status(), StatusCode::MISDIRECTED_REQUEST); + + let r: Response = ErrorUnprocessableEntity("err").into(); + assert_eq!(r.status(), StatusCode::UNPROCESSABLE_ENTITY); + + let r: Response = ErrorLocked("err").into(); + assert_eq!(r.status(), StatusCode::LOCKED); + + let r: Response = ErrorFailedDependency("err").into(); + assert_eq!(r.status(), StatusCode::FAILED_DEPENDENCY); + + let r: Response = ErrorUpgradeRequired("err").into(); + assert_eq!(r.status(), StatusCode::UPGRADE_REQUIRED); + + let r: Response = ErrorPreconditionRequired("err").into(); + assert_eq!(r.status(), StatusCode::PRECONDITION_REQUIRED); + + let r: Response = ErrorTooManyRequests("err").into(); + assert_eq!(r.status(), StatusCode::TOO_MANY_REQUESTS); + + let r: Response = ErrorRequestHeaderFieldsTooLarge("err").into(); + assert_eq!(r.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE); + + let r: Response = ErrorUnavailableForLegalReasons("err").into(); + assert_eq!(r.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS); + let r: Response = ErrorInternalServerError("err").into(); assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR); @@ -964,5 +1258,23 @@ mod tests { let r: Response = ErrorGatewayTimeout("err").into(); assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT); + + let r: Response = ErrorHttpVersionNotSupported("err").into(); + assert_eq!(r.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED); + + let r: Response = ErrorVariantAlsoNegotiates("err").into(); + assert_eq!(r.status(), StatusCode::VARIANT_ALSO_NEGOTIATES); + + let r: Response = ErrorInsufficientStorage("err").into(); + assert_eq!(r.status(), StatusCode::INSUFFICIENT_STORAGE); + + let r: Response = ErrorLoopDetected("err").into(); + assert_eq!(r.status(), StatusCode::LOOP_DETECTED); + + let r: Response = ErrorNotExtended("err").into(); + assert_eq!(r.status(), StatusCode::NOT_EXTENDED); + + let r: Response = ErrorNetworkAuthenticationRequired("err").into(); + assert_eq!(r.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED); } } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 1295dfddf..0a0ed04e2 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -14,11 +14,11 @@ use crate::body::{Body, BodyLength, MessageBody, ResponseBody}; use crate::config::ServiceConfig; use crate::error::DispatchError; use crate::error::{ParseError, PayloadError}; -use crate::payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; use crate::request::Request; use crate::response::Response; use super::codec::Codec; +use super::payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; use super::{H1ServiceResult, Message, MessageType}; const MAX_PIPELINED_MESSAGES: usize = 16; diff --git a/src/h1/mod.rs b/src/h1/mod.rs index a375b60d3..9054c2665 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -9,11 +9,13 @@ mod codec; mod decoder; mod dispatcher; mod encoder; +mod payload; mod service; pub use self::client::{ClientCodec, ClientPayloadCodec}; pub use self::codec::Codec; pub use self::dispatcher::Dispatcher; +pub use self::payload::{Payload, PayloadBuffer}; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; use crate::request::Request; diff --git a/src/payload.rs b/src/h1/payload.rs similarity index 100% rename from src/payload.rs rename to src/h1/payload.rs diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 7806bd806..5dfeefa9e 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -54,6 +54,7 @@ impl Response { STATIC_RESP!(Gone, StatusCode::GONE); STATIC_RESP!(LengthRequired, StatusCode::LENGTH_REQUIRED); STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); + STATIC_RESP!(PreconditionRequired, StatusCode::PRECONDITION_REQUIRED); STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG); STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); diff --git a/src/httpmessage.rs b/src/httpmessage.rs index c50de2e94..39aa1b689 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -25,7 +25,7 @@ pub trait HttpMessage: Sized { fn headers(&self) -> &HeaderMap; /// Message payload stream - fn payload(self) -> Self::Stream; + fn payload(&mut self) -> Option; #[doc(hidden)] /// Get a header @@ -128,7 +128,7 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn body(self) -> MessageBody { + fn body(&mut self) -> MessageBody { MessageBody::new(self) } @@ -162,7 +162,7 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn urlencoded(self) -> UrlEncoded { + fn urlencoded(&mut self) -> UrlEncoded { UrlEncoded::new(self) } @@ -198,19 +198,19 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn json(self) -> JsonBody { + fn json(&mut self) -> JsonBody { JsonBody::new(self) } /// Return stream of lines. - fn readlines(self) -> Readlines { + fn readlines(&mut self) -> Readlines { Readlines::new(self) } } /// Stream to read request line by line. pub struct Readlines { - stream: T::Stream, + stream: Option, buff: BytesMut, limit: usize, checked_buff: bool, @@ -220,7 +220,7 @@ pub struct Readlines { impl Readlines { /// Create a new stream to read request line by line. - fn new(req: T) -> Self { + fn new(req: &mut T) -> Self { let encoding = match req.encoding() { Ok(enc) => enc, Err(err) => return Self::err(req, err.into()), @@ -242,7 +242,7 @@ impl Readlines { self } - fn err(req: T, err: ReadlinesError) -> Self { + fn err(req: &mut T, err: ReadlinesError) -> Self { Readlines { stream: req.payload(), buff: BytesMut::new(), @@ -292,61 +292,65 @@ impl Stream for Readlines { self.checked_buff = true; } // poll req for more bytes - match self.stream.poll() { - Ok(Async::Ready(Some(mut bytes))) => { - // check if there is a newline in bytes - let mut found: Option = None; - for (ind, b) in bytes.iter().enumerate() { - if *b == b'\n' { - found = Some(ind); - break; + if let Some(ref mut stream) = self.stream { + match stream.poll() { + Ok(Async::Ready(Some(mut bytes))) => { + // check if there is a newline in bytes + let mut found: Option = None; + for (ind, b) in bytes.iter().enumerate() { + if *b == b'\n' { + found = Some(ind); + break; + } } + if let Some(ind) = found { + // check if line is longer than limit + if ind + 1 > self.limit { + return Err(ReadlinesError::LimitOverflow); + } + let enc: *const Encoding = self.encoding as *const Encoding; + let line = if enc == UTF_8 { + str::from_utf8(&bytes.split_to(ind + 1)) + .map_err(|_| ReadlinesError::EncodingError)? + .to_owned() + } else { + self.encoding + .decode(&bytes.split_to(ind + 1), DecoderTrap::Strict) + .map_err(|_| ReadlinesError::EncodingError)? + }; + // extend buffer with rest of the bytes; + self.buff.extend_from_slice(&bytes); + self.checked_buff = false; + return Ok(Async::Ready(Some(line))); + } + self.buff.extend_from_slice(&bytes); + Ok(Async::NotReady) } - if let Some(ind) = found { - // check if line is longer than limit - if ind + 1 > self.limit { + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(None)) => { + if self.buff.is_empty() { + return Ok(Async::Ready(None)); + } + if self.buff.len() > self.limit { return Err(ReadlinesError::LimitOverflow); } let enc: *const Encoding = self.encoding as *const Encoding; let line = if enc == UTF_8 { - str::from_utf8(&bytes.split_to(ind + 1)) + str::from_utf8(&self.buff) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { self.encoding - .decode(&bytes.split_to(ind + 1), DecoderTrap::Strict) + .decode(&self.buff, DecoderTrap::Strict) .map_err(|_| ReadlinesError::EncodingError)? }; - // extend buffer with rest of the bytes; - self.buff.extend_from_slice(&bytes); - self.checked_buff = false; - return Ok(Async::Ready(Some(line))); + self.buff.clear(); + Ok(Async::Ready(Some(line))) } - self.buff.extend_from_slice(&bytes); - Ok(Async::NotReady) + Err(e) => Err(ReadlinesError::from(e)), } - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(None)) => { - if self.buff.is_empty() { - return Ok(Async::Ready(None)); - } - if self.buff.len() > self.limit { - return Err(ReadlinesError::LimitOverflow); - } - let enc: *const Encoding = self.encoding as *const Encoding; - let line = if enc == UTF_8 { - str::from_utf8(&self.buff) - .map_err(|_| ReadlinesError::EncodingError)? - .to_owned() - } else { - self.encoding - .decode(&self.buff, DecoderTrap::Strict) - .map_err(|_| ReadlinesError::EncodingError)? - }; - self.buff.clear(); - Ok(Async::Ready(Some(line))) - } - Err(e) => Err(ReadlinesError::from(e)), + } else { + Ok(Async::Ready(None)) } } } @@ -362,7 +366,7 @@ pub struct MessageBody { impl MessageBody { /// Create `MessageBody` for request. - pub fn new(req: T) -> MessageBody { + pub fn new(req: &mut T) -> MessageBody { let mut len = None; if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { @@ -379,7 +383,7 @@ impl MessageBody { MessageBody { limit: 262_144, length: len, - stream: Some(req.payload()), + stream: req.payload(), fut: None, err: None, } @@ -424,6 +428,10 @@ where } } + if self.stream.is_none() { + return Ok(Async::Ready(Bytes::new())); + } + // future let limit = self.limit; self.fut = Some(Box::new( @@ -457,7 +465,7 @@ pub struct UrlEncoded { impl UrlEncoded { /// Create a new future to URL encode a request - pub fn new(req: T) -> UrlEncoded { + pub fn new(req: &mut T) -> UrlEncoded { // check content type if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { return Self::err(UrlencodedError::ContentType); @@ -482,7 +490,7 @@ impl UrlEncoded { UrlEncoded { encoding, - stream: Some(req.payload()), + stream: req.payload(), limit: 262_144, length: len, fut: None, @@ -694,7 +702,7 @@ mod tests { #[test] fn test_urlencoded_error() { - let req = TestRequest::with_header( + let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -705,7 +713,7 @@ mod tests { UrlencodedError::UnknownLength ); - let req = TestRequest::with_header( + let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -716,7 +724,7 @@ mod tests { UrlencodedError::Overflow ); - let req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain") + let mut req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain") .header(header::CONTENT_LENGTH, "10") .finish(); assert_eq!( @@ -727,7 +735,7 @@ mod tests { #[test] fn test_urlencoded() { - let req = TestRequest::with_header( + let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -743,7 +751,7 @@ mod tests { }) ); - let req = TestRequest::with_header( + let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8", ) @@ -762,19 +770,20 @@ mod tests { #[test] fn test_message_body() { - let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); + let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); match req.body().poll().err().unwrap() { PayloadError::UnknownLength => (), _ => unreachable!("error"), } - let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); + let mut req = + TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); match req.body().poll().err().unwrap() { PayloadError::Overflow => (), _ => unreachable!("error"), } - let req = TestRequest::default() + let mut req = TestRequest::default() .set_payload(Bytes::from_static(b"test")) .finish(); match req.body().poll().ok().unwrap() { @@ -782,7 +791,7 @@ mod tests { _ => unreachable!("error"), } - let req = TestRequest::default() + let mut req = TestRequest::default() .set_payload(Bytes::from_static(b"11111111111111")) .finish(); match req.body().limit(5).poll().err().unwrap() { @@ -793,14 +802,14 @@ mod tests { #[test] fn test_readlines() { - let req = TestRequest::default() + let mut req = TestRequest::default() .set_payload(Bytes::from_static( b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ industry. Lorem Ipsum has been the industry's standard dummy\n\ Contrary to popular belief, Lorem Ipsum is not simply random text.", )) .finish(); - let mut r = Readlines::new(req); + let mut r = Readlines::new(&mut req); match r.poll().ok().unwrap() { Async::Ready(Some(s)) => assert_eq!( s, diff --git a/src/json.rs b/src/json.rs index fc1ab4d25..6cd1e87ab 100644 --- a/src/json.rs +++ b/src/json.rs @@ -50,7 +50,7 @@ pub struct JsonBody { impl JsonBody { /// Create `JsonBody` for request. - pub fn new(req: T) -> Self { + pub fn new(req: &mut T) -> Self { // check content-type let json = if let Ok(Some(mime)) = req.mime_type() { mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) @@ -79,7 +79,7 @@ impl JsonBody { JsonBody { limit: 262_144, length: len, - stream: Some(req.payload()), + stream: req.payload(), fut: None, err: None, } @@ -164,11 +164,11 @@ mod tests { #[test] fn test_json_body() { - let req = TestRequest::default().finish(); + let mut req = TestRequest::default().finish(); let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - let req = TestRequest::default() + let mut req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/text"), @@ -177,7 +177,7 @@ mod tests { let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - let req = TestRequest::default() + let mut req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), @@ -190,7 +190,7 @@ mod tests { let mut json = req.json::().limit(100); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); - let req = TestRequest::default() + let mut req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), diff --git a/src/lib.rs b/src/lib.rs index cdb4f0382..a3ca52eda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,7 +78,6 @@ mod httpcodes; mod httpmessage; mod json; mod message; -mod payload; mod request; mod response; mod service; @@ -111,7 +110,6 @@ pub mod dev { pub use crate::httpmessage::{MessageBody, Readlines, UrlEncoded}; pub use crate::json::JsonBody; - pub use crate::payload::{Payload, PayloadBuffer}; pub use crate::response::ResponseBuilder; } diff --git a/src/request.rs b/src/request.rs index b60a772e1..4df95d450 100644 --- a/src/request.rs +++ b/src/request.rs @@ -10,7 +10,8 @@ use crate::error::PayloadError; use crate::extensions::Extensions; use crate::httpmessage::HttpMessage; use crate::message::{Message, MessagePool, RequestHead}; -use crate::payload::Payload; + +use crate::h1::Payload; /// Request pub struct Request

    { @@ -29,8 +30,8 @@ where } #[inline] - fn payload(mut self) -> P { - self.payload.take().unwrap() + fn payload(&mut self) -> Option

    { + self.payload.take() } } @@ -62,9 +63,9 @@ impl Request { } /// Take request's payload - pub fn take_payload(mut self) -> (Payload, Request<()>) { + pub fn take_payload(mut self) -> (Option, Request<()>) { ( - self.payload.take().unwrap(), + self.payload.take(), Request { payload: Some(()), inner: self.inner.clone(), diff --git a/src/service/mod.rs b/src/service/mod.rs index 83a40bd12..3939ab99c 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -1,3 +1,5 @@ +use h2::RecvStream; + mod senderror; pub use self::senderror::{SendError, SendResponse}; diff --git a/src/test.rs b/src/test.rs index 852dd3c0b..c68bbf8e5 100644 --- a/src/test.rs +++ b/src/test.rs @@ -6,8 +6,8 @@ use cookie::Cookie; use http::header::HeaderName; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; +use crate::h1::Payload; use crate::header::{Header, IntoHeaderValue}; -use crate::payload::Payload; use crate::Request; /// Test `Request` builder diff --git a/tests/test_client.rs b/tests/test_client.rs index 606bac22a..6f502b0af 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -47,7 +47,7 @@ fn test_h1_v2() { assert!(repr.contains("ClientRequest")); assert!(repr.contains("x-test")); - let response = srv.block_on(request.send(&mut connector)).unwrap(); + let mut response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response @@ -55,7 +55,7 @@ fn test_h1_v2() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); let request = srv.post().finish().unwrap(); - let response = srv.block_on(request.send(&mut connector)).unwrap(); + let mut response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response diff --git a/tests/test_server.rs b/tests/test_server.rs index 9fa27e71b..53db38403 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -89,7 +89,7 @@ fn test_h2_body() -> std::io::Result<()> { .map_err(|e| println!("Openssl error: {}", e)) .and_then( h2::H2Service::build() - .finish(|req: Request<_>| { + .finish(|mut req: Request<_>| { req.body() .limit(1024 * 1024) .and_then(|body| Ok(Response::Ok().body(body))) @@ -101,7 +101,7 @@ fn test_h2_body() -> std::io::Result<()> { let req = client::ClientRequest::get(srv.surl("/")) .body(data.clone()) .unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); let body = srv.block_on(response.body().limit(1024 * 1024)).unwrap(); @@ -350,7 +350,7 @@ fn test_headers() { let req = srv.get().finish().unwrap(); - let response = srv.block_on(req.send(&mut connector)).unwrap(); + let mut response = srv.block_on(req.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response @@ -387,7 +387,7 @@ fn test_body() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -402,7 +402,7 @@ fn test_head_empty() { }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -428,7 +428,7 @@ fn test_head_binary() { }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -477,7 +477,7 @@ fn test_body_length() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -496,7 +496,7 @@ fn test_body_chunked_explicit() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -517,7 +517,7 @@ fn test_body_chunked_implicit() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -540,7 +540,7 @@ fn test_response_http_error_handling() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 07f857d7a..bf5a3c41e 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -5,7 +5,6 @@ use actix_http_test::TestServer; use actix_service::NewService; use actix_utils::framed::IntoFramed; use actix_utils::stream::TakeItem; -use actix_web::ws as web_ws; use bytes::{Bytes, BytesMut}; use futures::future::{lazy, ok, Either}; use futures::{Future, Sink, Stream}; @@ -105,37 +104,4 @@ fn test_simple() { item, Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) ); - - { - let mut sys = actix_web::actix::System::new("test"); - let url = srv.url("/"); - - let (reader, mut writer) = sys - .block_on(lazy(|| web_ws::Client::new(url).connect())) - .unwrap(); - - writer.text("text"); - let (item, reader) = sys.block_on(reader.into_future()).unwrap(); - assert_eq!(item, Some(web_ws::Message::Text("text".to_owned()))); - - writer.binary(b"text".as_ref()); - let (item, reader) = sys.block_on(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(web_ws::Message::Binary(Bytes::from_static(b"text").into())) - ); - - writer.ping("ping"); - let (item, reader) = sys.block_on(reader.into_future()).unwrap(); - assert_eq!(item, Some(web_ws::Message::Pong("ping".to_owned()))); - - writer.close(Some(web_ws::CloseCode::Normal.into())); - let (item, _) = sys.block_on(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(web_ws::Message::Close(Some( - web_ws::CloseCode::Normal.into() - ))) - ); - } } From c4596b0bd6ce2758a79a93e11f2df77dc1f0f94a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Feb 2019 13:24:24 -0800 Subject: [PATCH 122/208] add headers from actix-web --- Cargo.toml | 3 + src/header.rs | 155 ---- src/header/common/accept.rs | 160 ++++ src/header/common/accept_charset.rs | 69 ++ src/header/common/accept_encoding.rs | 72 ++ src/header/common/accept_language.rs | 75 ++ src/header/common/allow.rs | 85 +++ src/header/common/cache_control.rs | 257 +++++++ src/header/common/content_disposition.rs | 918 +++++++++++++++++++++++ src/header/common/content_language.rs | 65 ++ src/header/common/content_range.rs | 208 +++++ src/header/common/content_type.rs | 122 +++ src/header/common/date.rs | 42 ++ src/header/common/etag.rs | 96 +++ src/header/common/expires.rs | 39 + src/header/common/if_match.rs | 70 ++ src/header/common/if_modified_since.rs | 39 + src/header/common/if_none_match.rs | 92 +++ src/header/common/if_range.rs | 116 +++ src/header/common/if_unmodified_since.rs | 40 + src/header/common/last_modified.rs | 38 + src/header/common/mod.rs | 352 +++++++++ src/header/common/range.rs | 434 +++++++++++ src/header/mod.rs | 465 ++++++++++++ src/header/shared/charset.rs | 153 ++++ src/header/shared/encoding.rs | 58 ++ src/header/shared/entity.rs | 265 +++++++ src/header/shared/httpdate.rs | 118 +++ src/header/shared/mod.rs | 14 + src/header/shared/quality_item.rs | 291 +++++++ tests/test_ws.rs | 2 +- 31 files changed, 4757 insertions(+), 156 deletions(-) delete mode 100644 src/header.rs create mode 100644 src/header/common/accept.rs create mode 100644 src/header/common/accept_charset.rs create mode 100644 src/header/common/accept_encoding.rs create mode 100644 src/header/common/accept_language.rs create mode 100644 src/header/common/allow.rs create mode 100644 src/header/common/cache_control.rs create mode 100644 src/header/common/content_disposition.rs create mode 100644 src/header/common/content_language.rs create mode 100644 src/header/common/content_range.rs create mode 100644 src/header/common/content_type.rs create mode 100644 src/header/common/date.rs create mode 100644 src/header/common/etag.rs create mode 100644 src/header/common/expires.rs create mode 100644 src/header/common/if_match.rs create mode 100644 src/header/common/if_modified_since.rs create mode 100644 src/header/common/if_none_match.rs create mode 100644 src/header/common/if_range.rs create mode 100644 src/header/common/if_unmodified_since.rs create mode 100644 src/header/common/last_modified.rs create mode 100644 src/header/common/mod.rs create mode 100644 src/header/common/range.rs create mode 100644 src/header/mod.rs create mode 100644 src/header/shared/charset.rs create mode 100644 src/header/shared/encoding.rs create mode 100644 src/header/shared/entity.rs create mode 100644 src/header/shared/httpdate.rs create mode 100644 src/header/shared/mod.rs create mode 100644 src/header/shared/quality_item.rs diff --git a/Cargo.toml b/Cargo.toml index 23938f2fa..ea246762e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,10 +56,13 @@ h2 = "0.1.16" http = "0.1.8" httparse = "1.3" indexmap = "1.0" +lazy_static = "1.0" +language-tags = "0.2" log = "0.4" mime = "0.3" percent-encoding = "1.0" rand = "0.6" +regex = "1.0" serde = "1.0" serde_json = "1.0" sha1 = "0.6" diff --git a/src/header.rs b/src/header.rs deleted file mode 100644 index 6276dd4f4..000000000 --- a/src/header.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! Various http headers - -use bytes::Bytes; -pub use http::header::*; -use http::Error as HttpError; -use mime::Mime; - -use crate::error::ParseError; -use crate::httpmessage::HttpMessage; - -#[doc(hidden)] -/// A trait for any object that will represent a header field and value. -pub trait Header -where - Self: IntoHeaderValue, -{ - /// Returns the name of the header field - fn name() -> HeaderName; - - /// Parse a header - fn parse(msg: &T) -> Result; -} - -#[doc(hidden)] -/// A trait for any object that can be Converted to a `HeaderValue` -pub trait IntoHeaderValue: Sized { - /// The type returned in the event of a conversion error. - type Error: Into; - - /// Try to convert value to a Header value. - fn try_into(self) -> Result; -} - -impl IntoHeaderValue for HeaderValue { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - Ok(self) - } -} - -impl<'a> IntoHeaderValue for &'a str { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - self.parse() - } -} - -impl<'a> IntoHeaderValue for &'a [u8] { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_bytes(self) - } -} - -impl IntoHeaderValue for Bytes { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(self) - } -} - -impl IntoHeaderValue for Vec { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(self)) - } -} - -impl IntoHeaderValue for String { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(self)) - } -} - -impl IntoHeaderValue for Mime { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(format!("{}", self))) - } -} - -/// Represents supported types of content encodings -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum ContentEncoding { - /// Automatically select encoding based on encoding negotiation - Auto, - /// A format using the Brotli algorithm - Br, - /// A format using the zlib structure with deflate algorithm - Deflate, - /// Gzip algorithm - Gzip, - /// Indicates the identity function (i.e. no compression, nor modification) - Identity, -} - -impl ContentEncoding { - #[inline] - /// Is the content compressed? - pub fn is_compression(self) -> bool { - match self { - ContentEncoding::Identity | ContentEncoding::Auto => false, - _ => true, - } - } - - #[inline] - /// Convert content encoding to string - pub fn as_str(self) -> &'static str { - match self { - ContentEncoding::Br => "br", - ContentEncoding::Gzip => "gzip", - ContentEncoding::Deflate => "deflate", - ContentEncoding::Identity | ContentEncoding::Auto => "identity", - } - } - - #[inline] - /// default quality value - pub fn quality(self) -> f64 { - match self { - ContentEncoding::Br => 1.1, - ContentEncoding::Gzip => 1.0, - ContentEncoding::Deflate => 0.9, - ContentEncoding::Identity | ContentEncoding::Auto => 0.1, - } - } -} - -// TODO: remove memory allocation -impl<'a> From<&'a str> for ContentEncoding { - fn from(s: &'a str) -> ContentEncoding { - match AsRef::::as_ref(&s.trim().to_lowercase()) { - "br" => ContentEncoding::Br, - "gzip" => ContentEncoding::Gzip, - "deflate" => ContentEncoding::Deflate, - _ => ContentEncoding::Identity, - } - } -} diff --git a/src/header/common/accept.rs b/src/header/common/accept.rs new file mode 100644 index 000000000..d52eba241 --- /dev/null +++ b/src/header/common/accept.rs @@ -0,0 +1,160 @@ +use mime::Mime; + +use crate::header::{qitem, QualityItem}; +use crate::http::header; + +header! { + /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) + /// + /// The `Accept` header field can be used by user agents to specify + /// response media types that are acceptable. Accept header fields can + /// be used to indicate that the request is specifically limited to a + /// small set of desired types, as in the case of a request for an + /// in-line image + /// + /// # ABNF + /// + /// ```text + /// Accept = #( media-range [ accept-params ] ) + /// + /// media-range = ( "*/*" + /// / ( type "/" "*" ) + /// / ( type "/" subtype ) + /// ) *( OWS ";" OWS parameter ) + /// accept-params = weight *( accept-ext ) + /// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ] + /// ``` + /// + /// # Example values + /// * `audio/*; q=0.2, audio/basic` + /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` + /// + /// # Examples + /// ```rust + /// # extern crate actix_http; + /// extern crate mime; + /// use actix_http::Response; + /// use actix_http::http::header::{Accept, qitem}; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// + /// builder.set( + /// Accept(vec![ + /// qitem(mime::TEXT_HTML), + /// ]) + /// ); + /// # } + /// ``` + /// + /// ```rust + /// # extern crate actix_http; + /// extern crate mime; + /// use actix_http::Response; + /// use actix_http::http::header::{Accept, qitem}; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// + /// builder.set( + /// Accept(vec![ + /// qitem(mime::APPLICATION_JSON), + /// ]) + /// ); + /// # } + /// ``` + /// + /// ```rust + /// # extern crate actix_http; + /// extern crate mime; + /// use actix_http::Response; + /// use actix_http::http::header::{Accept, QualityItem, q, qitem}; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// + /// builder.set( + /// Accept(vec![ + /// qitem(mime::TEXT_HTML), + /// qitem("application/xhtml+xml".parse().unwrap()), + /// QualityItem::new( + /// mime::TEXT_XML, + /// q(900) + /// ), + /// qitem("image/webp".parse().unwrap()), + /// QualityItem::new( + /// mime::STAR_STAR, + /// q(800) + /// ), + /// ]) + /// ); + /// # } + /// ``` + (Accept, header::ACCEPT) => (QualityItem)+ + + test_accept { + // Tests from the RFC + test_header!( + test1, + vec![b"audio/*; q=0.2, audio/basic"], + Some(HeaderField(vec![ + QualityItem::new("audio/*".parse().unwrap(), q(200)), + qitem("audio/basic".parse().unwrap()), + ]))); + test_header!( + test2, + vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], + Some(HeaderField(vec![ + QualityItem::new(mime::TEXT_PLAIN, q(500)), + qitem(mime::TEXT_HTML), + QualityItem::new( + "text/x-dvi".parse().unwrap(), + q(800)), + qitem("text/x-c".parse().unwrap()), + ]))); + // Custom tests + test_header!( + test3, + vec![b"text/plain; charset=utf-8"], + Some(Accept(vec![ + qitem(mime::TEXT_PLAIN_UTF_8), + ]))); + test_header!( + test4, + vec![b"text/plain; charset=utf-8; q=0.5"], + Some(Accept(vec![ + QualityItem::new(mime::TEXT_PLAIN_UTF_8, + q(500)), + ]))); + + #[test] + fn test_fuzzing1() { + use crate::test::TestRequest; + let req = TestRequest::with_header(crate::header::ACCEPT, "chunk#;e").finish(); + let header = Accept::parse(&req); + assert!(header.is_ok()); + } + } +} + +impl Accept { + /// A constructor to easily create `Accept: */*`. + pub fn star() -> Accept { + Accept(vec![qitem(mime::STAR_STAR)]) + } + + /// A constructor to easily create `Accept: application/json`. + pub fn json() -> Accept { + Accept(vec![qitem(mime::APPLICATION_JSON)]) + } + + /// A constructor to easily create `Accept: text/*`. + pub fn text() -> Accept { + Accept(vec![qitem(mime::TEXT_STAR)]) + } + + /// A constructor to easily create `Accept: image/*`. + pub fn image() -> Accept { + Accept(vec![qitem(mime::IMAGE_STAR)]) + } +} diff --git a/src/header/common/accept_charset.rs b/src/header/common/accept_charset.rs new file mode 100644 index 000000000..117e2015d --- /dev/null +++ b/src/header/common/accept_charset.rs @@ -0,0 +1,69 @@ +use crate::header::{Charset, QualityItem, ACCEPT_CHARSET}; + +header! { + /// `Accept-Charset` header, defined in + /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) + /// + /// The `Accept-Charset` header field can be sent by a user agent to + /// indicate what charsets are acceptable in textual response content. + /// This field allows user agents capable of understanding more + /// comprehensive or special-purpose charsets to signal that capability + /// to an origin server that is capable of representing information in + /// those charsets. + /// + /// # ABNF + /// + /// ```text + /// Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) + /// ``` + /// + /// # Example values + /// * `iso-8859-5, unicode-1-1;q=0.8` + /// + /// # Examples + /// ```rust + /// # extern crate actix_http; + /// use actix_http::Response; + /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) + /// ); + /// # } + /// ``` + /// ```rust + /// # extern crate actix_http; + /// use actix_http::Response; + /// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem}; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// AcceptCharset(vec![ + /// QualityItem::new(Charset::Us_Ascii, q(900)), + /// QualityItem::new(Charset::Iso_8859_10, q(200)), + /// ]) + /// ); + /// # } + /// ``` + /// ```rust + /// # extern crate actix_http; + /// use actix_http::Response; + /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) + /// ); + /// # } + /// ``` + (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)+ + + test_accept_charset { + /// Test case from RFC + test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); + } +} diff --git a/src/header/common/accept_encoding.rs b/src/header/common/accept_encoding.rs new file mode 100644 index 000000000..c90f529bc --- /dev/null +++ b/src/header/common/accept_encoding.rs @@ -0,0 +1,72 @@ +use header::{Encoding, QualityItem}; + +header! { + /// `Accept-Encoding` header, defined in + /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.4) + /// + /// The `Accept-Encoding` header field can be used by user agents to + /// indicate what response content-codings are + /// acceptable in the response. An `identity` token is used as a synonym + /// for "no encoding" in order to communicate when no encoding is + /// preferred. + /// + /// # ABNF + /// + /// ```text + /// Accept-Encoding = #( codings [ weight ] ) + /// codings = content-coding / "identity" / "*" + /// ``` + /// + /// # Example values + /// * `compress, gzip` + /// * `` + /// * `*` + /// * `compress;q=0.5, gzip;q=1` + /// * `gzip;q=1.0, identity; q=0.5, *;q=0` + /// + /// # Examples + /// ``` + /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; + /// + /// let mut headers = Headers::new(); + /// headers.set( + /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) + /// ); + /// ``` + /// ``` + /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; + /// + /// let mut headers = Headers::new(); + /// headers.set( + /// AcceptEncoding(vec![ + /// qitem(Encoding::Chunked), + /// qitem(Encoding::Gzip), + /// qitem(Encoding::Deflate), + /// ]) + /// ); + /// ``` + /// ``` + /// use hyper::header::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem}; + /// + /// let mut headers = Headers::new(); + /// headers.set( + /// AcceptEncoding(vec![ + /// qitem(Encoding::Chunked), + /// QualityItem::new(Encoding::Gzip, q(600)), + /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), + /// ]) + /// ); + /// ``` + (AcceptEncoding, "Accept-Encoding") => (QualityItem)* + + test_accept_encoding { + // From the RFC + test_header!(test1, vec![b"compress, gzip"]); + test_header!(test2, vec![b""], Some(AcceptEncoding(vec![]))); + test_header!(test3, vec![b"*"]); + // Note: Removed quality 1 from gzip + test_header!(test4, vec![b"compress;q=0.5, gzip"]); + // Note: Removed quality 1 from gzip + test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); + } +} diff --git a/src/header/common/accept_language.rs b/src/header/common/accept_language.rs new file mode 100644 index 000000000..55879b57f --- /dev/null +++ b/src/header/common/accept_language.rs @@ -0,0 +1,75 @@ +use crate::header::{QualityItem, ACCEPT_LANGUAGE}; +use language_tags::LanguageTag; + +header! { + /// `Accept-Language` header, defined in + /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) + /// + /// The `Accept-Language` header field can be used by user agents to + /// indicate the set of natural languages that are preferred in the + /// response. + /// + /// # ABNF + /// + /// ```text + /// Accept-Language = 1#( language-range [ weight ] ) + /// language-range = + /// ``` + /// + /// # Example values + /// * `da, en-gb;q=0.8, en;q=0.7` + /// * `en-us;q=1.0, en;q=0.5, fr` + /// + /// # Examples + /// + /// ```rust + /// # extern crate actix_http; + /// # extern crate language_tags; + /// use actix_http::Response; + /// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem}; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// let mut langtag: LanguageTag = Default::default(); + /// langtag.language = Some("en".to_owned()); + /// langtag.region = Some("US".to_owned()); + /// builder.set( + /// AcceptLanguage(vec![ + /// qitem(langtag), + /// ]) + /// ); + /// # } + /// ``` + /// + /// ```rust + /// # extern crate actix_http; + /// # #[macro_use] extern crate language_tags; + /// use actix_http::Response; + /// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem}; + /// # + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// AcceptLanguage(vec![ + /// qitem(langtag!(da)), + /// QualityItem::new(langtag!(en;;;GB), q(800)), + /// QualityItem::new(langtag!(en), q(700)), + /// ]) + /// ); + /// # } + /// ``` + (AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem)+ + + test_accept_language { + // From the RFC + test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); + // Own test + test_header!( + test2, vec![b"en-US, en; q=0.5, fr"], + Some(AcceptLanguage(vec![ + qitem("en-US".parse().unwrap()), + QualityItem::new("en".parse().unwrap(), q(500)), + qitem("fr".parse().unwrap()), + ]))); + } +} diff --git a/src/header/common/allow.rs b/src/header/common/allow.rs new file mode 100644 index 000000000..432cc00d5 --- /dev/null +++ b/src/header/common/allow.rs @@ -0,0 +1,85 @@ +use http::Method; +use http::header; + +header! { + /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) + /// + /// The `Allow` header field lists the set of methods advertised as + /// supported by the target resource. The purpose of this field is + /// strictly to inform the recipient of valid request methods associated + /// with the resource. + /// + /// # ABNF + /// + /// ```text + /// Allow = #method + /// ``` + /// + /// # Example values + /// * `GET, HEAD, PUT` + /// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr` + /// * `` + /// + /// # Examples + /// + /// ```rust + /// # extern crate http; + /// # extern crate actix_http; + /// use actix_http::Response; + /// use actix_http::http::header::Allow; + /// use http::Method; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// Allow(vec![Method::GET]) + /// ); + /// # } + /// ``` + /// + /// ```rust + /// # extern crate http; + /// # extern crate actix_http; + /// use actix_http::Response; + /// use actix_http::http::header::Allow; + /// use http::Method; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// Allow(vec![ + /// Method::GET, + /// Method::POST, + /// Method::PATCH, + /// ]) + /// ); + /// # } + /// ``` + (Allow, header::ALLOW) => (Method)* + + test_allow { + // From the RFC + test_header!( + test1, + vec![b"GET, HEAD, PUT"], + Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT]))); + // Own tests + test_header!( + test2, + vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"], + Some(HeaderField(vec![ + Method::OPTIONS, + Method::GET, + Method::PUT, + Method::POST, + Method::DELETE, + Method::HEAD, + Method::TRACE, + Method::CONNECT, + Method::PATCH]))); + test_header!( + test3, + vec![b""], + Some(HeaderField(Vec::::new()))); + } +} diff --git a/src/header/common/cache_control.rs b/src/header/common/cache_control.rs new file mode 100644 index 000000000..0b79ea7c0 --- /dev/null +++ b/src/header/common/cache_control.rs @@ -0,0 +1,257 @@ +use std::fmt::{self, Write}; +use std::str::FromStr; + +use http::header; + +use crate::header::{ + fmt_comma_delimited, from_comma_delimited, Header, IntoHeaderValue, Writer, +}; + +/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) +/// +/// The `Cache-Control` header field is used to specify directives for +/// caches along the request/response chain. Such cache directives are +/// unidirectional in that the presence of a directive in a request does +/// not imply that the same directive is to be given in the response. +/// +/// # ABNF +/// +/// ```text +/// Cache-Control = 1#cache-directive +/// cache-directive = token [ "=" ( token / quoted-string ) ] +/// ``` +/// +/// # Example values +/// +/// * `no-cache` +/// * `private, community="UCI"` +/// * `max-age=30` +/// +/// # Examples +/// ```rust +/// use actix_http::Response; +/// use actix_http::http::header::{CacheControl, CacheDirective}; +/// +/// let mut builder = Response::Ok(); +/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); +/// ``` +/// +/// ```rust +/// use actix_http::Response; +/// use actix_http::http::header::{CacheControl, CacheDirective}; +/// +/// let mut builder = Response::Ok(); +/// builder.set(CacheControl(vec![ +/// CacheDirective::NoCache, +/// CacheDirective::Private, +/// CacheDirective::MaxAge(360u32), +/// CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())), +/// ])); +/// ``` +#[derive(PartialEq, Clone, Debug)] +pub struct CacheControl(pub Vec); + +__hyper__deref!(CacheControl => Vec); + +//TODO: this could just be the header! macro +impl Header for CacheControl { + fn name() -> header::HeaderName { + header::CACHE_CONTROL + } + + #[inline] + fn parse(msg: &T) -> Result + where + T: crate::HttpMessage, + { + let directives = from_comma_delimited(msg.headers().get_all(Self::name()))?; + if !directives.is_empty() { + Ok(CacheControl(directives)) + } else { + Err(crate::error::ParseError::Header) + } + } +} + +impl fmt::Display for CacheControl { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt_comma_delimited(f, &self[..]) + } +} + +impl IntoHeaderValue for CacheControl { + type Error = header::InvalidHeaderValueBytes; + + fn try_into(self) -> Result { + let mut writer = Writer::new(); + let _ = write!(&mut writer, "{}", self); + header::HeaderValue::from_shared(writer.take()) + } +} + +/// `CacheControl` contains a list of these directives. +#[derive(PartialEq, Clone, Debug)] +pub enum CacheDirective { + /// "no-cache" + NoCache, + /// "no-store" + NoStore, + /// "no-transform" + NoTransform, + /// "only-if-cached" + OnlyIfCached, + + // request directives + /// "max-age=delta" + MaxAge(u32), + /// "max-stale=delta" + MaxStale(u32), + /// "min-fresh=delta" + MinFresh(u32), + + // response directives + /// "must-revalidate" + MustRevalidate, + /// "public" + Public, + /// "private" + Private, + /// "proxy-revalidate" + ProxyRevalidate, + /// "s-maxage=delta" + SMaxAge(u32), + + /// Extension directives. Optionally include an argument. + Extension(String, Option), +} + +impl fmt::Display for CacheDirective { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::CacheDirective::*; + fmt::Display::fmt( + match *self { + NoCache => "no-cache", + NoStore => "no-store", + NoTransform => "no-transform", + OnlyIfCached => "only-if-cached", + + MaxAge(secs) => return write!(f, "max-age={}", secs), + MaxStale(secs) => return write!(f, "max-stale={}", secs), + MinFresh(secs) => return write!(f, "min-fresh={}", secs), + + MustRevalidate => "must-revalidate", + Public => "public", + Private => "private", + ProxyRevalidate => "proxy-revalidate", + SMaxAge(secs) => return write!(f, "s-maxage={}", secs), + + Extension(ref name, None) => &name[..], + Extension(ref name, Some(ref arg)) => { + return write!(f, "{}={}", name, arg); + } + }, + f, + ) + } +} + +impl FromStr for CacheDirective { + type Err = Option<::Err>; + fn from_str(s: &str) -> Result::Err>> { + use self::CacheDirective::*; + match s { + "no-cache" => Ok(NoCache), + "no-store" => Ok(NoStore), + "no-transform" => Ok(NoTransform), + "only-if-cached" => Ok(OnlyIfCached), + "must-revalidate" => Ok(MustRevalidate), + "public" => Ok(Public), + "private" => Ok(Private), + "proxy-revalidate" => Ok(ProxyRevalidate), + "" => Err(None), + _ => match s.find('=') { + Some(idx) if idx + 1 < s.len() => { + match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) { + ("max-age", secs) => secs.parse().map(MaxAge).map_err(Some), + ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some), + ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some), + ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some), + (left, right) => { + Ok(Extension(left.to_owned(), Some(right.to_owned()))) + } + } + } + Some(_) => Err(None), + None => Ok(Extension(s.to_owned(), None)), + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::header::Header; + use crate::test::TestRequest; + + #[test] + fn test_parse_multiple_headers() { + let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private") + .finish(); + let cache = Header::parse(&req); + assert_eq!( + cache.ok(), + Some(CacheControl(vec![ + CacheDirective::NoCache, + CacheDirective::Private, + ])) + ) + } + + #[test] + fn test_parse_argument() { + let req = + TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private") + .finish(); + let cache = Header::parse(&req); + assert_eq!( + cache.ok(), + Some(CacheControl(vec![ + CacheDirective::MaxAge(100), + CacheDirective::Private, + ])) + ) + } + + #[test] + fn test_parse_quote_form() { + let req = + TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish(); + let cache = Header::parse(&req); + assert_eq!( + cache.ok(), + Some(CacheControl(vec![CacheDirective::MaxAge(200)])) + ) + } + + #[test] + fn test_parse_extension() { + let req = + TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish(); + let cache = Header::parse(&req); + assert_eq!( + cache.ok(), + Some(CacheControl(vec![ + CacheDirective::Extension("foo".to_owned(), None), + CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())), + ])) + ) + } + + #[test] + fn test_parse_bad_syntax() { + let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish(); + let cache: Result = Header::parse(&req); + assert_eq!(cache.ok(), None) + } +} diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs new file mode 100644 index 000000000..e04f9c89f --- /dev/null +++ b/src/header/common/content_disposition.rs @@ -0,0 +1,918 @@ +// # References +// +// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt +// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt +// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt +// Browser conformance tests at: http://greenbytes.de/tech/tc2231/ +// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml + +use lazy_static::lazy_static; +use regex::Regex; +use std::fmt::{self, Write}; + +use crate::header::{self, ExtendedValue, Header, IntoHeaderValue, Writer}; + +/// Split at the index of the first `needle` if it exists or at the end. +fn split_once(haystack: &str, needle: char) -> (&str, &str) { + haystack.find(needle).map_or_else( + || (haystack, ""), + |sc| { + let (first, last) = haystack.split_at(sc); + (first, last.split_at(1).1) + }, + ) +} + +/// Split at the index of the first `needle` if it exists or at the end, trim the right of the +/// first part and the left of the last part. +fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) { + let (first, last) = split_once(haystack, needle); + (first.trim_right(), last.trim_left()) +} + +/// The implied disposition of the content of the HTTP body. +#[derive(Clone, Debug, PartialEq)] +pub enum DispositionType { + /// Inline implies default processing + Inline, + /// Attachment implies that the recipient should prompt the user to save the response locally, + /// rather than process it normally (as per its media type). + Attachment, + /// Used in *multipart/form-data* as defined in + /// [RFC7578](https://tools.ietf.org/html/rfc7578) to carry the field name and the file name. + FormData, + /// Extension type. Should be handled by recipients the same way as Attachment + Ext(String), +} + +impl<'a> From<&'a str> for DispositionType { + fn from(origin: &'a str) -> DispositionType { + if origin.eq_ignore_ascii_case("inline") { + DispositionType::Inline + } else if origin.eq_ignore_ascii_case("attachment") { + DispositionType::Attachment + } else if origin.eq_ignore_ascii_case("form-data") { + DispositionType::FormData + } else { + DispositionType::Ext(origin.to_owned()) + } + } +} + +/// Parameter in [`ContentDisposition`]. +/// +/// # Examples +/// ``` +/// use actix_http::http::header::DispositionParam; +/// +/// let param = DispositionParam::Filename(String::from("sample.txt")); +/// assert!(param.is_filename()); +/// assert_eq!(param.as_filename().unwrap(), "sample.txt"); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub enum DispositionParam { + /// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from + /// the form. + Name(String), + /// A plain file name. + Filename(String), + /// An extended file name. It must not exist for `ContentType::Formdata` according to + /// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2). + FilenameExt(ExtendedValue), + /// An unrecognized regular parameter as defined in + /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *reg-parameter*, in + /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *token "=" value*. Recipients should + /// ignore unrecognizable parameters. + Unknown(String, String), + /// An unrecognized extended paramater as defined in + /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *ext-parameter*, in + /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *ext-token "=" ext-value*. The single + /// trailling asterisk is not included. Recipients should ignore unrecognizable parameters. + UnknownExt(String, ExtendedValue), +} + +impl DispositionParam { + /// Returns `true` if the paramater is [`Name`](DispositionParam::Name). + #[inline] + pub fn is_name(&self) -> bool { + self.as_name().is_some() + } + + /// Returns `true` if the paramater is [`Filename`](DispositionParam::Filename). + #[inline] + pub fn is_filename(&self) -> bool { + self.as_filename().is_some() + } + + /// Returns `true` if the paramater is [`FilenameExt`](DispositionParam::FilenameExt). + #[inline] + pub fn is_filename_ext(&self) -> bool { + self.as_filename_ext().is_some() + } + + /// Returns `true` if the paramater is [`Unknown`](DispositionParam::Unknown) and the `name` + #[inline] + /// matches. + pub fn is_unknown>(&self, name: T) -> bool { + self.as_unknown(name).is_some() + } + + /// Returns `true` if the paramater is [`UnknownExt`](DispositionParam::UnknownExt) and the + /// `name` matches. + #[inline] + pub fn is_unknown_ext>(&self, name: T) -> bool { + self.as_unknown_ext(name).is_some() + } + + /// Returns the name if applicable. + #[inline] + pub fn as_name(&self) -> Option<&str> { + match self { + DispositionParam::Name(ref name) => Some(name.as_str()), + _ => None, + } + } + + /// Returns the filename if applicable. + #[inline] + pub fn as_filename(&self) -> Option<&str> { + match self { + DispositionParam::Filename(ref filename) => Some(filename.as_str()), + _ => None, + } + } + + /// Returns the filename* if applicable. + #[inline] + pub fn as_filename_ext(&self) -> Option<&ExtendedValue> { + match self { + DispositionParam::FilenameExt(ref value) => Some(value), + _ => None, + } + } + + /// Returns the value of the unrecognized regular parameter if it is + /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. + #[inline] + pub fn as_unknown>(&self, name: T) -> Option<&str> { + match self { + DispositionParam::Unknown(ref ext_name, ref value) + if ext_name.eq_ignore_ascii_case(name.as_ref()) => + { + Some(value.as_str()) + } + _ => None, + } + } + + /// Returns the value of the unrecognized extended parameter if it is + /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. + #[inline] + pub fn as_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { + match self { + DispositionParam::UnknownExt(ref ext_name, ref value) + if ext_name.eq_ignore_ascii_case(name.as_ref()) => + { + Some(value) + } + _ => None, + } + } +} + +/// A *Content-Disposition* header. It is compatible to be used either as +/// [a response header for the main body](https://mdn.io/Content-Disposition#As_a_response_header_for_the_main_body) +/// as (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266), or as +/// [a header for a multipart body](https://mdn.io/Content-Disposition#As_a_header_for_a_multipart_body) +/// as (re)defined in [RFC7587](https://tools.ietf.org/html/rfc7578). +/// +/// In a regular HTTP response, the *Content-Disposition* response header is a header indicating if +/// the content is expected to be displayed *inline* in the browser, that is, as a Web page or as +/// part of a Web page, or as an attachment, that is downloaded and saved locally, and also can be +/// used to attach additional metadata, such as the filename to use when saving the response payload +/// locally. +/// +/// In a *multipart/form-data* body, the HTTP *Content-Disposition* general header is a header that +/// can be used on the subpart of a multipart body to give information about the field it applies to. +/// The subpart is delimited by the boundary defined in the *Content-Type* header. Used on the body +/// itself, *Content-Disposition* has no effect. +/// +/// # ABNF + +/// ```text +/// content-disposition = "Content-Disposition" ":" +/// disposition-type *( ";" disposition-parm ) +/// +/// disposition-type = "inline" | "attachment" | disp-ext-type +/// ; case-insensitive +/// +/// disp-ext-type = token +/// +/// disposition-parm = filename-parm | disp-ext-parm +/// +/// filename-parm = "filename" "=" value +/// | "filename*" "=" ext-value +/// +/// disp-ext-parm = token "=" value +/// | ext-token "=" ext-value +/// +/// ext-token = +/// ``` +/// +/// **Note**: filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within +/// *multipart/form-data*. +/// +/// # Example +/// +/// ``` +/// use actix_http::http::header::{ +/// Charset, ContentDisposition, DispositionParam, DispositionType, +/// ExtendedValue, +/// }; +/// +/// let cd1 = ContentDisposition { +/// disposition: DispositionType::Attachment, +/// parameters: vec![DispositionParam::FilenameExt(ExtendedValue { +/// charset: Charset::Iso_8859_1, // The character set for the bytes of the filename +/// language_tag: None, // The optional language tag (see `language-tag` crate) +/// value: b"\xa9 Copyright 1989.txt".to_vec(), // the actual bytes of the filename +/// })], +/// }; +/// assert!(cd1.is_attachment()); +/// assert!(cd1.get_filename_ext().is_some()); +/// +/// let cd2 = ContentDisposition { +/// disposition: DispositionType::FormData, +/// parameters: vec![ +/// DispositionParam::Name(String::from("file")), +/// DispositionParam::Filename(String::from("bill.odt")), +/// ], +/// }; +/// assert_eq!(cd2.get_name(), Some("file")); // field name +/// assert_eq!(cd2.get_filename(), Some("bill.odt")); +/// ``` +/// +/// # WARN +/// If "filename" parameter is supplied, do not use the file name blindly, check and possibly +/// change to match local file system conventions if applicable, and do not use directory path +/// information that may be present. See [RFC2183](https://tools.ietf.org/html/rfc2183#section-2.3) +/// . +#[derive(Clone, Debug, PartialEq)] +pub struct ContentDisposition { + /// The disposition type + pub disposition: DispositionType, + /// Disposition parameters + pub parameters: Vec, +} + +impl ContentDisposition { + /// Parse a raw Content-Disposition header value. + pub fn from_raw(hv: &header::HeaderValue) -> Result { + // `header::from_one_raw_str` invokes `hv.to_str` which assumes `hv` contains only visible + // ASCII characters. So `hv.as_bytes` is necessary here. + let hv = String::from_utf8(hv.as_bytes().to_vec()) + .map_err(|_| crate::error::ParseError::Header)?; + let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';'); + if disp_type.is_empty() { + return Err(crate::error::ParseError::Header); + } + let mut cd = ContentDisposition { + disposition: disp_type.into(), + parameters: Vec::new(), + }; + + while !left.is_empty() { + let (param_name, new_left) = split_once_and_trim(left, '='); + if param_name.is_empty() || param_name == "*" || new_left.is_empty() { + return Err(crate::error::ParseError::Header); + } + left = new_left; + if param_name.ends_with('*') { + // extended parameters + let param_name = ¶m_name[..param_name.len() - 1]; // trim asterisk + let (ext_value, new_left) = split_once_and_trim(left, ';'); + left = new_left; + let ext_value = header::parse_extended_value(ext_value)?; + + let param = if param_name.eq_ignore_ascii_case("filename") { + DispositionParam::FilenameExt(ext_value) + } else { + DispositionParam::UnknownExt(param_name.to_owned(), ext_value) + }; + cd.parameters.push(param); + } else { + // regular parameters + let value = if left.starts_with('\"') { + // quoted-string: defined in RFC6266 -> RFC2616 Section 3.6 + let mut escaping = false; + let mut quoted_string = vec![]; + let mut end = None; + // search for closing quote + for (i, &c) in left.as_bytes().iter().skip(1).enumerate() { + if escaping { + escaping = false; + quoted_string.push(c); + } else if c == 0x5c { + // backslash + escaping = true; + } else if c == 0x22 { + // double quote + end = Some(i + 1); // cuz skipped 1 for the leading quote + break; + } else { + quoted_string.push(c); + } + } + left = &left[end.ok_or(crate::error::ParseError::Header)? + 1..]; + left = split_once(left, ';').1.trim_left(); + // In fact, it should not be Err if the above code is correct. + String::from_utf8(quoted_string) + .map_err(|_| crate::error::ParseError::Header)? + } else { + // token: won't contains semicolon according to RFC 2616 Section 2.2 + let (token, new_left) = split_once_and_trim(left, ';'); + left = new_left; + token.to_owned() + }; + if value.is_empty() { + return Err(crate::error::ParseError::Header); + } + + let param = if param_name.eq_ignore_ascii_case("name") { + DispositionParam::Name(value) + } else if param_name.eq_ignore_ascii_case("filename") { + DispositionParam::Filename(value) + } else { + DispositionParam::Unknown(param_name.to_owned(), value) + }; + cd.parameters.push(param); + } + } + + Ok(cd) + } + + /// Returns `true` if it is [`Inline`](DispositionType::Inline). + pub fn is_inline(&self) -> bool { + match self.disposition { + DispositionType::Inline => true, + _ => false, + } + } + + /// Returns `true` if it is [`Attachment`](DispositionType::Attachment). + pub fn is_attachment(&self) -> bool { + match self.disposition { + DispositionType::Attachment => true, + _ => false, + } + } + + /// Returns `true` if it is [`FormData`](DispositionType::FormData). + pub fn is_form_data(&self) -> bool { + match self.disposition { + DispositionType::FormData => true, + _ => false, + } + } + + /// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches. + pub fn is_ext>(&self, disp_type: T) -> bool { + match self.disposition { + DispositionType::Ext(ref t) + if t.eq_ignore_ascii_case(disp_type.as_ref()) => + { + true + } + _ => false, + } + } + + /// Return the value of *name* if exists. + pub fn get_name(&self) -> Option<&str> { + self.parameters.iter().filter_map(|p| p.as_name()).nth(0) + } + + /// Return the value of *filename* if exists. + pub fn get_filename(&self) -> Option<&str> { + self.parameters + .iter() + .filter_map(|p| p.as_filename()) + .nth(0) + } + + /// Return the value of *filename\** if exists. + pub fn get_filename_ext(&self) -> Option<&ExtendedValue> { + self.parameters + .iter() + .filter_map(|p| p.as_filename_ext()) + .nth(0) + } + + /// Return the value of the parameter which the `name` matches. + pub fn get_unknown>(&self, name: T) -> Option<&str> { + let name = name.as_ref(); + self.parameters + .iter() + .filter_map(|p| p.as_unknown(name)) + .nth(0) + } + + /// Return the value of the extended parameter which the `name` matches. + pub fn get_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { + let name = name.as_ref(); + self.parameters + .iter() + .filter_map(|p| p.as_unknown_ext(name)) + .nth(0) + } +} + +impl IntoHeaderValue for ContentDisposition { + type Error = header::InvalidHeaderValueBytes; + + fn try_into(self) -> Result { + let mut writer = Writer::new(); + let _ = write!(&mut writer, "{}", self); + header::HeaderValue::from_shared(writer.take()) + } +} + +impl Header for ContentDisposition { + fn name() -> header::HeaderName { + header::CONTENT_DISPOSITION + } + + fn parse(msg: &T) -> Result { + if let Some(h) = msg.headers().get(Self::name()) { + Self::from_raw(&h) + } else { + Err(crate::error::ParseError::Header) + } + } +} + +impl fmt::Display for DispositionType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + DispositionType::Inline => write!(f, "inline"), + DispositionType::Attachment => write!(f, "attachment"), + DispositionType::FormData => write!(f, "form-data"), + DispositionType::Ext(ref s) => write!(f, "{}", s), + } + } +} + +impl fmt::Display for DispositionParam { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // All ASCII control charaters (0-30, 127) excepting horizontal tab, double quote, and + // backslash should be escaped in quoted-string (i.e. "foobar"). + // Ref: RFC6266 S4.1 -> RFC2616 S2.2; RFC 7578 S4.2 -> RFC2183 S2 -> ... . + lazy_static! { + static ref RE: Regex = Regex::new("[\x01-\x08\x10\x1F\x7F\"\\\\]").unwrap(); + } + match self { + DispositionParam::Name(ref value) => write!(f, "name={}", value), + DispositionParam::Filename(ref value) => { + write!(f, "filename=\"{}\"", RE.replace_all(value, "\\$0").as_ref()) + } + DispositionParam::Unknown(ref name, ref value) => write!( + f, + "{}=\"{}\"", + name, + &RE.replace_all(value, "\\$0").as_ref() + ), + DispositionParam::FilenameExt(ref ext_value) => { + write!(f, "filename*={}", ext_value) + } + DispositionParam::UnknownExt(ref name, ref ext_value) => { + write!(f, "{}*={}", name, ext_value) + } + } + } +} + +impl fmt::Display for ContentDisposition { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.disposition)?; + self.parameters + .iter() + .map(|param| write!(f, "; {}", param)) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::{ContentDisposition, DispositionParam, DispositionType}; + use crate::header::shared::Charset; + use crate::header::{ExtendedValue, HeaderValue}; + + #[test] + fn test_from_raw_basic() { + assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err()); + + let a = HeaderValue::from_static( + "form-data; dummy=3; name=upload; filename=\"sample.png\"", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), + DispositionParam::Name("upload".to_owned()), + DispositionParam::Filename("sample.png".to_owned()), + ], + }; + assert_eq!(a, b); + + let a = HeaderValue::from_static("attachment; filename=\"image.jpg\""); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![DispositionParam::Filename("image.jpg".to_owned())], + }; + assert_eq!(a, b); + + let a = HeaderValue::from_static("inline; filename=image.jpg"); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![DispositionParam::Filename("image.jpg".to_owned())], + }; + assert_eq!(a, b); + + let a = HeaderValue::from_static( + "attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![DispositionParam::Unknown( + String::from("creation-date"), + "Wed, 12 Feb 1997 16:29:51 -0500".to_owned(), + )], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_extended() { + let a = HeaderValue::from_static( + "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![DispositionParam::FilenameExt(ExtendedValue { + charset: Charset::Ext(String::from("UTF-8")), + language_tag: None, + value: vec![ + 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, + b'r', b'a', b't', b'e', b's', + ], + })], + }; + assert_eq!(a, b); + + let a = HeaderValue::from_static( + "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![DispositionParam::FilenameExt(ExtendedValue { + charset: Charset::Ext(String::from("UTF-8")), + language_tag: None, + value: vec![ + 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, + b'r', b'a', b't', b'e', b's', + ], + })], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_extra_whitespace() { + let a = HeaderValue::from_static( + "form-data ; du-mmy= 3 ; name =upload ; filename = \"sample.png\" ; ", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Unknown("du-mmy".to_owned(), "3".to_owned()), + DispositionParam::Name("upload".to_owned()), + DispositionParam::Filename("sample.png".to_owned()), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_unordered() { + let a = HeaderValue::from_static( + "form-data; dummy=3; filename=\"sample.png\" ; name=upload;", + // Actually, a trailling semolocon is not compliant. But it is fine to accept. + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), + DispositionParam::Filename("sample.png".to_owned()), + DispositionParam::Name("upload".to_owned()), + ], + }; + assert_eq!(a, b); + + let a = HeaderValue::from_str( + "attachment; filename*=iso-8859-1''foo-%E4.html; filename=\"foo-ä.html\"", + ) + .unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![ + DispositionParam::FilenameExt(ExtendedValue { + charset: Charset::Iso_8859_1, + language_tag: None, + value: b"foo-\xe4.html".to_vec(), + }), + DispositionParam::Filename("foo-ä.html".to_owned()), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_only_disp() { + let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment")) + .unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![], + }; + assert_eq!(a, b); + + let a = + ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![], + }; + assert_eq!(a, b); + + let a = ContentDisposition::from_raw(&HeaderValue::from_static( + "unknown-disp-param", + )) + .unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Ext(String::from("unknown-disp-param")), + parameters: vec![], + }; + assert_eq!(a, b); + } + + #[test] + fn from_raw_with_mixed_case() { + let a = HeaderValue::from_str( + "InLInE; fIlenAME*=iso-8859-1''foo-%E4.html; filEName=\"foo-ä.html\"", + ) + .unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![ + DispositionParam::FilenameExt(ExtendedValue { + charset: Charset::Iso_8859_1, + language_tag: None, + value: b"foo-\xe4.html".to_vec(), + }), + DispositionParam::Filename("foo-ä.html".to_owned()), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn from_raw_with_unicode() { + /* RFC7578 Section 4.2: + Some commonly deployed systems use multipart/form-data with file names directly encoded + including octets outside the US-ASCII range. The encoding used for the file names is + typically UTF-8, although HTML forms will use the charset associated with the form. + + Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above. + (And now, only UTF-8 is handled by this implementation.) + */ + let a = + HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"") + .unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Name(String::from("upload")), + DispositionParam::Filename(String::from("文件.webp")), + ], + }; + assert_eq!(a, b); + + let a = + HeaderValue::from_str("form-data; name=upload; filename=\"余固知謇謇之為患兮,忍而不能舍也.pptx\"").unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Name(String::from("upload")), + DispositionParam::Filename(String::from( + "余固知謇謇之為患兮,忍而不能舍也.pptx", + )), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_escape() { + let a = HeaderValue::from_static( + "form-data; dummy=3; name=upload; filename=\"s\\amp\\\"le.png\"", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), + DispositionParam::Name("upload".to_owned()), + DispositionParam::Filename( + ['s', 'a', 'm', 'p', '\"', 'l', 'e', '.', 'p', 'n', 'g'] + .iter() + .collect(), + ), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_semicolon() { + let a = + HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\""); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![DispositionParam::Filename(String::from( + "A semicolon here;.pdf", + ))], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_uncessary_percent_decode() { + let a = HeaderValue::from_static( + "form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"", // Should not be decoded! + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Name("photo".to_owned()), + DispositionParam::Filename(String::from("%74%65%73%74%2e%70%6e%67")), + ], + }; + assert_eq!(a, b); + + let a = HeaderValue::from_static( + "form-data; name=photo; filename=\"%74%65%73%74.png\"", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Name("photo".to_owned()), + DispositionParam::Filename(String::from("%74%65%73%74.png")), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_param_value_missing() { + let a = HeaderValue::from_static("form-data; name=upload ; filename="); + assert!(ContentDisposition::from_raw(&a).is_err()); + + let a = HeaderValue::from_static("attachment; dummy=; filename=invoice.pdf"); + assert!(ContentDisposition::from_raw(&a).is_err()); + + let a = HeaderValue::from_static("inline; filename= "); + assert!(ContentDisposition::from_raw(&a).is_err()); + } + + #[test] + fn test_from_raw_param_name_missing() { + let a = HeaderValue::from_static("inline; =\"test.txt\""); + assert!(ContentDisposition::from_raw(&a).is_err()); + + let a = HeaderValue::from_static("inline; =diary.odt"); + assert!(ContentDisposition::from_raw(&a).is_err()); + + let a = HeaderValue::from_static("inline; ="); + assert!(ContentDisposition::from_raw(&a).is_err()); + } + + #[test] + fn test_display_extended() { + let as_string = + "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; + let a = HeaderValue::from_static(as_string); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let display_rendered = format!("{}", a); + assert_eq!(as_string, display_rendered); + + let a = HeaderValue::from_static("attachment; filename=colourful.csv"); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let display_rendered = format!("{}", a); + assert_eq!( + "attachment; filename=\"colourful.csv\"".to_owned(), + display_rendered + ); + } + + #[test] + fn test_display_quote() { + let as_string = "form-data; name=upload; filename=\"Quote\\\"here.png\""; + as_string + .find(['\\', '\"'].iter().collect::().as_str()) + .unwrap(); // ensure `\"` is there + let a = HeaderValue::from_static(as_string); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let display_rendered = format!("{}", a); + assert_eq!(as_string, display_rendered); + } + + #[test] + fn test_display_space_tab() { + let as_string = "form-data; name=upload; filename=\"Space here.png\""; + let a = HeaderValue::from_static(as_string); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let display_rendered = format!("{}", a); + assert_eq!(as_string, display_rendered); + + let a: ContentDisposition = ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![DispositionParam::Filename(String::from("Tab\there.png"))], + }; + let display_rendered = format!("{}", a); + assert_eq!("inline; filename=\"Tab\x09here.png\"", display_rendered); + } + + #[test] + fn test_display_control_characters() { + /* let a = "attachment; filename=\"carriage\rreturn.png\""; + let a = HeaderValue::from_static(a); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let display_rendered = format!("{}", a); + assert_eq!( + "attachment; filename=\"carriage\\\rreturn.png\"", + display_rendered + );*/ + // No way to create a HeaderValue containing a carriage return. + + let a: ContentDisposition = ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![DispositionParam::Filename(String::from("bell\x07.png"))], + }; + let display_rendered = format!("{}", a); + assert_eq!("inline; filename=\"bell\\\x07.png\"", display_rendered); + } + + #[test] + fn test_param_methods() { + let param = DispositionParam::Filename(String::from("sample.txt")); + assert!(param.is_filename()); + assert_eq!(param.as_filename().unwrap(), "sample.txt"); + + let param = DispositionParam::Unknown(String::from("foo"), String::from("bar")); + assert!(param.is_unknown("foo")); + assert_eq!(param.as_unknown("fOo"), Some("bar")); + } + + #[test] + fn test_disposition_methods() { + let cd = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), + DispositionParam::Name("upload".to_owned()), + DispositionParam::Filename("sample.png".to_owned()), + ], + }; + assert_eq!(cd.get_name(), Some("upload")); + assert_eq!(cd.get_unknown("dummy"), Some("3")); + assert_eq!(cd.get_filename(), Some("sample.png")); + assert_eq!(cd.get_unknown_ext("dummy"), None); + assert_eq!(cd.get_unknown("duMMy"), Some("3")); + } +} diff --git a/src/header/common/content_language.rs b/src/header/common/content_language.rs new file mode 100644 index 000000000..838981a39 --- /dev/null +++ b/src/header/common/content_language.rs @@ -0,0 +1,65 @@ +use crate::header::{QualityItem, CONTENT_LANGUAGE}; +use language_tags::LanguageTag; + +header! { + /// `Content-Language` header, defined in + /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) + /// + /// The `Content-Language` header field describes the natural language(s) + /// of the intended audience for the representation. Note that this + /// might not be equivalent to all the languages used within the + /// representation. + /// + /// # ABNF + /// + /// ```text + /// Content-Language = 1#language-tag + /// ``` + /// + /// # Example values + /// + /// * `da` + /// * `mi, en` + /// + /// # Examples + /// + /// ```rust + /// # extern crate actix_http; + /// # #[macro_use] extern crate language_tags; + /// use actix_http::Response; + /// # use actix_http::http::header::{ContentLanguage, qitem}; + /// # + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// ContentLanguage(vec![ + /// qitem(langtag!(en)), + /// ]) + /// ); + /// # } + /// ``` + /// + /// ```rust + /// # extern crate actix_http; + /// # #[macro_use] extern crate language_tags; + /// use actix_http::Response; + /// # use actix_http::http::header::{ContentLanguage, qitem}; + /// # + /// # fn main() { + /// + /// let mut builder = Response::Ok(); + /// builder.set( + /// ContentLanguage(vec![ + /// qitem(langtag!(da)), + /// qitem(langtag!(en;;;GB)), + /// ]) + /// ); + /// # } + /// ``` + (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+ + + test_content_language { + test_header!(test1, vec![b"da"]); + test_header!(test2, vec![b"mi, en"]); + } +} diff --git a/src/header/common/content_range.rs b/src/header/common/content_range.rs new file mode 100644 index 000000000..cc7f27548 --- /dev/null +++ b/src/header/common/content_range.rs @@ -0,0 +1,208 @@ +use std::fmt::{self, Display, Write}; +use std::str::FromStr; + +use crate::error::ParseError; +use crate::header::{ + HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer, CONTENT_RANGE, +}; + +header! { + /// `Content-Range` header, defined in + /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) + (ContentRange, CONTENT_RANGE) => [ContentRangeSpec] + + test_content_range { + test_header!(test_bytes, + vec![b"bytes 0-499/500"], + Some(ContentRange(ContentRangeSpec::Bytes { + range: Some((0, 499)), + instance_length: Some(500) + }))); + + test_header!(test_bytes_unknown_len, + vec![b"bytes 0-499/*"], + Some(ContentRange(ContentRangeSpec::Bytes { + range: Some((0, 499)), + instance_length: None + }))); + + test_header!(test_bytes_unknown_range, + vec![b"bytes */500"], + Some(ContentRange(ContentRangeSpec::Bytes { + range: None, + instance_length: Some(500) + }))); + + test_header!(test_unregistered, + vec![b"seconds 1-2"], + Some(ContentRange(ContentRangeSpec::Unregistered { + unit: "seconds".to_owned(), + resp: "1-2".to_owned() + }))); + + test_header!(test_no_len, + vec![b"bytes 0-499"], + None::); + + test_header!(test_only_unit, + vec![b"bytes"], + None::); + + test_header!(test_end_less_than_start, + vec![b"bytes 499-0/500"], + None::); + + test_header!(test_blank, + vec![b""], + None::); + + test_header!(test_bytes_many_spaces, + vec![b"bytes 1-2/500 3"], + None::); + + test_header!(test_bytes_many_slashes, + vec![b"bytes 1-2/500/600"], + None::); + + test_header!(test_bytes_many_dashes, + vec![b"bytes 1-2-3/500"], + None::); + + } +} + +/// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2) +/// +/// # ABNF +/// +/// ```text +/// Content-Range = byte-content-range +/// / other-content-range +/// +/// byte-content-range = bytes-unit SP +/// ( byte-range-resp / unsatisfied-range ) +/// +/// byte-range-resp = byte-range "/" ( complete-length / "*" ) +/// byte-range = first-byte-pos "-" last-byte-pos +/// unsatisfied-range = "*/" complete-length +/// +/// complete-length = 1*DIGIT +/// +/// other-content-range = other-range-unit SP other-range-resp +/// other-range-resp = *CHAR +/// ``` +#[derive(PartialEq, Clone, Debug)] +pub enum ContentRangeSpec { + /// Byte range + Bytes { + /// First and last bytes of the range, omitted if request could not be + /// satisfied + range: Option<(u64, u64)>, + + /// Total length of the instance, can be omitted if unknown + instance_length: Option, + }, + + /// Custom range, with unit not registered at IANA + Unregistered { + /// other-range-unit + unit: String, + + /// other-range-resp + resp: String, + }, +} + +fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> { + let mut iter = s.splitn(2, separator); + match (iter.next(), iter.next()) { + (Some(a), Some(b)) => Some((a, b)), + _ => None, + } +} + +impl FromStr for ContentRangeSpec { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + let res = match split_in_two(s, ' ') { + Some(("bytes", resp)) => { + let (range, instance_length) = + split_in_two(resp, '/').ok_or(ParseError::Header)?; + + let instance_length = if instance_length == "*" { + None + } else { + Some(instance_length.parse().map_err(|_| ParseError::Header)?) + }; + + let range = if range == "*" { + None + } else { + let (first_byte, last_byte) = + split_in_two(range, '-').ok_or(ParseError::Header)?; + let first_byte = + first_byte.parse().map_err(|_| ParseError::Header)?; + let last_byte = last_byte.parse().map_err(|_| ParseError::Header)?; + if last_byte < first_byte { + return Err(ParseError::Header); + } + Some((first_byte, last_byte)) + }; + + ContentRangeSpec::Bytes { + range, + instance_length, + } + } + Some((unit, resp)) => ContentRangeSpec::Unregistered { + unit: unit.to_owned(), + resp: resp.to_owned(), + }, + _ => return Err(ParseError::Header), + }; + Ok(res) + } +} + +impl Display for ContentRangeSpec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ContentRangeSpec::Bytes { + range, + instance_length, + } => { + f.write_str("bytes ")?; + match range { + Some((first_byte, last_byte)) => { + write!(f, "{}-{}", first_byte, last_byte)?; + } + None => { + f.write_str("*")?; + } + }; + f.write_str("/")?; + if let Some(v) = instance_length { + write!(f, "{}", v) + } else { + f.write_str("*") + } + } + ContentRangeSpec::Unregistered { ref unit, ref resp } => { + f.write_str(unit)?; + f.write_str(" ")?; + f.write_str(resp) + } + } + } +} + +impl IntoHeaderValue for ContentRangeSpec { + type Error = InvalidHeaderValueBytes; + + fn try_into(self) -> Result { + let mut writer = Writer::new(); + let _ = write!(&mut writer, "{}", self); + HeaderValue::from_shared(writer.take()) + } +} diff --git a/src/header/common/content_type.rs b/src/header/common/content_type.rs new file mode 100644 index 000000000..a0baa5637 --- /dev/null +++ b/src/header/common/content_type.rs @@ -0,0 +1,122 @@ +use crate::header::CONTENT_TYPE; +use mime::Mime; + +header! { + /// `Content-Type` header, defined in + /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) + /// + /// The `Content-Type` header field indicates the media type of the + /// associated representation: either the representation enclosed in the + /// message payload or the selected representation, as determined by the + /// message semantics. The indicated media type defines both the data + /// format and how that data is intended to be processed by a recipient, + /// within the scope of the received message semantics, after any content + /// codings indicated by Content-Encoding are decoded. + /// + /// Although the `mime` crate allows the mime options to be any slice, this crate + /// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If + /// this is an issue, it's possible to implement `Header` on a custom struct. + /// + /// # ABNF + /// + /// ```text + /// Content-Type = media-type + /// ``` + /// + /// # Example values + /// + /// * `text/html; charset=utf-8` + /// * `application/json` + /// + /// # Examples + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::ContentType; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// ContentType::json() + /// ); + /// # } + /// ``` + /// + /// ```rust + /// # extern crate mime; + /// # extern crate actix_http; + /// use mime::TEXT_HTML; + /// use actix_http::Response; + /// use actix_http::http::header::ContentType; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// ContentType(TEXT_HTML) + /// ); + /// # } + /// ``` + (ContentType, CONTENT_TYPE) => [Mime] + + test_content_type { + test_header!( + test1, + vec![b"text/html"], + Some(HeaderField(mime::TEXT_HTML))); + } +} + +impl ContentType { + /// A constructor to easily create a `Content-Type: application/json` + /// header. + #[inline] + pub fn json() -> ContentType { + ContentType(mime::APPLICATION_JSON) + } + + /// A constructor to easily create a `Content-Type: text/plain; + /// charset=utf-8` header. + #[inline] + pub fn plaintext() -> ContentType { + ContentType(mime::TEXT_PLAIN_UTF_8) + } + + /// A constructor to easily create a `Content-Type: text/html` header. + #[inline] + pub fn html() -> ContentType { + ContentType(mime::TEXT_HTML) + } + + /// A constructor to easily create a `Content-Type: text/xml` header. + #[inline] + pub fn xml() -> ContentType { + ContentType(mime::TEXT_XML) + } + + /// A constructor to easily create a `Content-Type: + /// application/www-form-url-encoded` header. + #[inline] + pub fn form_url_encoded() -> ContentType { + ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) + } + /// A constructor to easily create a `Content-Type: image/jpeg` header. + #[inline] + pub fn jpeg() -> ContentType { + ContentType(mime::IMAGE_JPEG) + } + + /// A constructor to easily create a `Content-Type: image/png` header. + #[inline] + pub fn png() -> ContentType { + ContentType(mime::IMAGE_PNG) + } + + /// A constructor to easily create a `Content-Type: + /// application/octet-stream` header. + #[inline] + pub fn octet_stream() -> ContentType { + ContentType(mime::APPLICATION_OCTET_STREAM) + } +} + +impl Eq for ContentType {} diff --git a/src/header/common/date.rs b/src/header/common/date.rs new file mode 100644 index 000000000..784100e8d --- /dev/null +++ b/src/header/common/date.rs @@ -0,0 +1,42 @@ +use crate::header::{HttpDate, DATE}; +use std::time::SystemTime; + +header! { + /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) + /// + /// The `Date` header field represents the date and time at which the + /// message was originated. + /// + /// # ABNF + /// + /// ```text + /// Date = HTTP-date + /// ``` + /// + /// # Example values + /// + /// * `Tue, 15 Nov 1994 08:12:31 GMT` + /// + /// # Example + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::Date; + /// use std::time::SystemTime; + /// + /// let mut builder = Response::Ok(); + /// builder.set(Date(SystemTime::now().into())); + /// ``` + (Date, DATE) => [HttpDate] + + test_date { + test_header!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); + } +} + +impl Date { + /// Create a date instance set to the current system time + pub fn now() -> Date { + Date(SystemTime::now().into()) + } +} diff --git a/src/header/common/etag.rs b/src/header/common/etag.rs new file mode 100644 index 000000000..325b91cbf --- /dev/null +++ b/src/header/common/etag.rs @@ -0,0 +1,96 @@ +use crate::header::{EntityTag, ETAG}; + +header! { + /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) + /// + /// The `ETag` header field in a response provides the current entity-tag + /// for the selected representation, as determined at the conclusion of + /// handling the request. An entity-tag is an opaque validator for + /// differentiating between multiple representations of the same + /// resource, regardless of whether those multiple representations are + /// due to resource state changes over time, content negotiation + /// resulting in multiple representations being valid at the same time, + /// or both. An entity-tag consists of an opaque quoted string, possibly + /// prefixed by a weakness indicator. + /// + /// # ABNF + /// + /// ```text + /// ETag = entity-tag + /// ``` + /// + /// # Example values + /// + /// * `"xyzzy"` + /// * `W/"xyzzy"` + /// * `""` + /// + /// # Examples + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::{ETag, EntityTag}; + /// + /// let mut builder = Response::Ok(); + /// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned()))); + /// ``` + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::{ETag, EntityTag}; + /// + /// let mut builder = Response::Ok(); + /// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned()))); + /// ``` + (ETag, ETAG) => [EntityTag] + + test_etag { + // From the RFC + test_header!(test1, + vec![b"\"xyzzy\""], + Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); + test_header!(test2, + vec![b"W/\"xyzzy\""], + Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); + test_header!(test3, + vec![b"\"\""], + Some(ETag(EntityTag::new(false, "".to_owned())))); + // Own tests + test_header!(test4, + vec![b"\"foobar\""], + Some(ETag(EntityTag::new(false, "foobar".to_owned())))); + test_header!(test5, + vec![b"\"\""], + Some(ETag(EntityTag::new(false, "".to_owned())))); + test_header!(test6, + vec![b"W/\"weak-etag\""], + Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); + test_header!(test7, + vec![b"W/\"\x65\x62\""], + Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); + test_header!(test8, + vec![b"W/\"\""], + Some(ETag(EntityTag::new(true, "".to_owned())))); + test_header!(test9, + vec![b"no-dquotes"], + None::); + test_header!(test10, + vec![b"w/\"the-first-w-is-case-sensitive\""], + None::); + test_header!(test11, + vec![b""], + None::); + test_header!(test12, + vec![b"\"unmatched-dquotes1"], + None::); + test_header!(test13, + vec![b"unmatched-dquotes2\""], + None::); + test_header!(test14, + vec![b"matched-\"dquotes\""], + None::); + test_header!(test15, + vec![b"\""], + None::); + } +} diff --git a/src/header/common/expires.rs b/src/header/common/expires.rs new file mode 100644 index 000000000..3b9a7873d --- /dev/null +++ b/src/header/common/expires.rs @@ -0,0 +1,39 @@ +use crate::header::{HttpDate, EXPIRES}; + +header! { + /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) + /// + /// The `Expires` header field gives the date/time after which the + /// response is considered stale. + /// + /// The presence of an Expires field does not imply that the original + /// resource will change or cease to exist at, before, or after that + /// time. + /// + /// # ABNF + /// + /// ```text + /// Expires = HTTP-date + /// ``` + /// + /// # Example values + /// * `Thu, 01 Dec 1994 16:00:00 GMT` + /// + /// # Example + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::Expires; + /// use std::time::{SystemTime, Duration}; + /// + /// let mut builder = Response::Ok(); + /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); + /// builder.set(Expires(expiration.into())); + /// ``` + (Expires, EXPIRES) => [HttpDate] + + test_expires { + // Test case from RFC + test_header!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); + } +} diff --git a/src/header/common/if_match.rs b/src/header/common/if_match.rs new file mode 100644 index 000000000..7e0e9a7e0 --- /dev/null +++ b/src/header/common/if_match.rs @@ -0,0 +1,70 @@ +use crate::header::{EntityTag, IF_MATCH}; + +header! { + /// `If-Match` header, defined in + /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) + /// + /// The `If-Match` header field makes the request method conditional on + /// the recipient origin server either having at least one current + /// representation of the target resource, when the field-value is "*", + /// or having a current representation of the target resource that has an + /// entity-tag matching a member of the list of entity-tags provided in + /// the field-value. + /// + /// An origin server MUST use the strong comparison function when + /// comparing entity-tags for `If-Match`, since the client + /// intends this precondition to prevent the method from being applied if + /// there have been any changes to the representation data. + /// + /// # ABNF + /// + /// ```text + /// If-Match = "*" / 1#entity-tag + /// ``` + /// + /// # Example values + /// + /// * `"xyzzy"` + /// * "xyzzy", "r2d2xxxx", "c3piozzzz" + /// + /// # Examples + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::IfMatch; + /// + /// let mut builder = Response::Ok(); + /// builder.set(IfMatch::Any); + /// ``` + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::{IfMatch, EntityTag}; + /// + /// let mut builder = Response::Ok(); + /// builder.set( + /// IfMatch::Items(vec![ + /// EntityTag::new(false, "xyzzy".to_owned()), + /// EntityTag::new(false, "foobar".to_owned()), + /// EntityTag::new(false, "bazquux".to_owned()), + /// ]) + /// ); + /// ``` + (IfMatch, IF_MATCH) => {Any / (EntityTag)+} + + test_if_match { + test_header!( + test1, + vec![b"\"xyzzy\""], + Some(HeaderField::Items( + vec![EntityTag::new(false, "xyzzy".to_owned())]))); + test_header!( + test2, + vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""], + Some(HeaderField::Items( + vec![EntityTag::new(false, "xyzzy".to_owned()), + EntityTag::new(false, "r2d2xxxx".to_owned()), + EntityTag::new(false, "c3piozzzz".to_owned())]))); + test_header!(test3, vec![b"*"], Some(IfMatch::Any)); + } +} diff --git a/src/header/common/if_modified_since.rs b/src/header/common/if_modified_since.rs new file mode 100644 index 000000000..39aca595d --- /dev/null +++ b/src/header/common/if_modified_since.rs @@ -0,0 +1,39 @@ +use crate::header::{HttpDate, IF_MODIFIED_SINCE}; + +header! { + /// `If-Modified-Since` header, defined in + /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) + /// + /// The `If-Modified-Since` header field makes a GET or HEAD request + /// method conditional on the selected representation's modification date + /// being more recent than the date provided in the field-value. + /// Transfer of the selected representation's data is avoided if that + /// data has not changed. + /// + /// # ABNF + /// + /// ```text + /// If-Unmodified-Since = HTTP-date + /// ``` + /// + /// # Example values + /// * `Sat, 29 Oct 1994 19:43:31 GMT` + /// + /// # Example + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::IfModifiedSince; + /// use std::time::{SystemTime, Duration}; + /// + /// let mut builder = Response::Ok(); + /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); + /// builder.set(IfModifiedSince(modified.into())); + /// ``` + (IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate] + + test_if_modified_since { + // Test case from RFC + test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + } +} diff --git a/src/header/common/if_none_match.rs b/src/header/common/if_none_match.rs new file mode 100644 index 000000000..7f6ccb137 --- /dev/null +++ b/src/header/common/if_none_match.rs @@ -0,0 +1,92 @@ +use crate::header::{EntityTag, IF_NONE_MATCH}; + +header! { + /// `If-None-Match` header, defined in + /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) + /// + /// The `If-None-Match` header field makes the request method conditional + /// on a recipient cache or origin server either not having any current + /// representation of the target resource, when the field-value is "*", + /// or having a selected representation with an entity-tag that does not + /// match any of those listed in the field-value. + /// + /// A recipient MUST use the weak comparison function when comparing + /// entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags + /// can be used for cache validation even if there have been changes to + /// the representation data. + /// + /// # ABNF + /// + /// ```text + /// If-None-Match = "*" / 1#entity-tag + /// ``` + /// + /// # Example values + /// + /// * `"xyzzy"` + /// * `W/"xyzzy"` + /// * `"xyzzy", "r2d2xxxx", "c3piozzzz"` + /// * `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"` + /// * `*` + /// + /// # Examples + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::IfNoneMatch; + /// + /// let mut builder = Response::Ok(); + /// builder.set(IfNoneMatch::Any); + /// ``` + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::{IfNoneMatch, EntityTag}; + /// + /// let mut builder = Response::Ok(); + /// builder.set( + /// IfNoneMatch::Items(vec![ + /// EntityTag::new(false, "xyzzy".to_owned()), + /// EntityTag::new(false, "foobar".to_owned()), + /// EntityTag::new(false, "bazquux".to_owned()), + /// ]) + /// ); + /// ``` + (IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+} + + test_if_none_match { + test_header!(test1, vec![b"\"xyzzy\""]); + test_header!(test2, vec![b"W/\"xyzzy\""]); + test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); + test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); + test_header!(test5, vec![b"*"]); + } +} + +#[cfg(test)] +mod tests { + use super::IfNoneMatch; + use crate::header::{EntityTag, Header, IF_NONE_MATCH}; + use crate::test::TestRequest; + + #[test] + fn test_if_none_match() { + let mut if_none_match: Result; + + let req = TestRequest::with_header(IF_NONE_MATCH, "*").finish(); + if_none_match = Header::parse(&req); + assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any)); + + let req = + TestRequest::with_header(IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]) + .finish(); + + if_none_match = Header::parse(&req); + let mut entities: Vec = Vec::new(); + let foobar_etag = EntityTag::new(false, "foobar".to_owned()); + let weak_etag = EntityTag::new(true, "weak-etag".to_owned()); + entities.push(foobar_etag); + entities.push(weak_etag); + assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Items(entities))); + } +} diff --git a/src/header/common/if_range.rs b/src/header/common/if_range.rs new file mode 100644 index 000000000..2140ccbb3 --- /dev/null +++ b/src/header/common/if_range.rs @@ -0,0 +1,116 @@ +use std::fmt::{self, Display, Write}; + +use crate::error::ParseError; +use crate::header::{ + self, from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, + IntoHeaderValue, InvalidHeaderValueBytes, Writer, +}; +use crate::httpmessage::HttpMessage; + +/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) +/// +/// If a client has a partial copy of a representation and wishes to have +/// an up-to-date copy of the entire representation, it could use the +/// Range header field with a conditional GET (using either or both of +/// If-Unmodified-Since and If-Match.) However, if the precondition +/// fails because the representation has been modified, the client would +/// then have to make a second request to obtain the entire current +/// representation. +/// +/// The `If-Range` header field allows a client to \"short-circuit\" the +/// second request. Informally, its meaning is as follows: if the +/// representation is unchanged, send me the part(s) that I am requesting +/// in Range; otherwise, send me the entire representation. +/// +/// # ABNF +/// +/// ```text +/// If-Range = entity-tag / HTTP-date +/// ``` +/// +/// # Example values +/// +/// * `Sat, 29 Oct 1994 19:43:31 GMT` +/// * `\"xyzzy\"` +/// +/// # Examples +/// +/// ```rust +/// use actix_http::Response; +/// use actix_http::http::header::{EntityTag, IfRange}; +/// +/// let mut builder = Response::Ok(); +/// builder.set(IfRange::EntityTag(EntityTag::new( +/// false, +/// "xyzzy".to_owned(), +/// ))); +/// ``` +/// +/// ```rust +/// use actix_http::Response; +/// use actix_http::http::header::IfRange; +/// use std::time::{Duration, SystemTime}; +/// +/// let mut builder = Response::Ok(); +/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); +/// builder.set(IfRange::Date(fetched.into())); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub enum IfRange { + /// The entity-tag the client has of the resource + EntityTag(EntityTag), + /// The date when the client retrieved the resource + Date(HttpDate), +} + +impl Header for IfRange { + fn name() -> HeaderName { + header::IF_RANGE + } + #[inline] + fn parse(msg: &T) -> Result + where + T: HttpMessage, + { + let etag: Result = + from_one_raw_str(msg.headers().get(header::IF_RANGE)); + if let Ok(etag) = etag { + return Ok(IfRange::EntityTag(etag)); + } + let date: Result = + from_one_raw_str(msg.headers().get(header::IF_RANGE)); + if let Ok(date) = date { + return Ok(IfRange::Date(date)); + } + Err(ParseError::Header) + } +} + +impl Display for IfRange { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + IfRange::EntityTag(ref x) => Display::fmt(x, f), + IfRange::Date(ref x) => Display::fmt(x, f), + } + } +} + +impl IntoHeaderValue for IfRange { + type Error = InvalidHeaderValueBytes; + + fn try_into(self) -> Result { + let mut writer = Writer::new(); + let _ = write!(&mut writer, "{}", self); + HeaderValue::from_shared(writer.take()) + } +} + +#[cfg(test)] +mod test_if_range { + use super::IfRange as HeaderField; + use crate::header::*; + use std::str; + test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + test_header!(test2, vec![b"\"xyzzy\""]); + test_header!(test3, vec![b"this-is-invalid"], None::); +} diff --git a/src/header/common/if_unmodified_since.rs b/src/header/common/if_unmodified_since.rs new file mode 100644 index 000000000..d6c099e64 --- /dev/null +++ b/src/header/common/if_unmodified_since.rs @@ -0,0 +1,40 @@ +use crate::header::{HttpDate, IF_UNMODIFIED_SINCE}; + +header! { + /// `If-Unmodified-Since` header, defined in + /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) + /// + /// The `If-Unmodified-Since` header field makes the request method + /// conditional on the selected representation's last modification date + /// being earlier than or equal to the date provided in the field-value. + /// This field accomplishes the same purpose as If-Match for cases where + /// the user agent does not have an entity-tag for the representation. + /// + /// # ABNF + /// + /// ```text + /// If-Unmodified-Since = HTTP-date + /// ``` + /// + /// # Example values + /// + /// * `Sat, 29 Oct 1994 19:43:31 GMT` + /// + /// # Example + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::IfUnmodifiedSince; + /// use std::time::{SystemTime, Duration}; + /// + /// let mut builder = Response::Ok(); + /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); + /// builder.set(IfUnmodifiedSince(modified.into())); + /// ``` + (IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate] + + test_if_unmodified_since { + // Test case from RFC + test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + } +} diff --git a/src/header/common/last_modified.rs b/src/header/common/last_modified.rs new file mode 100644 index 000000000..cc888ccb0 --- /dev/null +++ b/src/header/common/last_modified.rs @@ -0,0 +1,38 @@ +use crate::header::{HttpDate, LAST_MODIFIED}; + +header! { + /// `Last-Modified` header, defined in + /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) + /// + /// The `Last-Modified` header field in a response provides a timestamp + /// indicating the date and time at which the origin server believes the + /// selected representation was last modified, as determined at the + /// conclusion of handling the request. + /// + /// # ABNF + /// + /// ```text + /// Expires = HTTP-date + /// ``` + /// + /// # Example values + /// + /// * `Sat, 29 Oct 1994 19:43:31 GMT` + /// + /// # Example + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::LastModified; + /// use std::time::{SystemTime, Duration}; + /// + /// let mut builder = Response::Ok(); + /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); + /// builder.set(LastModified(modified.into())); + /// ``` + (LastModified, LAST_MODIFIED) => [HttpDate] + + test_last_modified { + // Test case from RFC + test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);} +} diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs new file mode 100644 index 000000000..adc7484a9 --- /dev/null +++ b/src/header/common/mod.rs @@ -0,0 +1,352 @@ +//! A Collection of Header implementations for common HTTP Headers. +//! +//! ## Mime +//! +//! Several header fields use MIME values for their contents. Keeping with the +//! strongly-typed theme, the [mime](https://docs.rs/mime) crate +//! is used, such as `ContentType(pub Mime)`. +#![cfg_attr(rustfmt, rustfmt_skip)] + +pub use self::accept_charset::AcceptCharset; +//pub use self::accept_encoding::AcceptEncoding; +pub use self::accept_language::AcceptLanguage; +pub use self::accept::Accept; +pub use self::allow::Allow; +pub use self::cache_control::{CacheControl, CacheDirective}; +pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam}; +pub use self::content_language::ContentLanguage; +pub use self::content_range::{ContentRange, ContentRangeSpec}; +pub use self::content_type::ContentType; +pub use self::date::Date; +pub use self::etag::ETag; +pub use self::expires::Expires; +pub use self::if_match::IfMatch; +pub use self::if_modified_since::IfModifiedSince; +pub use self::if_none_match::IfNoneMatch; +pub use self::if_range::IfRange; +pub use self::if_unmodified_since::IfUnmodifiedSince; +pub use self::last_modified::LastModified; +//pub use self::range::{Range, ByteRangeSpec}; + +#[doc(hidden)] +#[macro_export] +macro_rules! __hyper__deref { + ($from:ty => $to:ty) => { + impl ::std::ops::Deref for $from { + type Target = $to; + + #[inline] + fn deref(&self) -> &$to { + &self.0 + } + } + + impl ::std::ops::DerefMut for $from { + #[inline] + fn deref_mut(&mut self) -> &mut $to { + &mut self.0 + } + } + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __hyper__tm { + ($id:ident, $tm:ident{$($tf:item)*}) => { + #[allow(unused_imports)] + #[cfg(test)] + mod $tm{ + use std::str; + use http::Method; + use mime::*; + use $crate::header::*; + use super::$id as HeaderField; + $($tf)* + } + + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! test_header { + ($id:ident, $raw:expr) => { + #[test] + fn $id() { + use $crate::test; + use super::*; + + let raw = $raw; + let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); + let mut req = test::TestRequest::default(); + for item in a { + req = req.header(HeaderField::name(), item); + } + let req = req.finish(); + let value = HeaderField::parse(&req); + let result = format!("{}", value.unwrap()); + let expected = String::from_utf8(raw[0].to_vec()).unwrap(); + let result_cmp: Vec = result + .to_ascii_lowercase() + .split(' ') + .map(|x| x.to_owned()) + .collect(); + let expected_cmp: Vec = expected + .to_ascii_lowercase() + .split(' ') + .map(|x| x.to_owned()) + .collect(); + assert_eq!(result_cmp.concat(), expected_cmp.concat()); + } + }; + ($id:ident, $raw:expr, $typed:expr) => { + #[test] + fn $id() { + use $crate::test; + + let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); + let mut req = test::TestRequest::default(); + for item in a { + req = req.header(HeaderField::name(), item); + } + let req = req.finish(); + let val = HeaderField::parse(&req); + let typed: Option = $typed; + // Test parsing + assert_eq!(val.ok(), typed); + // Test formatting + if typed.is_some() { + let raw = &($raw)[..]; + let mut iter = raw.iter().map(|b|str::from_utf8(&b[..]).unwrap()); + let mut joined = String::new(); + joined.push_str(iter.next().unwrap()); + for s in iter { + joined.push_str(", "); + joined.push_str(s); + } + assert_eq!(format!("{}", typed.unwrap()), joined); + } + } + } +} + +#[macro_export] +macro_rules! header { + // $a:meta: Attributes associated with the header item (usually docs) + // $id:ident: Identifier of the header + // $n:expr: Lowercase name of the header + // $nn:expr: Nice name of the header + + // List header, zero or more items + ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)*) => { + $(#[$a])* + #[derive(Clone, Debug, PartialEq)] + pub struct $id(pub Vec<$item>); + __hyper__deref!($id => Vec<$item>); + impl $crate::http::header::Header for $id { + #[inline] + fn name() -> $crate::http::header::HeaderName { + $name + } + #[inline] + fn parse(msg: &T) -> Result + where T: $crate::HttpMessage + { + $crate::http::header::from_comma_delimited( + msg.headers().get_all(Self::name())).map($id) + } + } + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> ::std::fmt::Result { + $crate::http::header::fmt_comma_delimited(f, &self.0[..]) + } + } + impl $crate::http::header::IntoHeaderValue for $id { + type Error = $crate::http::header::InvalidHeaderValueBytes; + + fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + use std::fmt::Write; + let mut writer = $crate::http::header::Writer::new(); + let _ = write!(&mut writer, "{}", self); + $crate::http::header::HeaderValue::from_shared(writer.take()) + } + } + }; + // List header, one or more items + ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)+) => { + $(#[$a])* + #[derive(Clone, Debug, PartialEq)] + pub struct $id(pub Vec<$item>); + __hyper__deref!($id => Vec<$item>); + impl $crate::http::header::Header for $id { + #[inline] + fn name() -> $crate::http::header::HeaderName { + $name + } + #[inline] + fn parse(msg: &T) -> Result + where T: $crate::HttpMessage + { + $crate::http::header::from_comma_delimited( + msg.headers().get_all(Self::name())).map($id) + } + } + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + $crate::http::header::fmt_comma_delimited(f, &self.0[..]) + } + } + impl $crate::http::header::IntoHeaderValue for $id { + type Error = $crate::http::header::InvalidHeaderValueBytes; + + fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + use std::fmt::Write; + let mut writer = $crate::http::header::Writer::new(); + let _ = write!(&mut writer, "{}", self); + $crate::http::header::HeaderValue::from_shared(writer.take()) + } + } + }; + // Single value header + ($(#[$a:meta])*($id:ident, $name:expr) => [$value:ty]) => { + $(#[$a])* + #[derive(Clone, Debug, PartialEq)] + pub struct $id(pub $value); + __hyper__deref!($id => $value); + impl $crate::http::header::Header for $id { + #[inline] + fn name() -> $crate::http::header::HeaderName { + $name + } + #[inline] + fn parse(msg: &T) -> Result + where T: $crate::HttpMessage + { + $crate::http::header::from_one_raw_str( + msg.headers().get(Self::name())).map($id) + } + } + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } + } + impl $crate::http::header::IntoHeaderValue for $id { + type Error = $crate::http::header::InvalidHeaderValueBytes; + + fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + self.0.try_into() + } + } + }; + // List header, one or more items with "*" option + ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => { + $(#[$a])* + #[derive(Clone, Debug, PartialEq)] + pub enum $id { + /// Any value is a match + Any, + /// Only the listed items are a match + Items(Vec<$item>), + } + impl $crate::http::header::Header for $id { + #[inline] + fn name() -> $crate::http::header::HeaderName { + $name + } + #[inline] + fn parse(msg: &T) -> Result + where T: $crate::HttpMessage + { + let any = msg.headers().get(Self::name()).and_then(|hdr| { + hdr.to_str().ok().and_then(|hdr| Some(hdr.trim() == "*"))}); + + if let Some(true) = any { + Ok($id::Any) + } else { + Ok($id::Items( + $crate::http::header::from_comma_delimited( + msg.headers().get_all(Self::name()))?)) + } + } + } + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + $id::Any => f.write_str("*"), + $id::Items(ref fields) => $crate::http::header::fmt_comma_delimited( + f, &fields[..]) + } + } + } + impl $crate::http::header::IntoHeaderValue for $id { + type Error = $crate::http::header::InvalidHeaderValueBytes; + + fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + use std::fmt::Write; + let mut writer = $crate::http::header::Writer::new(); + let _ = write!(&mut writer, "{}", self); + $crate::http::header::HeaderValue::from_shared(writer.take()) + } + } + }; + + // optional test module + ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => { + header! { + $(#[$a])* + ($id, $name) => ($item)* + } + + __hyper__tm! { $id, $tm { $($tf)* }} + }; + ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => { + header! { + $(#[$a])* + ($id, $n) => ($item)+ + } + + __hyper__tm! { $id, $tm { $($tf)* }} + }; + ($(#[$a:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => { + header! { + $(#[$a])* ($id, $name) => [$item] + } + + __hyper__tm! { $id, $tm { $($tf)* }} + }; + ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => { + header! { + $(#[$a])* + ($id, $name) => {Any / ($item)+} + } + + __hyper__tm! { $id, $tm { $($tf)* }} + }; +} + + +mod accept_charset; +//mod accept_encoding; +mod accept_language; +mod accept; +mod allow; +mod cache_control; +mod content_disposition; +mod content_language; +mod content_range; +mod content_type; +mod date; +mod etag; +mod expires; +mod if_match; +mod if_modified_since; +mod if_none_match; +mod if_range; +mod if_unmodified_since; +mod last_modified; diff --git a/src/header/common/range.rs b/src/header/common/range.rs new file mode 100644 index 000000000..71718fc7a --- /dev/null +++ b/src/header/common/range.rs @@ -0,0 +1,434 @@ +use std::fmt::{self, Display}; +use std::str::FromStr; + +use header::parsing::from_one_raw_str; +use header::{Header, Raw}; + +/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1) +/// +/// The "Range" header field on a GET request modifies the method +/// semantics to request transfer of only one or more subranges of the +/// selected representation data, rather than the entire selected +/// representation data. +/// +/// # ABNF +/// +/// ```text +/// Range = byte-ranges-specifier / other-ranges-specifier +/// other-ranges-specifier = other-range-unit "=" other-range-set +/// other-range-set = 1*VCHAR +/// +/// bytes-unit = "bytes" +/// +/// byte-ranges-specifier = bytes-unit "=" byte-range-set +/// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec) +/// byte-range-spec = first-byte-pos "-" [last-byte-pos] +/// first-byte-pos = 1*DIGIT +/// last-byte-pos = 1*DIGIT +/// ``` +/// +/// # Example values +/// +/// * `bytes=1000-` +/// * `bytes=-2000` +/// * `bytes=0-1,30-40` +/// * `bytes=0-10,20-90,-100` +/// * `custom_unit=0-123` +/// * `custom_unit=xxx-yyy` +/// +/// # Examples +/// +/// ``` +/// use hyper::header::{Headers, Range, ByteRangeSpec}; +/// +/// let mut headers = Headers::new(); +/// headers.set(Range::Bytes( +/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] +/// )); +/// +/// headers.clear(); +/// headers.set(Range::Unregistered("letters".to_owned(), "a-f".to_owned())); +/// ``` +/// +/// ``` +/// use hyper::header::{Headers, Range}; +/// +/// let mut headers = Headers::new(); +/// headers.set(Range::bytes(1, 100)); +/// +/// headers.clear(); +/// headers.set(Range::bytes_multi(vec![(1, 100), (200, 300)])); +/// ``` +#[derive(PartialEq, Clone, Debug)] +pub enum Range { + /// Byte range + Bytes(Vec), + /// Custom range, with unit not registered at IANA + /// (`other-range-unit`: String , `other-range-set`: String) + Unregistered(String, String), +} + +/// Each `Range::Bytes` header can contain one or more `ByteRangeSpecs`. +/// Each `ByteRangeSpec` defines a range of bytes to fetch +#[derive(PartialEq, Clone, Debug)] +pub enum ByteRangeSpec { + /// Get all bytes between x and y ("x-y") + FromTo(u64, u64), + /// Get all bytes starting from x ("x-") + AllFrom(u64), + /// Get last x bytes ("-x") + Last(u64), +} + +impl ByteRangeSpec { + /// Given the full length of the entity, attempt to normalize the byte range + /// into an satisfiable end-inclusive (from, to) range. + /// + /// The resulting range is guaranteed to be a satisfiable range within the + /// bounds of `0 <= from <= to < full_length`. + /// + /// If the byte range is deemed unsatisfiable, `None` is returned. + /// An unsatisfiable range is generally cause for a server to either reject + /// the client request with a `416 Range Not Satisfiable` status code, or to + /// simply ignore the range header and serve the full entity using a `200 + /// OK` status code. + /// + /// This function closely follows [RFC 7233][1] section 2.1. + /// As such, it considers ranges to be satisfiable if they meet the + /// following conditions: + /// + /// > If a valid byte-range-set includes at least one byte-range-spec with + /// a first-byte-pos that is less than the current length of the + /// representation, or at least one suffix-byte-range-spec with a + /// non-zero suffix-length, then the byte-range-set is satisfiable. + /// Otherwise, the byte-range-set is unsatisfiable. + /// + /// The function also computes remainder ranges based on the RFC: + /// + /// > If the last-byte-pos value is + /// absent, or if the value is greater than or equal to the current + /// length of the representation data, the byte range is interpreted as + /// the remainder of the representation (i.e., the server replaces the + /// value of last-byte-pos with a value that is one less than the current + /// length of the selected representation). + /// + /// [1]: https://tools.ietf.org/html/rfc7233 + pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> { + // If the full length is zero, there is no satisfiable end-inclusive range. + if full_length == 0 { + return None; + } + match self { + &ByteRangeSpec::FromTo(from, to) => { + if from < full_length && from <= to { + Some((from, ::std::cmp::min(to, full_length - 1))) + } else { + None + } + } + &ByteRangeSpec::AllFrom(from) => { + if from < full_length { + Some((from, full_length - 1)) + } else { + None + } + } + &ByteRangeSpec::Last(last) => { + if last > 0 { + // From the RFC: If the selected representation is shorter + // than the specified suffix-length, + // the entire representation is used. + if last > full_length { + Some((0, full_length - 1)) + } else { + Some((full_length - last, full_length - 1)) + } + } else { + None + } + } + } + } +} + +impl Range { + /// Get the most common byte range header ("bytes=from-to") + pub fn bytes(from: u64, to: u64) -> Range { + Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)]) + } + + /// Get byte range header with multiple subranges + /// ("bytes=from1-to1,from2-to2,fromX-toX") + pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range { + Range::Bytes( + ranges + .iter() + .map(|r| ByteRangeSpec::FromTo(r.0, r.1)) + .collect(), + ) + } +} + +impl fmt::Display for ByteRangeSpec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to), + ByteRangeSpec::Last(pos) => write!(f, "-{}", pos), + ByteRangeSpec::AllFrom(pos) => write!(f, "{}-", pos), + } + } +} + +impl fmt::Display for Range { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Range::Bytes(ref ranges) => { + try!(write!(f, "bytes=")); + + for (i, range) in ranges.iter().enumerate() { + if i != 0 { + try!(f.write_str(",")); + } + try!(Display::fmt(range, f)); + } + Ok(()) + } + Range::Unregistered(ref unit, ref range_str) => { + write!(f, "{}={}", unit, range_str) + } + } + } +} + +impl FromStr for Range { + type Err = ::Error; + + fn from_str(s: &str) -> ::Result { + let mut iter = s.splitn(2, '='); + + match (iter.next(), iter.next()) { + (Some("bytes"), Some(ranges)) => { + let ranges = from_comma_delimited(ranges); + if ranges.is_empty() { + return Err(::Error::Header); + } + Ok(Range::Bytes(ranges)) + } + (Some(unit), Some(range_str)) if unit != "" && range_str != "" => Ok( + Range::Unregistered(unit.to_owned(), range_str.to_owned()), + ), + _ => Err(::Error::Header), + } + } +} + +impl FromStr for ByteRangeSpec { + type Err = ::Error; + + fn from_str(s: &str) -> ::Result { + let mut parts = s.splitn(2, '-'); + + match (parts.next(), parts.next()) { + (Some(""), Some(end)) => end.parse() + .or(Err(::Error::Header)) + .map(ByteRangeSpec::Last), + (Some(start), Some("")) => start + .parse() + .or(Err(::Error::Header)) + .map(ByteRangeSpec::AllFrom), + (Some(start), Some(end)) => match (start.parse(), end.parse()) { + (Ok(start), Ok(end)) if start <= end => { + Ok(ByteRangeSpec::FromTo(start, end)) + } + _ => Err(::Error::Header), + }, + _ => Err(::Error::Header), + } + } +} + +fn from_comma_delimited(s: &str) -> Vec { + s.split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y), + }) + .filter_map(|x| x.parse().ok()) + .collect() +} + +impl Header for Range { + fn header_name() -> &'static str { + static NAME: &'static str = "Range"; + NAME + } + + fn parse_header(raw: &Raw) -> ::Result { + from_one_raw_str(raw) + } + + fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { + f.fmt_line(self) + } +} + +#[test] +fn test_parse_bytes_range_valid() { + let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap(); + let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap(); + let r3 = Range::bytes(1, 100); + assert_eq!(r, r2); + assert_eq!(r2, r3); + + let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap(); + let r2: Range = + Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); + let r3 = Range::Bytes(vec![ + ByteRangeSpec::FromTo(1, 100), + ByteRangeSpec::AllFrom(200), + ]); + assert_eq!(r, r2); + assert_eq!(r2, r3); + + let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap(); + let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap(); + let r3 = Range::Bytes(vec![ + ByteRangeSpec::FromTo(1, 100), + ByteRangeSpec::Last(100), + ]); + assert_eq!(r, r2); + assert_eq!(r2, r3); + + let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); + let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); + assert_eq!(r, r2); +} + +#[test] +fn test_parse_unregistered_range_valid() { + let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); + let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); + assert_eq!(r, r2); + + let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap(); + let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned()); + assert_eq!(r, r2); + + let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap(); + let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned()); + assert_eq!(r, r2); +} + +#[test] +fn test_parse_invalid() { + let r: ::Result = Header::parse_header(&"bytes=1-a,-".into()); + assert_eq!(r.ok(), None); + + let r: ::Result = Header::parse_header(&"bytes=1-2-3".into()); + assert_eq!(r.ok(), None); + + let r: ::Result = Header::parse_header(&"abc".into()); + assert_eq!(r.ok(), None); + + let r: ::Result = Header::parse_header(&"bytes=1-100=".into()); + assert_eq!(r.ok(), None); + + let r: ::Result = Header::parse_header(&"bytes=".into()); + assert_eq!(r.ok(), None); + + let r: ::Result = Header::parse_header(&"custom=".into()); + assert_eq!(r.ok(), None); + + let r: ::Result = Header::parse_header(&"=1-100".into()); + assert_eq!(r.ok(), None); +} + +#[test] +fn test_fmt() { + use header::Headers; + + let mut headers = Headers::new(); + + headers.set(Range::Bytes(vec![ + ByteRangeSpec::FromTo(0, 1000), + ByteRangeSpec::AllFrom(2000), + ])); + assert_eq!(&headers.to_string(), "Range: bytes=0-1000,2000-\r\n"); + + headers.clear(); + headers.set(Range::Bytes(vec![])); + + assert_eq!(&headers.to_string(), "Range: bytes=\r\n"); + + headers.clear(); + headers.set(Range::Unregistered( + "custom".to_owned(), + "1-xxx".to_owned(), + )); + + assert_eq!(&headers.to_string(), "Range: custom=1-xxx\r\n"); +} + +#[test] +fn test_byte_range_spec_to_satisfiable_range() { + assert_eq!( + Some((0, 0)), + ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(3) + ); + assert_eq!( + Some((1, 2)), + ByteRangeSpec::FromTo(1, 2).to_satisfiable_range(3) + ); + assert_eq!( + Some((1, 2)), + ByteRangeSpec::FromTo(1, 5).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::FromTo(3, 3).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0) + ); + + assert_eq!( + Some((0, 2)), + ByteRangeSpec::AllFrom(0).to_satisfiable_range(3) + ); + assert_eq!( + Some((2, 2)), + ByteRangeSpec::AllFrom(2).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::AllFrom(3).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::AllFrom(5).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::AllFrom(0).to_satisfiable_range(0) + ); + + assert_eq!( + Some((1, 2)), + ByteRangeSpec::Last(2).to_satisfiable_range(3) + ); + assert_eq!( + Some((2, 2)), + ByteRangeSpec::Last(1).to_satisfiable_range(3) + ); + assert_eq!( + Some((0, 2)), + ByteRangeSpec::Last(5).to_satisfiable_range(3) + ); + assert_eq!(None, ByteRangeSpec::Last(0).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::Last(2).to_satisfiable_range(0)); +} diff --git a/src/header/mod.rs b/src/header/mod.rs new file mode 100644 index 000000000..1ef1bd198 --- /dev/null +++ b/src/header/mod.rs @@ -0,0 +1,465 @@ +//! Various http headers +// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header) + +use std::{fmt, str::FromStr}; + +use bytes::{Bytes, BytesMut}; +use http::header::GetAll; +use http::Error as HttpError; +use mime::Mime; + +pub use http::header::*; + +use crate::error::ParseError; +use crate::httpmessage::HttpMessage; + +mod common; +mod shared; +#[doc(hidden)] +pub use self::common::*; +#[doc(hidden)] +pub use self::shared::*; + +#[doc(hidden)] +/// A trait for any object that will represent a header field and value. +pub trait Header +where + Self: IntoHeaderValue, +{ + /// Returns the name of the header field + fn name() -> HeaderName; + + /// Parse a header + fn parse(msg: &T) -> Result; +} + +#[doc(hidden)] +/// A trait for any object that can be Converted to a `HeaderValue` +pub trait IntoHeaderValue: Sized { + /// The type returned in the event of a conversion error. + type Error: Into; + + /// Try to convert value to a Header value. + fn try_into(self) -> Result; +} + +impl IntoHeaderValue for HeaderValue { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into(self) -> Result { + Ok(self) + } +} + +impl<'a> IntoHeaderValue for &'a str { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into(self) -> Result { + self.parse() + } +} + +impl<'a> IntoHeaderValue for &'a [u8] { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_bytes(self) + } +} + +impl IntoHeaderValue for Bytes { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(self) + } +} + +impl IntoHeaderValue for Vec { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(Bytes::from(self)) + } +} + +impl IntoHeaderValue for String { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(Bytes::from(self)) + } +} + +impl IntoHeaderValue for Mime { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(Bytes::from(format!("{}", self))) + } +} + +/// Represents supported types of content encodings +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum ContentEncoding { + /// Automatically select encoding based on encoding negotiation + Auto, + /// A format using the Brotli algorithm + Br, + /// A format using the zlib structure with deflate algorithm + Deflate, + /// Gzip algorithm + Gzip, + /// Indicates the identity function (i.e. no compression, nor modification) + Identity, +} + +impl ContentEncoding { + #[inline] + /// Is the content compressed? + pub fn is_compression(self) -> bool { + match self { + ContentEncoding::Identity | ContentEncoding::Auto => false, + _ => true, + } + } + + #[inline] + /// Convert content encoding to string + pub fn as_str(self) -> &'static str { + match self { + ContentEncoding::Br => "br", + ContentEncoding::Gzip => "gzip", + ContentEncoding::Deflate => "deflate", + ContentEncoding::Identity | ContentEncoding::Auto => "identity", + } + } + + #[inline] + /// default quality value + pub fn quality(self) -> f64 { + match self { + ContentEncoding::Br => 1.1, + ContentEncoding::Gzip => 1.0, + ContentEncoding::Deflate => 0.9, + ContentEncoding::Identity | ContentEncoding::Auto => 0.1, + } + } +} + +impl<'a> From<&'a str> for ContentEncoding { + fn from(s: &'a str) -> ContentEncoding { + let s = s.trim(); + + if s.eq_ignore_ascii_case("br") { + ContentEncoding::Br + } else if s.eq_ignore_ascii_case("gzip") { + ContentEncoding::Gzip + } else if s.eq_ignore_ascii_case("deflate") { + ContentEncoding::Deflate + } else { + ContentEncoding::Identity + } + } +} + +#[doc(hidden)] +pub(crate) struct Writer { + buf: BytesMut, +} + +impl Writer { + fn new() -> Writer { + Writer { + buf: BytesMut::new(), + } + } + fn take(&mut self) -> Bytes { + self.buf.take().freeze() + } +} + +impl fmt::Write for Writer { + #[inline] + fn write_str(&mut self, s: &str) -> fmt::Result { + self.buf.extend_from_slice(s.as_bytes()); + Ok(()) + } + + #[inline] + fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { + fmt::write(self, args) + } +} + +#[inline] +#[doc(hidden)] +/// Reads a comma-delimited raw header into a Vec. +pub fn from_comma_delimited( + all: GetAll, +) -> Result, ParseError> { + let mut result = Vec::new(); + for h in all { + let s = h.to_str().map_err(|_| ParseError::Header)?; + result.extend( + s.split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y), + }) + .filter_map(|x| x.trim().parse().ok()), + ) + } + Ok(result) +} + +#[inline] +#[doc(hidden)] +/// Reads a single string when parsing a header. +pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { + if let Some(line) = val { + let line = line.to_str().map_err(|_| ParseError::Header)?; + if !line.is_empty() { + return T::from_str(line).or(Err(ParseError::Header)); + } + } + Err(ParseError::Header) +} + +#[inline] +#[doc(hidden)] +/// Format an array into a comma-delimited string. +pub fn fmt_comma_delimited(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result +where + T: fmt::Display, +{ + let mut iter = parts.iter(); + if let Some(part) = iter.next() { + fmt::Display::fmt(part, f)?; + } + for part in iter { + f.write_str(", ")?; + fmt::Display::fmt(part, f)?; + } + Ok(()) +} + +// From hyper v0.11.27 src/header/parsing.rs + +/// The value part of an extended parameter consisting of three parts: +/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`), +/// and a character sequence representing the actual value (`value`), separated by single quote +/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). +#[derive(Clone, Debug, PartialEq)] +pub struct ExtendedValue { + /// The character set that is used to encode the `value` to a string. + pub charset: Charset, + /// The human language details of the `value`, if available. + pub language_tag: Option, + /// The parameter value, as expressed in octets. + pub value: Vec, +} + +/// Parses extended header parameter values (`ext-value`), as defined in +/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). +/// +/// Extended values are denoted by parameter names that end with `*`. +/// +/// ## ABNF +/// +/// ```text +/// ext-value = charset "'" [ language ] "'" value-chars +/// ; like RFC 2231's +/// ; (see [RFC2231], Section 7) +/// +/// charset = "UTF-8" / "ISO-8859-1" / mime-charset +/// +/// mime-charset = 1*mime-charsetc +/// mime-charsetc = ALPHA / DIGIT +/// / "!" / "#" / "$" / "%" / "&" +/// / "+" / "-" / "^" / "_" / "`" +/// / "{" / "}" / "~" +/// ; as in Section 2.3 of [RFC2978] +/// ; except that the single quote is not included +/// ; SHOULD be registered in the IANA charset registry +/// +/// language = +/// +/// value-chars = *( pct-encoded / attr-char ) +/// +/// pct-encoded = "%" HEXDIG HEXDIG +/// ; see [RFC3986], Section 2.1 +/// +/// attr-char = ALPHA / DIGIT +/// / "!" / "#" / "$" / "&" / "+" / "-" / "." +/// / "^" / "_" / "`" / "|" / "~" +/// ; token except ( "*" / "'" / "%" ) +/// ``` +pub fn parse_extended_value( + val: &str, +) -> Result { + // Break into three pieces separated by the single-quote character + let mut parts = val.splitn(3, '\''); + + // Interpret the first piece as a Charset + let charset: Charset = match parts.next() { + None => return Err(crate::error::ParseError::Header), + Some(n) => FromStr::from_str(n).map_err(|_| crate::error::ParseError::Header)?, + }; + + // Interpret the second piece as a language tag + let language_tag: Option = match parts.next() { + None => return Err(crate::error::ParseError::Header), + Some("") => None, + Some(s) => match s.parse() { + Ok(lt) => Some(lt), + Err(_) => return Err(crate::error::ParseError::Header), + }, + }; + + // Interpret the third piece as a sequence of value characters + let value: Vec = match parts.next() { + None => return Err(crate::error::ParseError::Header), + Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(), + }; + + Ok(ExtendedValue { + value, + charset, + language_tag, + }) +} + +impl fmt::Display for ExtendedValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let encoded_value = percent_encoding::percent_encode( + &self.value[..], + self::percent_encoding_http::HTTP_VALUE, + ); + if let Some(ref lang) = self.language_tag { + write!(f, "{}'{}'{}", self.charset, lang, encoded_value) + } else { + write!(f, "{}''{}", self.charset, encoded_value) + } + } +} + +/// Percent encode a sequence of bytes with a character set defined in +/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] +/// +/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 +pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { + let encoded = + percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); + fmt::Display::fmt(&encoded, f) +} + +mod percent_encoding_http { + use percent_encoding::{self, define_encode_set}; + + // internal module because macro is hard-coded to make a public item + // but we don't want to public export this item + define_encode_set! { + // This encode set is used for HTTP header values and is defined at + // https://tools.ietf.org/html/rfc5987#section-3.2 + pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { + ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', + '[', '\\', ']', '{', '}' + } + } +} + +#[cfg(test)] +mod tests { + use super::shared::Charset; + use super::{parse_extended_value, ExtendedValue}; + use language_tags::LanguageTag; + + #[test] + fn test_parse_extended_value_with_encoding_and_language_tag() { + let expected_language_tag = "en".parse::().unwrap(); + // RFC 5987, Section 3.2.2 + // Extended notation, using the Unicode character U+00A3 (POUND SIGN) + let result = parse_extended_value("iso-8859-1'en'%A3%20rates"); + assert!(result.is_ok()); + let extended_value = result.unwrap(); + assert_eq!(Charset::Iso_8859_1, extended_value.charset); + assert!(extended_value.language_tag.is_some()); + assert_eq!(expected_language_tag, extended_value.language_tag.unwrap()); + assert_eq!( + vec![163, b' ', b'r', b'a', b't', b'e', b's'], + extended_value.value + ); + } + + #[test] + fn test_parse_extended_value_with_encoding() { + // RFC 5987, Section 3.2.2 + // Extended notation, using the Unicode characters U+00A3 (POUND SIGN) + // and U+20AC (EURO SIGN) + let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); + assert!(result.is_ok()); + let extended_value = result.unwrap(); + assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset); + assert!(extended_value.language_tag.is_none()); + assert_eq!( + vec![ + 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', + b't', b'e', b's', + ], + extended_value.value + ); + } + + #[test] + fn test_parse_extended_value_missing_language_tag_and_encoding() { + // From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2 + let result = parse_extended_value("foo%20bar.html"); + assert!(result.is_err()); + } + + #[test] + fn test_parse_extended_value_partially_formatted() { + let result = parse_extended_value("UTF-8'missing third part"); + assert!(result.is_err()); + } + + #[test] + fn test_parse_extended_value_partially_formatted_blank() { + let result = parse_extended_value("blank second part'"); + assert!(result.is_err()); + } + + #[test] + fn test_fmt_extended_value_with_encoding_and_language_tag() { + let extended_value = ExtendedValue { + charset: Charset::Iso_8859_1, + language_tag: Some("en".parse().expect("Could not parse language tag")), + value: vec![163, b' ', b'r', b'a', b't', b'e', b's'], + }; + assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value)); + } + + #[test] + fn test_fmt_extended_value_with_encoding() { + let extended_value = ExtendedValue { + charset: Charset::Ext("UTF-8".to_string()), + language_tag: None, + value: vec![ + 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', + b't', b'e', b's', + ], + }; + assert_eq!( + "UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", + format!("{}", extended_value) + ); + } +} diff --git a/src/header/shared/charset.rs b/src/header/shared/charset.rs new file mode 100644 index 000000000..ec3fe3854 --- /dev/null +++ b/src/header/shared/charset.rs @@ -0,0 +1,153 @@ +use std::fmt::{self, Display}; +use std::str::FromStr; + +use self::Charset::*; + +/// A Mime charset. +/// +/// The string representation is normalized to upper case. +/// +/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url]. +/// +/// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml +#[derive(Clone, Debug, PartialEq)] +#[allow(non_camel_case_types)] +pub enum Charset { + /// US ASCII + Us_Ascii, + /// ISO-8859-1 + Iso_8859_1, + /// ISO-8859-2 + Iso_8859_2, + /// ISO-8859-3 + Iso_8859_3, + /// ISO-8859-4 + Iso_8859_4, + /// ISO-8859-5 + Iso_8859_5, + /// ISO-8859-6 + Iso_8859_6, + /// ISO-8859-7 + Iso_8859_7, + /// ISO-8859-8 + Iso_8859_8, + /// ISO-8859-9 + Iso_8859_9, + /// ISO-8859-10 + Iso_8859_10, + /// Shift_JIS + Shift_Jis, + /// EUC-JP + Euc_Jp, + /// ISO-2022-KR + Iso_2022_Kr, + /// EUC-KR + Euc_Kr, + /// ISO-2022-JP + Iso_2022_Jp, + /// ISO-2022-JP-2 + Iso_2022_Jp_2, + /// ISO-8859-6-E + Iso_8859_6_E, + /// ISO-8859-6-I + Iso_8859_6_I, + /// ISO-8859-8-E + Iso_8859_8_E, + /// ISO-8859-8-I + Iso_8859_8_I, + /// GB2312 + Gb2312, + /// Big5 + Big5, + /// KOI8-R + Koi8_R, + /// An arbitrary charset specified as a string + Ext(String), +} + +impl Charset { + fn label(&self) -> &str { + match *self { + Us_Ascii => "US-ASCII", + Iso_8859_1 => "ISO-8859-1", + Iso_8859_2 => "ISO-8859-2", + Iso_8859_3 => "ISO-8859-3", + Iso_8859_4 => "ISO-8859-4", + Iso_8859_5 => "ISO-8859-5", + Iso_8859_6 => "ISO-8859-6", + Iso_8859_7 => "ISO-8859-7", + Iso_8859_8 => "ISO-8859-8", + Iso_8859_9 => "ISO-8859-9", + Iso_8859_10 => "ISO-8859-10", + Shift_Jis => "Shift-JIS", + Euc_Jp => "EUC-JP", + Iso_2022_Kr => "ISO-2022-KR", + Euc_Kr => "EUC-KR", + Iso_2022_Jp => "ISO-2022-JP", + Iso_2022_Jp_2 => "ISO-2022-JP-2", + Iso_8859_6_E => "ISO-8859-6-E", + Iso_8859_6_I => "ISO-8859-6-I", + Iso_8859_8_E => "ISO-8859-8-E", + Iso_8859_8_I => "ISO-8859-8-I", + Gb2312 => "GB2312", + Big5 => "big5", + Koi8_R => "KOI8-R", + Ext(ref s) => s, + } + } +} + +impl Display for Charset { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self.label()) + } +} + +impl FromStr for Charset { + type Err = crate::Error; + + fn from_str(s: &str) -> crate::Result { + Ok(match s.to_ascii_uppercase().as_ref() { + "US-ASCII" => Us_Ascii, + "ISO-8859-1" => Iso_8859_1, + "ISO-8859-2" => Iso_8859_2, + "ISO-8859-3" => Iso_8859_3, + "ISO-8859-4" => Iso_8859_4, + "ISO-8859-5" => Iso_8859_5, + "ISO-8859-6" => Iso_8859_6, + "ISO-8859-7" => Iso_8859_7, + "ISO-8859-8" => Iso_8859_8, + "ISO-8859-9" => Iso_8859_9, + "ISO-8859-10" => Iso_8859_10, + "SHIFT-JIS" => Shift_Jis, + "EUC-JP" => Euc_Jp, + "ISO-2022-KR" => Iso_2022_Kr, + "EUC-KR" => Euc_Kr, + "ISO-2022-JP" => Iso_2022_Jp, + "ISO-2022-JP-2" => Iso_2022_Jp_2, + "ISO-8859-6-E" => Iso_8859_6_E, + "ISO-8859-6-I" => Iso_8859_6_I, + "ISO-8859-8-E" => Iso_8859_8_E, + "ISO-8859-8-I" => Iso_8859_8_I, + "GB2312" => Gb2312, + "big5" => Big5, + "KOI8-R" => Koi8_R, + s => Ext(s.to_owned()), + }) + } +} + +#[test] +fn test_parse() { + assert_eq!(Us_Ascii, "us-ascii".parse().unwrap()); + assert_eq!(Us_Ascii, "US-Ascii".parse().unwrap()); + assert_eq!(Us_Ascii, "US-ASCII".parse().unwrap()); + assert_eq!(Shift_Jis, "Shift-JIS".parse().unwrap()); + assert_eq!(Ext("ABCD".to_owned()), "abcd".parse().unwrap()); +} + +#[test] +fn test_display() { + assert_eq!("US-ASCII", format!("{}", Us_Ascii)); + assert_eq!("ABCD", format!("{}", Ext("ABCD".to_owned()))); +} diff --git a/src/header/shared/encoding.rs b/src/header/shared/encoding.rs new file mode 100644 index 000000000..af7404828 --- /dev/null +++ b/src/header/shared/encoding.rs @@ -0,0 +1,58 @@ +use std::{fmt, str}; + +pub use self::Encoding::{ + Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers, +}; + +/// A value to represent an encoding used in `Transfer-Encoding` +/// or `Accept-Encoding` header. +#[derive(Clone, PartialEq, Debug)] +pub enum Encoding { + /// The `chunked` encoding. + Chunked, + /// The `br` encoding. + Brotli, + /// The `gzip` encoding. + Gzip, + /// The `deflate` encoding. + Deflate, + /// The `compress` encoding. + Compress, + /// The `identity` encoding. + Identity, + /// The `trailers` encoding. + Trailers, + /// Some other encoding that is less common, can be any String. + EncodingExt(String), +} + +impl fmt::Display for Encoding { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + Chunked => "chunked", + Brotli => "br", + Gzip => "gzip", + Deflate => "deflate", + Compress => "compress", + Identity => "identity", + Trailers => "trailers", + EncodingExt(ref s) => s.as_ref(), + }) + } +} + +impl str::FromStr for Encoding { + type Err = crate::error::ParseError; + fn from_str(s: &str) -> Result { + match s { + "chunked" => Ok(Chunked), + "br" => Ok(Brotli), + "deflate" => Ok(Deflate), + "gzip" => Ok(Gzip), + "compress" => Ok(Compress), + "identity" => Ok(Identity), + "trailers" => Ok(Trailers), + _ => Ok(EncodingExt(s.to_owned())), + } + } +} diff --git a/src/header/shared/entity.rs b/src/header/shared/entity.rs new file mode 100644 index 000000000..da02dc193 --- /dev/null +++ b/src/header/shared/entity.rs @@ -0,0 +1,265 @@ +use std::fmt::{self, Display, Write}; +use std::str::FromStr; + +use crate::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer}; + +/// check that each char in the slice is either: +/// 1. `%x21`, or +/// 2. in the range `%x23` to `%x7E`, or +/// 3. above `%x80` +fn check_slice_validity(slice: &str) -> bool { + slice + .bytes() + .all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80')) +} + +/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3) +/// +/// An entity tag consists of a string enclosed by two literal double quotes. +/// Preceding the first double quote is an optional weakness indicator, +/// which always looks like `W/`. Examples for valid tags are `"xyzzy"` and +/// `W/"xyzzy"`. +/// +/// # ABNF +/// +/// ```text +/// entity-tag = [ weak ] opaque-tag +/// weak = %x57.2F ; "W/", case-sensitive +/// opaque-tag = DQUOTE *etagc DQUOTE +/// etagc = %x21 / %x23-7E / obs-text +/// ; VCHAR except double quotes, plus obs-text +/// ``` +/// +/// # Comparison +/// To check if two entity tags are equivalent in an application always use the +/// `strong_eq` or `weak_eq` methods based on the context of the Tag. Only use +/// `==` to check if two tags are identical. +/// +/// The example below shows the results for a set of entity-tag pairs and +/// both the weak and strong comparison function results: +/// +/// | `ETag 1`| `ETag 2`| Strong Comparison | Weak Comparison | +/// |---------|---------|-------------------|-----------------| +/// | `W/"1"` | `W/"1"` | no match | match | +/// | `W/"1"` | `W/"2"` | no match | no match | +/// | `W/"1"` | `"1"` | no match | match | +/// | `"1"` | `"1"` | match | match | +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EntityTag { + /// Weakness indicator for the tag + pub weak: bool, + /// The opaque string in between the DQUOTEs + tag: String, +} + +impl EntityTag { + /// Constructs a new EntityTag. + /// # Panics + /// If the tag contains invalid characters. + pub fn new(weak: bool, tag: String) -> EntityTag { + assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); + EntityTag { weak, tag } + } + + /// Constructs a new weak EntityTag. + /// # Panics + /// If the tag contains invalid characters. + pub fn weak(tag: String) -> EntityTag { + EntityTag::new(true, tag) + } + + /// Constructs a new strong EntityTag. + /// # Panics + /// If the tag contains invalid characters. + pub fn strong(tag: String) -> EntityTag { + EntityTag::new(false, tag) + } + + /// Get the tag. + pub fn tag(&self) -> &str { + self.tag.as_ref() + } + + /// Set the tag. + /// # Panics + /// If the tag contains invalid characters. + pub fn set_tag(&mut self, tag: String) { + assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); + self.tag = tag + } + + /// For strong comparison two entity-tags are equivalent if both are not + /// weak and their opaque-tags match character-by-character. + pub fn strong_eq(&self, other: &EntityTag) -> bool { + !self.weak && !other.weak && self.tag == other.tag + } + + /// For weak comparison two entity-tags are equivalent if their + /// opaque-tags match character-by-character, regardless of either or + /// both being tagged as "weak". + pub fn weak_eq(&self, other: &EntityTag) -> bool { + self.tag == other.tag + } + + /// The inverse of `EntityTag.strong_eq()`. + pub fn strong_ne(&self, other: &EntityTag) -> bool { + !self.strong_eq(other) + } + + /// The inverse of `EntityTag.weak_eq()`. + pub fn weak_ne(&self, other: &EntityTag) -> bool { + !self.weak_eq(other) + } +} + +impl Display for EntityTag { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.weak { + write!(f, "W/\"{}\"", self.tag) + } else { + write!(f, "\"{}\"", self.tag) + } + } +} + +impl FromStr for EntityTag { + type Err = crate::error::ParseError; + + fn from_str(s: &str) -> Result { + let length: usize = s.len(); + let slice = &s[..]; + // Early exits if it doesn't terminate in a DQUOTE. + if !slice.ends_with('"') || slice.len() < 2 { + return Err(crate::error::ParseError::Header); + } + // The etag is weak if its first char is not a DQUOTE. + if slice.len() >= 2 + && slice.starts_with('"') + && check_slice_validity(&slice[1..length - 1]) + { + // No need to check if the last char is a DQUOTE, + // we already did that above. + return Ok(EntityTag { + weak: false, + tag: slice[1..length - 1].to_owned(), + }); + } else if slice.len() >= 4 + && slice.starts_with("W/\"") + && check_slice_validity(&slice[3..length - 1]) + { + return Ok(EntityTag { + weak: true, + tag: slice[3..length - 1].to_owned(), + }); + } + Err(crate::error::ParseError::Header) + } +} + +impl IntoHeaderValue for EntityTag { + type Error = InvalidHeaderValueBytes; + + fn try_into(self) -> Result { + let mut wrt = Writer::new(); + write!(wrt, "{}", self).unwrap(); + HeaderValue::from_shared(wrt.take()) + } +} + +#[cfg(test)] +mod tests { + use super::EntityTag; + + #[test] + fn test_etag_parse_success() { + // Expected success + assert_eq!( + "\"foobar\"".parse::().unwrap(), + EntityTag::strong("foobar".to_owned()) + ); + assert_eq!( + "\"\"".parse::().unwrap(), + EntityTag::strong("".to_owned()) + ); + assert_eq!( + "W/\"weaktag\"".parse::().unwrap(), + EntityTag::weak("weaktag".to_owned()) + ); + assert_eq!( + "W/\"\x65\x62\"".parse::().unwrap(), + EntityTag::weak("\x65\x62".to_owned()) + ); + assert_eq!( + "W/\"\"".parse::().unwrap(), + EntityTag::weak("".to_owned()) + ); + } + + #[test] + fn test_etag_parse_failures() { + // Expected failures + assert!("no-dquotes".parse::().is_err()); + assert!("w/\"the-first-w-is-case-sensitive\"" + .parse::() + .is_err()); + assert!("".parse::().is_err()); + assert!("\"unmatched-dquotes1".parse::().is_err()); + assert!("unmatched-dquotes2\"".parse::().is_err()); + assert!("matched-\"dquotes\"".parse::().is_err()); + } + + #[test] + fn test_etag_fmt() { + assert_eq!( + format!("{}", EntityTag::strong("foobar".to_owned())), + "\"foobar\"" + ); + assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); + assert_eq!( + format!("{}", EntityTag::weak("weak-etag".to_owned())), + "W/\"weak-etag\"" + ); + assert_eq!( + format!("{}", EntityTag::weak("\u{0065}".to_owned())), + "W/\"\x65\"" + ); + assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); + } + + #[test] + fn test_cmp() { + // | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison | + // |---------|---------|-------------------|-----------------| + // | `W/"1"` | `W/"1"` | no match | match | + // | `W/"1"` | `W/"2"` | no match | no match | + // | `W/"1"` | `"1"` | no match | match | + // | `"1"` | `"1"` | match | match | + let mut etag1 = EntityTag::weak("1".to_owned()); + let mut etag2 = EntityTag::weak("1".to_owned()); + assert!(!etag1.strong_eq(&etag2)); + assert!(etag1.weak_eq(&etag2)); + assert!(etag1.strong_ne(&etag2)); + assert!(!etag1.weak_ne(&etag2)); + + etag1 = EntityTag::weak("1".to_owned()); + etag2 = EntityTag::weak("2".to_owned()); + assert!(!etag1.strong_eq(&etag2)); + assert!(!etag1.weak_eq(&etag2)); + assert!(etag1.strong_ne(&etag2)); + assert!(etag1.weak_ne(&etag2)); + + etag1 = EntityTag::weak("1".to_owned()); + etag2 = EntityTag::strong("1".to_owned()); + assert!(!etag1.strong_eq(&etag2)); + assert!(etag1.weak_eq(&etag2)); + assert!(etag1.strong_ne(&etag2)); + assert!(!etag1.weak_ne(&etag2)); + + etag1 = EntityTag::strong("1".to_owned()); + etag2 = EntityTag::strong("1".to_owned()); + assert!(etag1.strong_eq(&etag2)); + assert!(etag1.weak_eq(&etag2)); + assert!(!etag1.strong_ne(&etag2)); + assert!(!etag1.weak_ne(&etag2)); + } +} diff --git a/src/header/shared/httpdate.rs b/src/header/shared/httpdate.rs new file mode 100644 index 000000000..350f77bbe --- /dev/null +++ b/src/header/shared/httpdate.rs @@ -0,0 +1,118 @@ +use std::fmt::{self, Display}; +use std::io::Write; +use std::str::FromStr; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use bytes::{BufMut, BytesMut}; +use http::header::{HeaderValue, InvalidHeaderValueBytes}; + +use crate::error::ParseError; +use crate::header::IntoHeaderValue; + +/// A timestamp with HTTP formatting and parsing +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct HttpDate(time::Tm); + +impl FromStr for HttpDate { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + match time::strptime(s, "%a, %d %b %Y %T %Z") + .or_else(|_| time::strptime(s, "%A, %d-%b-%y %T %Z")) + .or_else(|_| time::strptime(s, "%c")) + { + Ok(t) => Ok(HttpDate(t)), + Err(_) => Err(ParseError::Header), + } + } +} + +impl Display for HttpDate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0.to_utc().rfc822(), f) + } +} + +impl From for HttpDate { + fn from(tm: time::Tm) -> HttpDate { + HttpDate(tm) + } +} + +impl From for HttpDate { + fn from(sys: SystemTime) -> HttpDate { + let tmspec = match sys.duration_since(UNIX_EPOCH) { + Ok(dur) => { + time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32) + } + Err(err) => { + let neg = err.duration(); + time::Timespec::new( + -(neg.as_secs() as i64), + -(neg.subsec_nanos() as i32), + ) + } + }; + HttpDate(time::at_utc(tmspec)) + } +} + +impl IntoHeaderValue for HttpDate { + type Error = InvalidHeaderValueBytes; + + fn try_into(self) -> Result { + let mut wrt = BytesMut::with_capacity(29).writer(); + write!(wrt, "{}", self.0.rfc822()).unwrap(); + HeaderValue::from_shared(wrt.get_mut().take().freeze()) + } +} + +impl From for SystemTime { + fn from(date: HttpDate) -> SystemTime { + let spec = date.0.to_timespec(); + if spec.sec >= 0 { + UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32) + } else { + UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32) + } + } +} + +#[cfg(test)] +mod tests { + use super::HttpDate; + use time::Tm; + + const NOV_07: HttpDate = HttpDate(Tm { + tm_nsec: 0, + tm_sec: 37, + tm_min: 48, + tm_hour: 8, + tm_mday: 7, + tm_mon: 10, + tm_year: 94, + tm_wday: 0, + tm_isdst: 0, + tm_yday: 0, + tm_utcoff: 0, + }); + + #[test] + fn test_date() { + assert_eq!( + "Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), + NOV_07 + ); + assert_eq!( + "Sunday, 07-Nov-94 08:48:37 GMT" + .parse::() + .unwrap(), + NOV_07 + ); + assert_eq!( + "Sun Nov 7 08:48:37 1994".parse::().unwrap(), + NOV_07 + ); + assert!("this-is-no-date".parse::().is_err()); + } +} diff --git a/src/header/shared/mod.rs b/src/header/shared/mod.rs new file mode 100644 index 000000000..f2bc91634 --- /dev/null +++ b/src/header/shared/mod.rs @@ -0,0 +1,14 @@ +//! Copied for `hyper::header::shared`; + +pub use self::charset::Charset; +pub use self::encoding::Encoding; +pub use self::entity::EntityTag; +pub use self::httpdate::HttpDate; +pub use self::quality_item::{q, qitem, Quality, QualityItem}; +pub use language_tags::LanguageTag; + +mod charset; +mod encoding; +mod entity; +mod httpdate; +mod quality_item; diff --git a/src/header/shared/quality_item.rs b/src/header/shared/quality_item.rs new file mode 100644 index 000000000..07c206581 --- /dev/null +++ b/src/header/shared/quality_item.rs @@ -0,0 +1,291 @@ +use std::{cmp, fmt, str}; + +use self::internal::IntoQuality; + +/// Represents a quality used in quality values. +/// +/// Can be created with the `q` function. +/// +/// # Implementation notes +/// +/// The quality value is defined as a number between 0 and 1 with three decimal +/// places. This means there are 1001 possible values. Since floating point +/// numbers are not exact and the smallest floating point data type (`f32`) +/// consumes four bytes, hyper uses an `u16` value to store the +/// quality internally. For performance reasons you may set quality directly to +/// a value between 0 and 1000 e.g. `Quality(532)` matches the quality +/// `q=0.532`. +/// +/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1) +/// gives more information on quality values in HTTP header fields. +#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct Quality(u16); + +impl Default for Quality { + fn default() -> Quality { + Quality(1000) + } +} + +/// Represents an item with a quality value as defined in +/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1). +#[derive(Clone, PartialEq, Debug)] +pub struct QualityItem { + /// The actual contents of the field. + pub item: T, + /// The quality (client or server preference) for the value. + pub quality: Quality, +} + +impl QualityItem { + /// Creates a new `QualityItem` from an item and a quality. + /// The item can be of any type. + /// The quality should be a value in the range [0, 1]. + pub fn new(item: T, quality: Quality) -> QualityItem { + QualityItem { item, quality } + } +} + +impl cmp::PartialOrd for QualityItem { + fn partial_cmp(&self, other: &QualityItem) -> Option { + self.quality.partial_cmp(&other.quality) + } +} + +impl fmt::Display for QualityItem { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.item, f)?; + match self.quality.0 { + 1000 => Ok(()), + 0 => f.write_str("; q=0"), + x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')), + } + } +} + +impl str::FromStr for QualityItem { + type Err = crate::error::ParseError; + + fn from_str(s: &str) -> Result, crate::error::ParseError> { + if !s.is_ascii() { + return Err(crate::error::ParseError::Header); + } + // Set defaults used if parsing fails. + let mut raw_item = s; + let mut quality = 1f32; + + let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect(); + if parts.len() == 2 { + if parts[0].len() < 2 { + return Err(crate::error::ParseError::Header); + } + let start = &parts[0][0..2]; + if start == "q=" || start == "Q=" { + let q_part = &parts[0][2..parts[0].len()]; + if q_part.len() > 5 { + return Err(crate::error::ParseError::Header); + } + match q_part.parse::() { + Ok(q_value) => { + if 0f32 <= q_value && q_value <= 1f32 { + quality = q_value; + raw_item = parts[1]; + } else { + return Err(crate::error::ParseError::Header); + } + } + Err(_) => return Err(crate::error::ParseError::Header), + } + } + } + match raw_item.parse::() { + // we already checked above that the quality is within range + Ok(item) => Ok(QualityItem::new(item, from_f32(quality))), + Err(_) => Err(crate::error::ParseError::Header), + } + } +} + +#[inline] +fn from_f32(f: f32) -> Quality { + // this function is only used internally. A check that `f` is within range + // should be done before calling this method. Just in case, this + // debug_assert should catch if we were forgetful + debug_assert!( + f >= 0f32 && f <= 1f32, + "q value must be between 0.0 and 1.0" + ); + Quality((f * 1000f32) as u16) +} + +/// Convenience function to wrap a value in a `QualityItem` +/// Sets `q` to the default 1.0 +pub fn qitem(item: T) -> QualityItem { + QualityItem::new(item, Default::default()) +} + +/// Convenience function to create a `Quality` from a float or integer. +/// +/// Implemented for `u16` and `f32`. Panics if value is out of range. +pub fn q(val: T) -> Quality { + val.into_quality() +} + +mod internal { + use super::Quality; + + // TryFrom is probably better, but it's not stable. For now, we want to + // keep the functionality of the `q` function, while allowing it to be + // generic over `f32` and `u16`. + // + // `q` would panic before, so keep that behavior. `TryFrom` can be + // introduced later for a non-panicking conversion. + + pub trait IntoQuality: Sealed + Sized { + fn into_quality(self) -> Quality; + } + + impl IntoQuality for f32 { + fn into_quality(self) -> Quality { + assert!( + self >= 0f32 && self <= 1f32, + "float must be between 0.0 and 1.0" + ); + super::from_f32(self) + } + } + + impl IntoQuality for u16 { + fn into_quality(self) -> Quality { + assert!(self <= 1000, "u16 must be between 0 and 1000"); + Quality(self) + } + } + + pub trait Sealed {} + impl Sealed for u16 {} + impl Sealed for f32 {} +} + +#[cfg(test)] +mod tests { + use super::super::encoding::*; + use super::*; + + #[test] + fn test_quality_item_fmt_q_1() { + let x = qitem(Chunked); + assert_eq!(format!("{}", x), "chunked"); + } + #[test] + fn test_quality_item_fmt_q_0001() { + let x = QualityItem::new(Chunked, Quality(1)); + assert_eq!(format!("{}", x), "chunked; q=0.001"); + } + #[test] + fn test_quality_item_fmt_q_05() { + // Custom value + let x = QualityItem { + item: EncodingExt("identity".to_owned()), + quality: Quality(500), + }; + assert_eq!(format!("{}", x), "identity; q=0.5"); + } + + #[test] + fn test_quality_item_fmt_q_0() { + // Custom value + let x = QualityItem { + item: EncodingExt("identity".to_owned()), + quality: Quality(0), + }; + assert_eq!(x.to_string(), "identity; q=0"); + } + + #[test] + fn test_quality_item_from_str1() { + let x: Result, _> = "chunked".parse(); + assert_eq!( + x.unwrap(), + QualityItem { + item: Chunked, + quality: Quality(1000), + } + ); + } + #[test] + fn test_quality_item_from_str2() { + let x: Result, _> = "chunked; q=1".parse(); + assert_eq!( + x.unwrap(), + QualityItem { + item: Chunked, + quality: Quality(1000), + } + ); + } + #[test] + fn test_quality_item_from_str3() { + let x: Result, _> = "gzip; q=0.5".parse(); + assert_eq!( + x.unwrap(), + QualityItem { + item: Gzip, + quality: Quality(500), + } + ); + } + #[test] + fn test_quality_item_from_str4() { + let x: Result, _> = "gzip; q=0.273".parse(); + assert_eq!( + x.unwrap(), + QualityItem { + item: Gzip, + quality: Quality(273), + } + ); + } + #[test] + fn test_quality_item_from_str5() { + let x: Result, _> = "gzip; q=0.2739999".parse(); + assert!(x.is_err()); + } + #[test] + fn test_quality_item_from_str6() { + let x: Result, _> = "gzip; q=2".parse(); + assert!(x.is_err()); + } + #[test] + fn test_quality_item_ordering() { + let x: QualityItem = "gzip; q=0.5".parse().ok().unwrap(); + let y: QualityItem = "gzip; q=0.273".parse().ok().unwrap(); + let comparision_result: bool = x.gt(&y); + assert!(comparision_result) + } + + #[test] + fn test_quality() { + assert_eq!(q(0.5), Quality(500)); + } + + #[test] + #[should_panic] // FIXME - 32-bit msvc unwinding broken + #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] + fn test_quality_invalid() { + q(-1.0); + } + + #[test] + #[should_panic] // FIXME - 32-bit msvc unwinding broken + #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] + fn test_quality_invalid2() { + q(2.0); + } + + #[test] + fn test_fuzzing_bugs() { + assert!("99999;".parse::>().is_err()); + assert!("\x0d;;;=\u{d6aa}==".parse::>().is_err()) + } +} diff --git a/tests/test_ws.rs b/tests/test_ws.rs index bf5a3c41e..e5a54c7c1 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -6,7 +6,7 @@ use actix_service::NewService; use actix_utils::framed::IntoFramed; use actix_utils::stream::TakeItem; use bytes::{Bytes, BytesMut}; -use futures::future::{lazy, ok, Either}; +use futures::future::{ok, Either}; use futures::{Future, Sink, Stream}; use actix_http::{h1, ws, ResponseError, SendResponse, ServiceConfig}; From 7d49a07f91156253a371000d24fa9e4291cbce46 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Feb 2019 13:39:15 -0800 Subject: [PATCH 123/208] add h1/h2 payload --- src/lib.rs | 1 + src/payload.rs | 32 ++++++++++++++++++++++++++++++++ src/request.rs | 12 +++++++----- src/service/mod.rs | 2 -- src/test.rs | 8 ++++---- 5 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 src/payload.rs diff --git a/src/lib.rs b/src/lib.rs index a3ca52eda..715823930 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,7 @@ mod service; pub mod error; pub mod h1; pub mod h2; +pub mod payload; pub mod test; pub mod ws; diff --git a/src/payload.rs b/src/payload.rs new file mode 100644 index 000000000..ede1281ec --- /dev/null +++ b/src/payload.rs @@ -0,0 +1,32 @@ +use bytes::Bytes; +use derive_more::From; +use futures::{Poll, Stream}; +use h2::RecvStream; + +use crate::error::PayloadError; + +#[derive(From)] +pub enum Payload { + H1(crate::h1::Payload), + H2(crate::h2::Payload), + Dyn(Box>), +} + +impl From for Payload { + fn from(v: RecvStream) -> Self { + Payload::H2(crate::h2::Payload::new(v)) + } +} + +impl Stream for Payload { + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll, Self::Error> { + match self { + Payload::H1(ref mut pl) => pl.poll(), + Payload::H2(ref mut pl) => pl.poll(), + Payload::Dyn(ref mut pl) => pl.poll(), + } + } +} diff --git a/src/request.rs b/src/request.rs index 4df95d450..f6be69ddb 100644 --- a/src/request.rs +++ b/src/request.rs @@ -10,8 +10,7 @@ use crate::error::PayloadError; use crate::extensions::Extensions; use crate::httpmessage::HttpMessage; use crate::message::{Message, MessagePool, RequestHead}; - -use crate::h1::Payload; +use crate::payload::Payload; /// Request pub struct Request

    { @@ -39,7 +38,7 @@ impl Request { /// Create new Request instance pub fn new() -> Request { Request { - payload: Some(Payload::empty()), + payload: None, inner: MessagePool::get_message(), } } @@ -55,9 +54,12 @@ impl Request { } /// Create new Request instance - pub fn set_payload

    (self, payload: P) -> Request

    { + pub fn set_payload(self, payload: I) -> Request

    + where + I: Into

    , + { Request { - payload: Some(payload), + payload: Some(payload.into()), inner: self.inner.clone(), } } diff --git a/src/service/mod.rs b/src/service/mod.rs index 3939ab99c..83a40bd12 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -1,5 +1,3 @@ -use h2::RecvStream; - mod senderror; pub use self::senderror::{SendError, SendResponse}; diff --git a/src/test.rs b/src/test.rs index c68bbf8e5..cd160e60a 100644 --- a/src/test.rs +++ b/src/test.rs @@ -6,8 +6,8 @@ use cookie::Cookie; use http::header::HeaderName; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; -use crate::h1::Payload; use crate::header::{Header, IntoHeaderValue}; +use crate::payload::Payload; use crate::Request; /// Test `Request` builder @@ -125,9 +125,9 @@ impl TestRequest { /// Set request payload pub fn set_payload>(mut self, data: B) -> Self { - let mut payload = Payload::empty(); + let mut payload = crate::h1::Payload::empty(); payload.unread_data(data.into()); - self.payload = Some(payload); + self.payload = Some(payload.into()); self } @@ -151,7 +151,7 @@ impl TestRequest { let mut req = if let Some(pl) = payload { Request::with_payload(pl) } else { - Request::with_payload(Payload::empty()) + Request::with_payload(crate::h1::Payload::empty().into()) }; let inner = req.inner_mut(); From 5575ee7d2d0ce5281c5980a9b6c8769e2d5c9d85 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Feb 2019 13:41:50 -0800 Subject: [PATCH 124/208] use same payload type for h1 and h2 --- src/h1/service.rs | 3 ++- src/h2/dispatcher.rs | 5 +++-- src/h2/service.rs | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/h1/service.rs b/src/h1/service.rs index c35d18714..fbc0a2f0f 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -11,6 +11,7 @@ use log::error; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, ParseError}; +use crate::payload::Payload; use crate::request::Request; use crate::response::Response; @@ -27,7 +28,7 @@ pub struct H1Service { impl H1Service where - S: NewService> + Clone, + S: NewService, Response = Response> + Clone, S::Service: Clone, S::Error: Debug, B: MessageBody, diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index 2994d0a3d..301777a82 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -21,10 +21,11 @@ use crate::body::{Body, BodyLength, MessageBody, ResponseBody}; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error, ParseError, PayloadError, ResponseError}; use crate::message::ResponseHead; +use crate::payload::Payload; use crate::request::Request; use crate::response::Response; -use super::{H2ServiceResult, Payload}; +use super::H2ServiceResult; const CHUNK_SIZE: usize = 16_384; @@ -113,7 +114,7 @@ where } let (parts, body) = req.into_parts(); - let mut req = Request::with_payload(Payload::new(body)); + let mut req = Request::with_payload(body.into()); let head = &mut req.inner_mut().head; head.uri = parts.uri; diff --git a/src/h2/service.rs b/src/h2/service.rs index b598b0a6d..5759b55e2 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -14,11 +14,12 @@ use log::error; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, Error, ParseError, ResponseError}; +use crate::payload::Payload; use crate::request::Request; use crate::response::Response; use super::dispatcher::Dispatcher; -use super::{H2ServiceResult, Payload}; +use super::H2ServiceResult; /// `NewService` implementation for HTTP2 transport pub struct H2Service { From 2a6e4dc7ab0ea8fca3df3ebbb7e1905e4a7daa42 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Feb 2019 19:26:12 -0800 Subject: [PATCH 125/208] use non mutable self for HttpMessage::payload() for ergonomic reasons --- src/client/h2proto.rs | 3 ++- src/client/response.rs | 13 +++++++------ src/httpmessage.rs | 2 +- src/lib.rs | 2 +- src/request.rs | 18 +++++++++--------- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index f2f18d935..ecd18cf82 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::time; use actix_codec::{AsyncRead, AsyncWrite}; @@ -110,7 +111,7 @@ where Ok(ClientResponse { head, - payload: Some(Box::new(Payload::new(body))), + payload: RefCell::new(Some(Box::new(Payload::new(body)))), }) }) .from_err() diff --git a/src/client/response.rs b/src/client/response.rs index 65c59f2a6..7a83d825d 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::fmt; use bytes::Bytes; @@ -13,7 +14,7 @@ use crate::message::{Head, ResponseHead}; #[derive(Default)] pub struct ClientResponse { pub(crate) head: ResponseHead, - pub(crate) payload: Option, + pub(crate) payload: RefCell>, } impl HttpMessage for ClientResponse { @@ -24,8 +25,8 @@ impl HttpMessage for ClientResponse { } #[inline] - fn payload(&mut self) -> Option { - self.payload.take() + fn payload(&self) -> Option { + self.payload.borrow_mut().take() } } @@ -34,7 +35,7 @@ impl ClientResponse { pub fn new() -> ClientResponse { ClientResponse { head: ResponseHead::default(), - payload: None, + payload: RefCell::new(None), } } @@ -80,7 +81,7 @@ impl ClientResponse { /// Set response payload pub fn set_payload(&mut self, payload: PayloadStream) { - self.payload = Some(payload); + *self.payload.get_mut() = Some(payload); } } @@ -89,7 +90,7 @@ impl Stream for ClientResponse { type Error = PayloadError; fn poll(&mut self) -> Poll, Self::Error> { - if let Some(ref mut payload) = self.payload { + if let Some(ref mut payload) = self.payload.get_mut() { payload.poll() } else { Ok(Async::Ready(None)) diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 39aa1b689..47fc57d6c 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -25,7 +25,7 @@ pub trait HttpMessage: Sized { fn headers(&self) -> &HeaderMap; /// Message payload stream - fn payload(&mut self) -> Option; + fn payload(&self) -> Option; #[doc(hidden)] /// Get a header diff --git a/src/lib.rs b/src/lib.rs index 715823930..f34da84c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,7 +75,7 @@ mod extensions; mod header; mod helpers; mod httpcodes; -mod httpmessage; +pub mod httpmessage; mod json; mod message; mod request; diff --git a/src/request.rs b/src/request.rs index f6be69ddb..519cc38ed 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,4 +1,4 @@ -use std::cell::{Ref, RefMut}; +use std::cell::{Ref, RefCell, RefMut}; use std::fmt; use std::rc::Rc; @@ -14,7 +14,7 @@ use crate::payload::Payload; /// Request pub struct Request

    { - pub(crate) payload: Option

    , + pub(crate) payload: RefCell>, pub(crate) inner: Rc>, } @@ -29,8 +29,8 @@ where } #[inline] - fn payload(&mut self) -> Option

    { - self.payload.take() + fn payload(&self) -> Option

    { + self.payload.borrow_mut().take() } } @@ -38,7 +38,7 @@ impl Request { /// Create new Request instance pub fn new() -> Request { Request { - payload: None, + payload: RefCell::new(None), inner: MessagePool::get_message(), } } @@ -48,7 +48,7 @@ impl Request { /// Create new Request instance pub fn with_payload(payload: Payload) -> Request { Request { - payload: Some(payload), + payload: RefCell::new(Some(payload.into())), inner: MessagePool::get_message(), } } @@ -59,7 +59,7 @@ impl Request { I: Into

    , { Request { - payload: Some(payload.into()), + payload: RefCell::new(Some(payload.into())), inner: self.inner.clone(), } } @@ -67,9 +67,9 @@ impl Request { /// Take request's payload pub fn take_payload(mut self) -> (Option, Request<()>) { ( - self.payload.take(), + self.payload.get_mut().take(), Request { - payload: Some(()), + payload: RefCell::new(None), inner: self.inner.clone(), }, ) From a7a2d4cf5c82320fc742e479a6e3a02f86f68090 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Feb 2019 19:53:48 -0800 Subject: [PATCH 126/208] fix warns --- examples/echo.rs | 2 +- examples/echo2.rs | 2 +- src/httpmessage.rs | 43 +++++++++++++++++++++---------------------- src/json.rs | 10 +++++----- tests/test_client.rs | 4 ++-- tests/test_server.rs | 20 ++++++++++---------- 6 files changed, 40 insertions(+), 41 deletions(-) diff --git a/examples/echo.rs b/examples/echo.rs index 03d5b4706..5b68024f1 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -18,7 +18,7 @@ fn main() { .client_timeout(1000) .client_disconnect(1000) .server_hostname("localhost") - .finish(|mut req: Request| { + .finish(|req: Request| { req.body().limit(512).and_then(|bytes: Bytes| { info!("request body: {:?}", bytes); let mut res = Response::Ok(); diff --git a/examples/echo2.rs b/examples/echo2.rs index 2fd9cbcff..daaafa087 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -8,7 +8,7 @@ use futures::Future; use log::info; use std::env; -fn handle_request(mut req: Request) -> impl Future { +fn handle_request(req: Request) -> impl Future { req.body().limit(512).from_err().and_then(|bytes: Bytes| { info!("request body: {:?}", bytes); let mut res = Response::Ok(); diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 47fc57d6c..f071cd7bc 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -128,7 +128,7 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn body(&mut self) -> MessageBody { + fn body(&self) -> MessageBody { MessageBody::new(self) } @@ -162,7 +162,7 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn urlencoded(&mut self) -> UrlEncoded { + fn urlencoded(&self) -> UrlEncoded { UrlEncoded::new(self) } @@ -198,12 +198,12 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn json(&mut self) -> JsonBody { + fn json(&self) -> JsonBody { JsonBody::new(self) } /// Return stream of lines. - fn readlines(&mut self) -> Readlines { + fn readlines(&self) -> Readlines { Readlines::new(self) } } @@ -220,10 +220,10 @@ pub struct Readlines { impl Readlines { /// Create a new stream to read request line by line. - fn new(req: &mut T) -> Self { + fn new(req: &T) -> Self { let encoding = match req.encoding() { Ok(enc) => enc, - Err(err) => return Self::err(req, err.into()), + Err(err) => return Self::err(err.into()), }; Readlines { @@ -242,9 +242,9 @@ impl Readlines { self } - fn err(req: &mut T, err: ReadlinesError) -> Self { + fn err(err: ReadlinesError) -> Self { Readlines { - stream: req.payload(), + stream: None, buff: BytesMut::new(), limit: 262_144, checked_buff: true, @@ -366,7 +366,7 @@ pub struct MessageBody { impl MessageBody { /// Create `MessageBody` for request. - pub fn new(req: &mut T) -> MessageBody { + pub fn new(req: &T) -> MessageBody { let mut len = None; if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { @@ -465,7 +465,7 @@ pub struct UrlEncoded { impl UrlEncoded { /// Create a new future to URL encode a request - pub fn new(req: &mut T) -> UrlEncoded { + pub fn new(req: &T) -> UrlEncoded { // check content type if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { return Self::err(UrlencodedError::ContentType); @@ -702,7 +702,7 @@ mod tests { #[test] fn test_urlencoded_error() { - let mut req = TestRequest::with_header( + let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -713,7 +713,7 @@ mod tests { UrlencodedError::UnknownLength ); - let mut req = TestRequest::with_header( + let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -724,7 +724,7 @@ mod tests { UrlencodedError::Overflow ); - let mut req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain") + let req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain") .header(header::CONTENT_LENGTH, "10") .finish(); assert_eq!( @@ -735,7 +735,7 @@ mod tests { #[test] fn test_urlencoded() { - let mut req = TestRequest::with_header( + let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -751,7 +751,7 @@ mod tests { }) ); - let mut req = TestRequest::with_header( + let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8", ) @@ -770,20 +770,19 @@ mod tests { #[test] fn test_message_body() { - let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); + let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); match req.body().poll().err().unwrap() { PayloadError::UnknownLength => (), _ => unreachable!("error"), } - let mut req = - TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); + let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); match req.body().poll().err().unwrap() { PayloadError::Overflow => (), _ => unreachable!("error"), } - let mut req = TestRequest::default() + let req = TestRequest::default() .set_payload(Bytes::from_static(b"test")) .finish(); match req.body().poll().ok().unwrap() { @@ -791,7 +790,7 @@ mod tests { _ => unreachable!("error"), } - let mut req = TestRequest::default() + let req = TestRequest::default() .set_payload(Bytes::from_static(b"11111111111111")) .finish(); match req.body().limit(5).poll().err().unwrap() { @@ -802,14 +801,14 @@ mod tests { #[test] fn test_readlines() { - let mut req = TestRequest::default() + let req = TestRequest::default() .set_payload(Bytes::from_static( b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ industry. Lorem Ipsum has been the industry's standard dummy\n\ Contrary to popular belief, Lorem Ipsum is not simply random text.", )) .finish(); - let mut r = Readlines::new(&mut req); + let mut r = Readlines::new(&req); match r.poll().ok().unwrap() { Async::Ready(Some(s)) => assert_eq!( s, diff --git a/src/json.rs b/src/json.rs index 6cd1e87ab..f750f545d 100644 --- a/src/json.rs +++ b/src/json.rs @@ -50,7 +50,7 @@ pub struct JsonBody { impl JsonBody { /// Create `JsonBody` for request. - pub fn new(req: &mut T) -> Self { + pub fn new(req: &T) -> Self { // check content-type let json = if let Ok(Some(mime)) = req.mime_type() { mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) @@ -164,11 +164,11 @@ mod tests { #[test] fn test_json_body() { - let mut req = TestRequest::default().finish(); + let req = TestRequest::default().finish(); let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - let mut req = TestRequest::default() + let req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/text"), @@ -177,7 +177,7 @@ mod tests { let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - let mut req = TestRequest::default() + let req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), @@ -190,7 +190,7 @@ mod tests { let mut json = req.json::().limit(100); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); - let mut req = TestRequest::default() + let req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), diff --git a/tests/test_client.rs b/tests/test_client.rs index 6f502b0af..606bac22a 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -47,7 +47,7 @@ fn test_h1_v2() { assert!(repr.contains("ClientRequest")); assert!(repr.contains("x-test")); - let mut response = srv.block_on(request.send(&mut connector)).unwrap(); + let response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response @@ -55,7 +55,7 @@ fn test_h1_v2() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); let request = srv.post().finish().unwrap(); - let mut response = srv.block_on(request.send(&mut connector)).unwrap(); + let response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response diff --git a/tests/test_server.rs b/tests/test_server.rs index 53db38403..9fa27e71b 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -89,7 +89,7 @@ fn test_h2_body() -> std::io::Result<()> { .map_err(|e| println!("Openssl error: {}", e)) .and_then( h2::H2Service::build() - .finish(|mut req: Request<_>| { + .finish(|req: Request<_>| { req.body() .limit(1024 * 1024) .and_then(|body| Ok(Response::Ok().body(body))) @@ -101,7 +101,7 @@ fn test_h2_body() -> std::io::Result<()> { let req = client::ClientRequest::get(srv.surl("/")) .body(data.clone()) .unwrap(); - let mut response = srv.send_request(req).unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); let body = srv.block_on(response.body().limit(1024 * 1024)).unwrap(); @@ -350,7 +350,7 @@ fn test_headers() { let req = srv.get().finish().unwrap(); - let mut response = srv.block_on(req.send(&mut connector)).unwrap(); + let response = srv.block_on(req.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response @@ -387,7 +387,7 @@ fn test_body() { }); let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -402,7 +402,7 @@ fn test_head_empty() { }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -428,7 +428,7 @@ fn test_head_binary() { }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -477,7 +477,7 @@ fn test_body_length() { }); let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -496,7 +496,7 @@ fn test_body_chunked_explicit() { }); let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -517,7 +517,7 @@ fn test_body_chunked_implicit() { }); let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -540,7 +540,7 @@ fn test_response_http_error_handling() { }); let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let response = srv.send_request(req).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response From b0e36fdcf9c3eb43606b3980fd12bf53bb8a0cd7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Feb 2019 21:16:46 -0800 Subject: [PATCH 127/208] simplify Message api --- src/h1/codec.rs | 8 +- src/h1/decoder.rs | 39 +++--- src/h2/dispatcher.rs | 2 +- src/message.rs | 104 ++++++++++---- src/request.rs | 95 +++++-------- src/response.rs | 321 +++++++++++++++---------------------------- src/test.rs | 10 +- 7 files changed, 243 insertions(+), 336 deletions(-) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index fbc8b4a58..23feda505 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -115,10 +115,10 @@ impl Decoder for Codec { None => None, }) } else if let Some((req, payload)) = self.decoder.decode(src)? { - self.flags - .set(Flags::HEAD, req.inner.head.method == Method::HEAD); - self.version = req.inner().head.version; - self.ctype = req.inner().head.connection_type(); + let head = req.head(); + self.flags.set(Flags::HEAD, head.method == Method::HEAD); + self.version = head.version; + self.ctype = head.connection_type(); if self.ctype == ConnectionType::KeepAlive && !self.flags.contains(Flags::KEEPALIVE_ENABLED) { diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 74e1fb68c..80bca94c5 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -159,7 +159,7 @@ pub(crate) trait MessageType: Sized { impl MessageType for Request { fn set_connection_type(&mut self, ctype: Option) { - self.inner_mut().head.ctype = ctype; + self.head_mut().ctype = ctype; } fn headers_mut(&mut self) -> &mut HeaderMap { @@ -218,12 +218,10 @@ impl MessageType for Request { } }; - { - let inner = msg.inner_mut(); - inner.head.uri = uri; - inner.head.method = method; - inner.head.version = ver; - } + let head = msg.head_mut(); + head.uri = uri; + head.method = method; + head.version = ver; Ok(Some((msg, decoder))) } @@ -817,7 +815,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::Close)); + assert_eq!(req.head().ctype, Some(ConnectionType::Close)); let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ @@ -825,7 +823,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::Close)); + assert_eq!(req.head().ctype, Some(ConnectionType::Close)); } #[test] @@ -836,7 +834,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::Close)); + assert_eq!(req.head().ctype, Some(ConnectionType::Close)); } #[test] @@ -847,7 +845,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::KeepAlive)); + assert_eq!(req.head().ctype, Some(ConnectionType::KeepAlive)); let mut buf = BytesMut::from( "GET /test HTTP/1.0\r\n\ @@ -855,7 +853,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::KeepAlive)); + assert_eq!(req.head().ctype, Some(ConnectionType::KeepAlive)); } #[test] @@ -866,7 +864,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::KeepAlive)); + assert_eq!(req.head().ctype, Some(ConnectionType::KeepAlive)); } #[test] @@ -877,7 +875,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.inner().head.connection_type(), ConnectionType::Close); + assert_eq!(req.head().connection_type(), ConnectionType::Close); } #[test] @@ -888,11 +886,8 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.inner().head.ctype, None); - assert_eq!( - req.inner().head.connection_type(), - ConnectionType::KeepAlive - ); + assert_eq!(req.head().ctype, None); + assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); } #[test] @@ -905,7 +900,7 @@ mod tests { let req = parse_ready!(&mut buf); assert!(req.upgrade()); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::Upgrade)); + assert_eq!(req.head().ctype, Some(ConnectionType::Upgrade)); let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ @@ -915,7 +910,7 @@ mod tests { let req = parse_ready!(&mut buf); assert!(req.upgrade()); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::Upgrade)); + assert_eq!(req.head().ctype, Some(ConnectionType::Upgrade)); } #[test] @@ -1013,7 +1008,7 @@ mod tests { ); let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::Upgrade)); + assert_eq!(req.head().ctype, Some(ConnectionType::Upgrade)); assert!(req.upgrade()); assert!(pl.is_unhandled()); } diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index 301777a82..001acc560 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -116,7 +116,7 @@ where let (parts, body) = req.into_parts(); let mut req = Request::with_payload(body.into()); - let head = &mut req.inner_mut().head; + let head = &mut req.head_mut(); head.uri = parts.uri; head.method = parts.method; head.version = parts.version; diff --git a/src/message.rs b/src/message.rs index a73392221..08edeef38 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::cell::{Ref, RefCell, RefMut}; use std::collections::VecDeque; use std::rc::Rc; @@ -146,12 +146,59 @@ impl ResponseHead { } pub struct Message { - pub head: T, - pub extensions: RefCell, - pub(crate) pool: &'static MessagePool, + inner: Rc>, + pool: &'static MessagePool, } impl Message { + /// Get new message from the pool of objects + pub fn new() -> Self { + T::pool().get_message() + } + + /// Message extensions + #[inline] + pub fn extensions(&self) -> Ref { + self.inner.as_ref().extensions.borrow() + } + + /// Mutable reference to a the message's extensions + #[inline] + pub fn extensions_mut(&self) -> RefMut { + self.inner.as_ref().extensions.borrow_mut() + } +} + +impl std::ops::Deref for Message { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner.as_ref().head + } +} + +impl std::ops::DerefMut for Message { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut Rc::get_mut(&mut self.inner) + .expect("Multiple copies exist") + .head + } +} + +impl Drop for Message { + fn drop(&mut self) { + if Rc::strong_count(&self.inner) == 1 { + self.pool.release(self.inner.clone()); + } + } +} + +struct MessageInner { + head: T, + extensions: RefCell, +} + +impl MessageInner { #[inline] /// Reset request instance pub fn reset(&mut self) { @@ -160,10 +207,9 @@ impl Message { } } -impl Default for Message { +impl Default for MessageInner { fn default() -> Self { - Message { - pool: T::pool(), + MessageInner { head: T::default(), extensions: RefCell::new(Extensions::new()), } @@ -172,41 +218,39 @@ impl Default for Message { #[doc(hidden)] /// Request's objects pool -pub struct MessagePool(RefCell>>>); +pub struct MessagePool(RefCell>>>); thread_local!(static REQUEST_POOL: &'static MessagePool = MessagePool::::create()); thread_local!(static RESPONSE_POOL: &'static MessagePool = MessagePool::::create()); -impl MessagePool { - /// Get default request's pool - pub fn pool() -> &'static MessagePool { - REQUEST_POOL.with(|p| *p) - } - - /// Get Request object - #[inline] - pub fn get_message() -> Rc> { - REQUEST_POOL.with(|pool| { - if let Some(mut msg) = pool.0.borrow_mut().pop_front() { - if let Some(r) = Rc::get_mut(&mut msg) { - r.reset(); - } - return msg; - } - Rc::new(Message::default()) - }) - } -} - impl MessagePool { fn create() -> &'static MessagePool { let pool = MessagePool(RefCell::new(VecDeque::with_capacity(128))); Box::leak(Box::new(pool)) } + /// Get message from the pool + #[inline] + fn get_message(&'static self) -> Message { + if let Some(mut msg) = self.0.borrow_mut().pop_front() { + if let Some(r) = Rc::get_mut(&mut msg) { + r.reset(); + } + Message { + inner: msg, + pool: self, + } + } else { + Message { + inner: Rc::new(MessageInner::default()), + pool: self, + } + } + } + #[inline] /// Release request instance - pub(crate) fn release(&self, msg: Rc>) { + fn release(&self, msg: Rc>) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { v.push_front(msg); diff --git a/src/request.rs b/src/request.rs index 519cc38ed..0064de4e0 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,6 +1,5 @@ use std::cell::{Ref, RefCell, RefMut}; use std::fmt; -use std::rc::Rc; use bytes::Bytes; use futures::Stream; @@ -9,13 +8,13 @@ use http::{header, HeaderMap, Method, Uri, Version}; use crate::error::PayloadError; use crate::extensions::Extensions; use crate::httpmessage::HttpMessage; -use crate::message::{Message, MessagePool, RequestHead}; +use crate::message::{Message, RequestHead}; use crate::payload::Payload; /// Request pub struct Request

    { pub(crate) payload: RefCell>, - pub(crate) inner: Rc>, + pub(crate) inner: Message, } impl

    HttpMessage for Request

    @@ -25,7 +24,7 @@ where type Stream = P; fn headers(&self) -> &HeaderMap { - &self.inner.head.headers + &self.head().headers } #[inline] @@ -34,12 +33,21 @@ where } } +impl From> for Request { + fn from(msg: Message) -> Self { + Request { + payload: RefCell::new(None), + inner: msg, + } + } +} + impl Request { /// Create new Request instance pub fn new() -> Request { Request { payload: RefCell::new(None), - inner: MessagePool::get_message(), + inner: Message::new(), } } } @@ -49,7 +57,7 @@ impl Request { pub fn with_payload(payload: Payload) -> Request { Request { payload: RefCell::new(Some(payload.into())), - inner: MessagePool::get_message(), + inner: Message::new(), } } @@ -60,123 +68,90 @@ impl Request { { Request { payload: RefCell::new(Some(payload.into())), - inner: self.inner.clone(), + inner: self.inner, } } - /// Take request's payload - pub fn take_payload(mut self) -> (Option, Request<()>) { - ( - self.payload.get_mut().take(), - Request { - payload: RefCell::new(None), - inner: self.inner.clone(), - }, - ) - } - - // /// Create new Request instance with pool - // pub(crate) fn with_pool(pool: &'static MessagePool) -> Request { - // Request { - // inner: Rc::new(Message { - // pool, - // url: Url::default(), - // head: RequestHead::default(), - // status: StatusCode::OK, - // flags: Cell::new(MessageFlags::empty()), - // payload: RefCell::new(None), - // extensions: RefCell::new(Extensions::new()), - // }), - // } - // } - - #[inline] - #[doc(hidden)] - pub fn inner(&self) -> &Message { - self.inner.as_ref() - } - - #[inline] - #[doc(hidden)] - pub fn inner_mut(&mut self) -> &mut Message { - Rc::get_mut(&mut self.inner).expect("Multiple copies exist") + /// Split request into request head and payload + pub fn into_parts(mut self) -> (Message, Option) { + (self.inner, self.payload.get_mut().take()) } #[inline] /// Http message part of the request pub fn head(&self) -> &RequestHead { - &self.inner.as_ref().head + &*self.inner } #[inline] #[doc(hidden)] /// Mutable reference to a http message part of the request pub fn head_mut(&mut self) -> &mut RequestHead { - &mut self.inner_mut().head + &mut *self.inner } /// Request's uri. #[inline] pub fn uri(&self) -> &Uri { - &self.inner().head.uri + &self.head().uri } /// Mutable reference to the request's uri. #[inline] pub fn uri_mut(&mut self) -> &mut Uri { - &mut self.inner_mut().head.uri + &mut self.head_mut().uri } /// Read the Request method. #[inline] pub fn method(&self) -> &Method { - &self.inner().head.method + &self.head().method } /// Read the Request Version. #[inline] pub fn version(&self) -> Version { - self.inner().head.version + self.head().version } /// The target path of this Request. #[inline] pub fn path(&self) -> &str { - self.inner().head.uri.path() + self.head().uri.path() } #[inline] /// Returns Request's headers. pub fn headers(&self) -> &HeaderMap { - &self.inner().head.headers + &self.head().headers } #[inline] /// Returns mutable Request's headers. pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.inner_mut().head.headers + &mut self.head_mut().headers } /// Request extensions #[inline] pub fn extensions(&self) -> Ref { - self.inner().extensions.borrow() + self.inner.extensions() } /// Mutable reference to a the request's extensions #[inline] pub fn extensions_mut(&self) -> RefMut { - self.inner().extensions.borrow_mut() + self.inner.extensions_mut() } /// Check if request requires connection upgrade pub fn upgrade(&self) -> bool { - if let Some(conn) = self.inner().head.headers.get(header::CONNECTION) { + if let Some(conn) = self.head().headers.get(header::CONNECTION) { if let Ok(s) = conn.to_str() { return s.to_lowercase().contains("upgrade"); } } - self.inner().head.method == Method::CONNECT + self.head().method == Method::CONNECT } // #[doc(hidden)] @@ -189,14 +164,6 @@ impl Request { // } } -impl Drop for Request { - fn drop(&mut self) { - if Rc::strong_count(&self.inner) == 1 { - self.inner.pool.release(self.inner.clone()); - } - } -} - impl fmt::Debug for Request { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!( diff --git a/src/response.rs b/src/response.rs index a4b65f2b4..d84100fa2 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,7 +1,4 @@ -#![allow(dead_code)] //! Http response -use std::cell::RefCell; -use std::collections::VecDeque; use std::io::Write; use std::{fmt, str}; @@ -9,26 +6,27 @@ use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; use futures::Stream; use http::header::{self, HeaderName, HeaderValue}; -use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version}; +use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode}; use serde::Serialize; use serde_json; use crate::body::{Body, BodyStream, MessageBody, ResponseBody}; use crate::error::Error; use crate::header::{Header, IntoHeaderValue}; -use crate::message::{ConnectionType, Head, ResponseHead}; - -/// max write buffer size 64k -pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; +use crate::message::{ConnectionType, Head, Message, ResponseHead}; /// An HTTP Response -pub struct Response(Box, ResponseBody); +pub struct Response { + head: Message, + body: ResponseBody, + error: Option, +} impl Response { /// Create http response builder with specific status. #[inline] pub fn build(status: StatusCode) -> ResponseBuilder { - ResponsePool::get(status) + ResponseBuilder::new(status) } /// Create http response builder @@ -40,14 +38,21 @@ impl Response { /// Constructs a response #[inline] pub fn new(status: StatusCode) -> Response { - ResponsePool::with_body(status, Body::Empty) + let mut head: Message = Message::new(); + head.status = status; + + Response { + head, + body: ResponseBody::Body(Body::Empty), + error: None, + } } /// Constructs an error response #[inline] pub fn from_error(error: Error) -> Response { let mut resp = error.as_response_error().error_response(); - resp.get_mut().error = Some(error); + resp.error = Some(error); resp } @@ -67,7 +72,7 @@ impl Response { } ResponseBuilder { - response: Some(self.0), + head: Some(self.head), err: None, cookies: jar, } @@ -75,90 +80,85 @@ impl Response { /// Convert response to response with body pub fn into_body(self) -> Response { - let b = match self.1 { + let b = match self.body { ResponseBody::Body(b) => b, ResponseBody::Other(b) => b, }; - Response(self.0, ResponseBody::Other(b)) + Response { + head: self.head, + error: self.error, + body: ResponseBody::Other(b), + } } } impl Response { - #[inline] - fn get_ref(&self) -> &InnerResponse { - self.0.as_ref() - } - - #[inline] - fn get_mut(&mut self) -> &mut InnerResponse { - self.0.as_mut() - } - #[inline] /// Http message part of the response pub fn head(&self) -> &ResponseHead { - &self.0.as_ref().head + &*self.head } #[inline] /// Mutable reference to a http message part of the response pub fn head_mut(&mut self) -> &mut ResponseHead { - &mut self.0.as_mut().head + &mut *self.head } /// Constructs a response with body #[inline] pub fn with_body(status: StatusCode, body: B) -> Response { - ResponsePool::with_body(status, body) + let mut head: Message = Message::new(); + head.status = status; + Response { + head, + body: ResponseBody::Body(body), + error: None, + } } /// The source `error` for this response #[inline] pub fn error(&self) -> Option<&Error> { - self.get_ref().error.as_ref() + self.error.as_ref() } /// Get the response status code #[inline] pub fn status(&self) -> StatusCode { - self.get_ref().head.status + self.head.status } /// Set the `StatusCode` for this response #[inline] pub fn status_mut(&mut self) -> &mut StatusCode { - &mut self.get_mut().head.status + &mut self.head.status } /// Get the headers from the response #[inline] pub fn headers(&self) -> &HeaderMap { - &self.get_ref().head.headers + &self.head.headers } /// Get a mutable reference to the headers #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.get_mut().head.headers + &mut self.head.headers } /// Get an iterator for the cookies set by this response #[inline] pub fn cookies(&self) -> CookieIter { CookieIter { - iter: self - .get_ref() - .head - .headers - .get_all(header::SET_COOKIE) - .iter(), + iter: self.head.headers.get_all(header::SET_COOKIE).iter(), } } /// Add a cookie to this response #[inline] pub fn add_cookie(&mut self, cookie: &Cookie) -> Result<(), HttpError> { - let h = &mut self.get_mut().head.headers; + let h = &mut self.head.headers; HeaderValue::from_str(&cookie.to_string()) .map(|c| { h.append(header::SET_COOKIE, c); @@ -170,7 +170,7 @@ impl Response { /// the number of cookies removed. #[inline] pub fn del_cookie(&mut self, name: &str) -> usize { - let h = &mut self.get_mut().head.headers; + let h = &mut self.head.headers; let vals: Vec = h .get_all(header::SET_COOKIE) .iter() @@ -196,28 +196,36 @@ impl Response { /// Connection upgrade status #[inline] pub fn upgrade(&self) -> bool { - self.get_ref().head.upgrade() + self.head.upgrade() } /// Keep-alive status for this connection pub fn keep_alive(&self) -> bool { - self.get_ref().head.keep_alive() + self.head.keep_alive() } /// Get body os this response #[inline] - pub(crate) fn body(&self) -> &ResponseBody { - &self.1 + pub fn body(&self) -> &ResponseBody { + &self.body } /// Set a body pub(crate) fn set_body(self, body: B2) -> Response { - Response(self.0, ResponseBody::Body(body)) + Response { + head: self.head, + body: ResponseBody::Body(body), + error: None, + } } /// Drop request's body pub(crate) fn drop_body(self) -> Response<()> { - Response(self.0, ResponseBody::Body(())) + Response { + head: self.head, + body: ResponseBody::Body(()), + error: None, + } } /// Set a body and return previous body value @@ -225,21 +233,14 @@ impl Response { self, body: B2, ) -> (Response, ResponseBody) { - (Response(self.0, ResponseBody::Body(body)), self.1) - } - - /// Size of response in bytes, excluding HTTP headers - pub fn response_size(&self) -> u64 { - self.get_ref().response_size - } - - /// Set response size - pub(crate) fn set_response_size(&mut self, size: u64) { - self.get_mut().response_size = size; - } - - pub(crate) fn release(self) { - ResponsePool::release(self.0); + ( + Response { + head: self.head, + body: ResponseBody::Body(body), + error: self.error, + }, + self.body, + ) } } @@ -248,15 +249,15 @@ impl fmt::Debug for Response { let res = writeln!( f, "\nResponse {:?} {}{}", - self.get_ref().head.version, - self.get_ref().head.status, - self.get_ref().head.reason.unwrap_or(""), + self.head.version, + self.head.status, + self.head.reason.unwrap_or(""), ); let _ = writeln!(f, " headers:"); - for (key, val) in self.get_ref().head.headers.iter() { + for (key, val) in self.head.headers.iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); } - let _ = writeln!(f, " body: {:?}", self.1.length()); + let _ = writeln!(f, " body: {:?}", self.body.length()); res } } @@ -284,17 +285,29 @@ impl<'a> Iterator for CookieIter<'a> { /// This type can be used to construct an instance of `Response` through a /// builder-like pattern. pub struct ResponseBuilder { - response: Option>, + head: Option>, err: Option, cookies: Option, } impl ResponseBuilder { + /// Create response builder + pub fn new(status: StatusCode) -> Self { + let mut head: Message = Message::new(); + head.status = status; + + ResponseBuilder { + head: Some(head), + err: None, + cookies: None, + } + } + /// Set HTTP status code of this response. #[inline] pub fn status(&mut self, status: StatusCode) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.head.status = status; + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.status = status; } self } @@ -316,10 +329,10 @@ impl ResponseBuilder { /// ``` #[doc(hidden)] pub fn set(&mut self, hdr: H) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match hdr.try_into() { Ok(value) => { - parts.head.headers.append(H::name(), value); + parts.headers.append(H::name(), value); } Err(e) => self.err = Some(e.into()), } @@ -346,11 +359,11 @@ impl ResponseBuilder { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.response, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match HeaderName::try_from(key) { Ok(key) => match value.try_into() { Ok(value) => { - parts.head.headers.append(key, value); + parts.headers.append(key, value); } Err(e) => self.err = Some(e.into()), }, @@ -379,11 +392,11 @@ impl ResponseBuilder { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.response, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match HeaderName::try_from(key) { Ok(key) => match value.try_into() { Ok(value) => { - parts.head.headers.insert(key, value); + parts.headers.insert(key, value); } Err(e) => self.err = Some(e.into()), }, @@ -396,8 +409,8 @@ impl ResponseBuilder { /// Set the custom reason for the response. #[inline] pub fn reason(&mut self, reason: &'static str) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.head.reason = Some(reason); + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.reason = Some(reason); } self } @@ -405,8 +418,8 @@ impl ResponseBuilder { /// Set connection type to KeepAlive #[inline] pub fn keep_alive(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.head.set_connection_type(ConnectionType::KeepAlive); + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_connection_type(ConnectionType::KeepAlive); } self } @@ -417,8 +430,8 @@ impl ResponseBuilder { where V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.head.set_connection_type(ConnectionType::Upgrade); + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_connection_type(ConnectionType::Upgrade); } self.set_header(header::UPGRADE, value) } @@ -426,8 +439,8 @@ impl ResponseBuilder { /// Force close connection, even if it is marked as keep-alive #[inline] pub fn force_close(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.head.set_connection_type(ConnectionType::Close); + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_connection_type(ConnectionType::Close); } self } @@ -438,10 +451,10 @@ impl ResponseBuilder { where HeaderValue: HttpTryFrom, { - if let Some(parts) = parts(&mut self.response, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match HeaderValue::try_from(value) { Ok(value) => { - parts.head.headers.insert(header::CONTENT_TYPE, value); + parts.headers.insert(header::CONTENT_TYPE, value); } Err(e) => self.err = Some(e.into()), }; @@ -540,20 +553,6 @@ impl ResponseBuilder { self } - // /// Set write buffer capacity - // /// - // /// This parameter makes sense only for streaming response - // /// or actor. If write buffer reaches specified capacity, stream or actor - // /// get paused. - // /// - // /// Default write buffer capacity is 64kb - // pub fn write_buffer_capacity(&mut self, cap: usize) -> &mut Self { - // if let Some(parts) = parts(&mut self.response, &self.err) { - // parts.write_capacity = cap; - // } - // self - // } - /// Set a body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. @@ -569,19 +568,23 @@ impl ResponseBuilder { return Response::from(Error::from(e)).into_body(); } - let mut response = self.response.take().expect("cannot reuse response builder"); + let mut response = self.head.take().expect("cannot reuse response builder"); if let Some(ref jar) = self.cookies { for cookie in jar.delta() { match HeaderValue::from_str(&cookie.to_string()) { Ok(val) => { - let _ = response.head.headers.append(header::SET_COOKIE, val); + let _ = response.headers.append(header::SET_COOKIE, val); } Err(e) => return Response::from(Error::from(e)).into_body(), }; } } - Response(response, ResponseBody::Body(body)) + Response { + head: response, + body: ResponseBody::Body(body), + error: None, + } } #[inline] @@ -609,9 +612,8 @@ impl ResponseBuilder { pub fn json2(&mut self, value: &T) -> Response { match serde_json::to_string(value) { Ok(body) => { - let contains = if let Some(parts) = parts(&mut self.response, &self.err) - { - parts.head.headers.contains_key(header::CONTENT_TYPE) + let contains = if let Some(parts) = parts(&mut self.head, &self.err) { + parts.headers.contains_key(header::CONTENT_TYPE) } else { true }; @@ -636,7 +638,7 @@ impl ResponseBuilder { /// This method construct new `ResponseBuilder` pub fn take(&mut self) -> ResponseBuilder { ResponseBuilder { - response: self.response.take(), + head: self.head.take(), err: self.err.take(), cookies: self.cookies.take(), } @@ -646,9 +648,9 @@ impl ResponseBuilder { #[inline] #[allow(clippy::borrowed_box)] fn parts<'a>( - parts: &'a mut Option>, + parts: &'a mut Option>, err: &Option, -) -> Option<&'a mut Box> { +) -> Option<&'a mut Message> { if err.is_some() { return None; } @@ -719,107 +721,6 @@ impl From for Response { } } -struct InnerResponse { - head: ResponseHead, - response_size: u64, - error: Option, - pool: &'static ResponsePool, -} - -impl InnerResponse { - #[inline] - fn new(status: StatusCode, pool: &'static ResponsePool) -> InnerResponse { - InnerResponse { - head: ResponseHead { - status, - version: Version::default(), - headers: HeaderMap::with_capacity(16), - reason: None, - ctype: None, - }, - pool, - response_size: 0, - error: None, - } - } -} - -/// Internal use only! -pub(crate) struct ResponsePool(RefCell>>); - -thread_local!(static POOL: &'static ResponsePool = ResponsePool::pool()); - -impl ResponsePool { - fn pool() -> &'static ResponsePool { - let pool = ResponsePool(RefCell::new(VecDeque::with_capacity(128))); - Box::leak(Box::new(pool)) - } - - pub fn get_pool() -> &'static ResponsePool { - POOL.with(|p| *p) - } - - #[inline] - pub fn get_builder( - pool: &'static ResponsePool, - status: StatusCode, - ) -> ResponseBuilder { - if let Some(mut msg) = pool.0.borrow_mut().pop_front() { - msg.head.status = status; - ResponseBuilder { - response: Some(msg), - err: None, - cookies: None, - } - } else { - let msg = Box::new(InnerResponse::new(status, pool)); - ResponseBuilder { - response: Some(msg), - err: None, - cookies: None, - } - } - } - - #[inline] - pub fn get_response( - pool: &'static ResponsePool, - status: StatusCode, - body: B, - ) -> Response { - if let Some(mut msg) = pool.0.borrow_mut().pop_front() { - msg.head.status = status; - Response(msg, ResponseBody::Body(body)) - } else { - Response( - Box::new(InnerResponse::new(status, pool)), - ResponseBody::Body(body), - ) - } - } - - #[inline] - fn get(status: StatusCode) -> ResponseBuilder { - POOL.with(|pool| ResponsePool::get_builder(pool, status)) - } - - #[inline] - fn with_body(status: StatusCode, body: B) -> Response { - POOL.with(|pool| ResponsePool::get_response(pool, status, body)) - } - - #[inline] - fn release(mut inner: Box) { - let mut p = inner.pool.0.borrow_mut(); - if p.len() < 128 { - inner.head.clear(); - inner.response_size = 0; - inner.error = None; - p.push_front(inner); - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/test.rs b/src/test.rs index cd160e60a..b0e308728 100644 --- a/src/test.rs +++ b/src/test.rs @@ -154,11 +154,11 @@ impl TestRequest { Request::with_payload(crate::h1::Payload::empty().into()) }; - let inner = req.inner_mut(); - inner.head.uri = uri; - inner.head.method = method; - inner.head.version = version; - inner.head.headers = headers; + let head = req.head_mut(); + head.uri = uri; + head.method = method; + head.version = version; + head.headers = headers; // req.set_cookies(cookies); req From ed7ca7fe079ef6e9ee419bafe54e517657846041 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Feb 2019 21:50:20 -0800 Subject: [PATCH 128/208] make Message clonable and expose as public --- Cargo.toml | 2 +- src/lib.rs | 1 + src/message.rs | 9 +++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ea246762e..f940ae86c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ documentation = "https://actix.rs/api/actix-http/stable/actix_http/" categories = ["network-programming", "asynchronous", "web-programming::http-server", "web-programming::websocket"] -license = "MIT/Apache-2.0" +license = "Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" diff --git a/src/lib.rs b/src/lib.rs index f34da84c1..932b54852 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,6 +94,7 @@ pub use self::config::{KeepAlive, ServiceConfig, ServiceConfigBuilder}; pub use self::error::{Error, ResponseError, Result}; pub use self::extensions::Extensions; pub use self::httpmessage::HttpMessage; +pub use self::message::{Message, RequestHead, ResponseHead}; pub use self::request::Request; pub use self::response::Response; pub use self::service::{SendError, SendResponse}; diff --git a/src/message.rs b/src/message.rs index 08edeef38..12da9eaf1 100644 --- a/src/message.rs +++ b/src/message.rs @@ -169,6 +169,15 @@ impl Message { } } +impl Clone for Message { + fn clone(&self) -> Self { + Message { + inner: self.inner.clone(), + pool: self.pool, + } + } +} + impl std::ops::Deref for Message { type Target = T; From f3ed1b601ea6833fa20efae4d47f0251e6543b3a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Feb 2019 08:44:22 -0800 Subject: [PATCH 129/208] Change service response to Into --- src/h1/dispatcher.rs | 13 ++++++++----- src/h1/service.rs | 15 ++++++++++----- src/h2/dispatcher.rs | 14 +++++++++----- src/h2/service.rs | 21 ++++++++++++++------- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 0a0ed04e2..667e674c5 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -85,8 +85,9 @@ impl State { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service, S::Error: Debug, + S::Response: Into>, B: MessageBody, { /// Create http/1 dispatcher. @@ -139,8 +140,9 @@ where impl InnerDispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service, S::Error: Debug, + S::Response: Into>, B: MessageBody, { fn can_read(&self) -> bool { @@ -224,7 +226,7 @@ where State::ServiceCall(mut fut) => { match fut.poll().map_err(DispatchError::Service)? { Async::Ready(res) => { - let (res, body) = res.replace_body(()); + let (res, body) = res.into().replace_body(()); Some(self.send_response(res, body)?) } Async::NotReady => { @@ -287,7 +289,7 @@ where let mut task = self.service.call(req); match task.poll().map_err(DispatchError::Service)? { Async::Ready(res) => { - let (res, body) = res.replace_body(()); + let (res, body) = res.into().replace_body(()); self.send_response(res, body) } Async::NotReady => Ok(State::ServiceCall(task)), @@ -459,8 +461,9 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service, S::Error: Debug, + S::Response: Into>, B: MessageBody, { type Item = H1ServiceResult; diff --git a/src/h1/service.rs b/src/h1/service.rs index fbc0a2f0f..02a4b15d6 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -28,9 +28,10 @@ pub struct H1Service { impl H1Service where - S: NewService, Response = Response> + Clone, + S: NewService> + Clone, S::Service: Clone, S::Error: Debug, + S::Response: Into>, B: MessageBody, { /// Create new `HttpService` instance. @@ -53,9 +54,10 @@ where impl NewService for H1Service where T: AsyncRead + AsyncWrite, - S: NewService> + Clone, + S: NewService + Clone, S::Service: Clone, S::Error: Debug, + S::Response: Into>, B: MessageBody, { type Request = T; @@ -214,9 +216,10 @@ pub struct H1ServiceResponse { impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService>, + S: NewService, S::Service: Clone, S::Error: Debug, + S::Response: Into>, B: MessageBody, { type Item = H1ServiceHandler; @@ -240,8 +243,9 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where - S: Service> + Clone, + S: Service + Clone, S::Error: Debug, + S::Response: Into>, B: MessageBody, { fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { @@ -256,8 +260,9 @@ where impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service> + Clone, + S: Service + Clone, S::Error: Debug, + S::Response: Into>, B: MessageBody, { type Request = T; diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index 001acc560..b7339fa1d 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -50,8 +50,9 @@ pub struct Dispatcher { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, Response = Response> + 'static, + S: Service> + 'static, S::Error: Into + fmt::Debug, + S::Response: Into>, B: MessageBody + 'static, { pub fn new( @@ -91,8 +92,9 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, Response = Response> + 'static, + S: Service> + 'static, S::Error: Into + fmt::Debug, + S::Response: Into>, B: MessageBody + 'static, { type Item = (); @@ -149,8 +151,9 @@ enum ServiceResponseState { impl ServiceResponse where - S: Service, Response = Response> + 'static, + S: Service> + 'static, S::Error: Into + fmt::Debug, + S::Response: Into>, B: MessageBody + 'static, { fn prepare_response( @@ -216,8 +219,9 @@ where impl Future for ServiceResponse where - S: Service, Response = Response> + 'static, + S: Service> + 'static, S::Error: Into + fmt::Debug, + S::Response: Into>, B: MessageBody + 'static, { type Item = (); @@ -228,7 +232,7 @@ where ServiceResponseState::ServiceCall(ref mut call, ref mut send) => { match call.poll() { Ok(Async::Ready(res)) => { - let (res, body) = res.replace_body(()); + let (res, body) = res.into().replace_body(()); let mut send = send.take().unwrap(); let mut length = body.length(); diff --git a/src/h2/service.rs b/src/h2/service.rs index 5759b55e2..a1526375f 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -30,9 +30,10 @@ pub struct H2Service { impl H2Service where - S: NewService, Response = Response> + Clone, + S: NewService> + Clone, S::Service: Clone + 'static, S::Error: Into + Debug + 'static, + S::Response: Into>, B: MessageBody + 'static, { /// Create new `HttpService` instance. @@ -55,9 +56,10 @@ where impl NewService for H2Service where T: AsyncRead + AsyncWrite, - S: NewService, Response = Response> + Clone, + S: NewService> + Clone, S::Service: Clone + 'static, S::Error: Into + Debug, + S::Response: Into>, B: MessageBody + 'static, { type Request = T; @@ -235,8 +237,9 @@ pub struct H2ServiceResponse { impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService, Response = Response>, + S: NewService>, S::Service: Clone + 'static, + S::Response: Into>, S::Error: Into + Debug, B: MessageBody + 'static, { @@ -261,8 +264,9 @@ pub struct H2ServiceHandler { impl H2ServiceHandler where - S: Service, Response = Response> + Clone + 'static, + S: Service> + Clone + 'static, S::Error: Into + Debug, + S::Response: Into>, B: MessageBody + 'static, { fn new(cfg: ServiceConfig, srv: S) -> H2ServiceHandler { @@ -277,8 +281,9 @@ where impl Service for H2ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service, Response = Response> + Clone + 'static, + S: Service> + Clone + 'static, S::Error: Into + Debug, + S::Response: Into>, B: MessageBody + 'static, { type Request = T; @@ -312,8 +317,9 @@ enum State { pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service, Response = Response> + Clone + 'static, + S: Service> + Clone + 'static, S::Error: Into + Debug, + S::Response: Into>, B: MessageBody + 'static, { state: State, @@ -322,8 +328,9 @@ where impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service, Response = Response> + Clone, + S: Service> + Clone, S::Error: Into + Debug, + S::Response: Into>, B: MessageBody, { type Item = (); From 6a343fae06c90aacd7c843d28c742a52cd7bc9f6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Feb 2019 10:33:49 -0800 Subject: [PATCH 130/208] simplify Message type --- src/message.rs | 76 +++++++++++++++++++------------------------------- 1 file changed, 28 insertions(+), 48 deletions(-) diff --git a/src/message.rs b/src/message.rs index 12da9eaf1..7fb45bcc5 100644 --- a/src/message.rs +++ b/src/message.rs @@ -45,6 +45,7 @@ pub struct RequestHead { pub version: Version, pub headers: HeaderMap, pub ctype: Option, + pub extensions: RefCell, } impl Default for RequestHead { @@ -55,6 +56,7 @@ impl Default for RequestHead { version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), ctype: None, + extensions: RefCell::new(Extensions::new()), } } } @@ -63,6 +65,7 @@ impl Head for RequestHead { fn clear(&mut self) { self.ctype = None; self.headers.clear(); + self.extensions.borrow_mut().clear(); } fn set_connection_type(&mut self, ctype: ConnectionType) { @@ -84,6 +87,20 @@ impl Head for RequestHead { } } +impl RequestHead { + /// Message extensions + #[inline] + pub fn extensions(&self) -> Ref { + self.extensions.borrow() + } + + /// Mutable reference to a the message's extensions + #[inline] + pub fn extensions_mut(&self) -> RefMut { + self.extensions.borrow_mut() + } +} + #[derive(Debug)] pub struct ResponseHead { pub version: Version, @@ -146,7 +163,7 @@ impl ResponseHead { } pub struct Message { - inner: Rc>, + head: Rc, pool: &'static MessagePool, } @@ -155,24 +172,12 @@ impl Message { pub fn new() -> Self { T::pool().get_message() } - - /// Message extensions - #[inline] - pub fn extensions(&self) -> Ref { - self.inner.as_ref().extensions.borrow() - } - - /// Mutable reference to a the message's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut { - self.inner.as_ref().extensions.borrow_mut() - } } impl Clone for Message { fn clone(&self) -> Self { Message { - inner: self.inner.clone(), + head: self.head.clone(), pool: self.pool, } } @@ -182,52 +187,27 @@ impl std::ops::Deref for Message { type Target = T; fn deref(&self) -> &Self::Target { - &self.inner.as_ref().head + &self.head.as_ref() } } impl std::ops::DerefMut for Message { fn deref_mut(&mut self) -> &mut Self::Target { - &mut Rc::get_mut(&mut self.inner) - .expect("Multiple copies exist") - .head + Rc::get_mut(&mut self.head).expect("Multiple copies exist") } } impl Drop for Message { fn drop(&mut self) { - if Rc::strong_count(&self.inner) == 1 { - self.pool.release(self.inner.clone()); - } - } -} - -struct MessageInner { - head: T, - extensions: RefCell, -} - -impl MessageInner { - #[inline] - /// Reset request instance - pub fn reset(&mut self) { - self.head.clear(); - self.extensions.borrow_mut().clear(); - } -} - -impl Default for MessageInner { - fn default() -> Self { - MessageInner { - head: T::default(), - extensions: RefCell::new(Extensions::new()), + if Rc::strong_count(&self.head) == 1 { + self.pool.release(self.head.clone()); } } } #[doc(hidden)] /// Request's objects pool -pub struct MessagePool(RefCell>>>); +pub struct MessagePool(RefCell>>); thread_local!(static REQUEST_POOL: &'static MessagePool = MessagePool::::create()); thread_local!(static RESPONSE_POOL: &'static MessagePool = MessagePool::::create()); @@ -243,15 +223,15 @@ impl MessagePool { fn get_message(&'static self) -> Message { if let Some(mut msg) = self.0.borrow_mut().pop_front() { if let Some(r) = Rc::get_mut(&mut msg) { - r.reset(); + r.clear(); } Message { - inner: msg, + head: msg, pool: self, } } else { Message { - inner: Rc::new(MessageInner::default()), + head: Rc::new(T::default()), pool: self, } } @@ -259,7 +239,7 @@ impl MessagePool { #[inline] /// Release request instance - fn release(&self, msg: Rc>) { + fn release(&self, msg: Rc) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { v.push_front(msg); From a66d8589c2fd0048680d68945a47ab20fd3f3938 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Feb 2019 10:45:35 -0800 Subject: [PATCH 131/208] add Extensions::contains method --- src/extensions.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/extensions.rs b/src/extensions.rs index f7805641b..148e4c18e 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -26,6 +26,11 @@ impl Extensions { self.map.insert(TypeId::of::(), Box::new(val)); } + /// Check if container contains entry + pub fn contains(&self) -> bool { + self.map.get(&TypeId::of::()).is_some() + } + /// Get a reference to a type previously inserted on this `Extensions`. pub fn get(&self) -> Option<&T> { self.map From 1af149b9e61090556b3df98e21e11930dc9c337d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Feb 2019 20:27:39 -0800 Subject: [PATCH 132/208] remove Clone constraint from handler service --- src/h1/dispatcher.rs | 15 ++++++++------- src/h1/service.rs | 22 +++++++++++----------- src/h2/dispatcher.rs | 7 ++++--- src/h2/service.rs | 35 ++++++++++++++++++++--------------- src/request.rs | 9 --------- 5 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 667e674c5..06d4312a4 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -5,6 +5,7 @@ use std::time::Instant; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::Service; +use actix_utils::cloneable::CloneableService; use bitflags::bitflags; use futures::{try_ready, Async, Future, Poll, Sink, Stream}; use log::{debug, error, trace}; @@ -35,18 +36,18 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher +pub struct Dispatcher where S::Error: Debug, { inner: Option>, } -struct InnerDispatcher +struct InnerDispatcher where S::Error: Debug, { - service: S, + service: CloneableService, flags: Flags, framed: Framed, error: Option>, @@ -85,13 +86,13 @@ impl State { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, { /// Create http/1 dispatcher. - pub fn new(stream: T, config: ServiceConfig, service: S) -> Self { + pub fn new(stream: T, config: ServiceConfig, service: CloneableService) -> Self { Dispatcher::with_timeout(stream, config, None, service) } @@ -100,7 +101,7 @@ where stream: T, config: ServiceConfig, timeout: Option, - service: S, + service: CloneableService, ) -> Self { let keepalive = config.keep_alive_enabled(); let flags = if keepalive { @@ -140,7 +141,7 @@ where impl InnerDispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, diff --git a/src/h1/service.rs b/src/h1/service.rs index 02a4b15d6..169a80ebc 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -4,6 +4,7 @@ use std::net; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::{IntoNewService, NewService, Service}; +use actix_utils::cloneable::CloneableService; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, Poll, Stream}; use log::error; @@ -28,10 +29,10 @@ pub struct H1Service { impl H1Service where - S: NewService> + Clone, - S::Service: Clone, + S: NewService>, S::Error: Debug, S::Response: Into>, + S::Service: 'static, B: MessageBody, { /// Create new `HttpService` instance. @@ -54,10 +55,10 @@ where impl NewService for H1Service where T: AsyncRead + AsyncWrite, - S: NewService + Clone, - S::Service: Clone, + S: NewService, S::Error: Debug, S::Response: Into>, + S::Service: 'static, B: MessageBody, { type Request = T; @@ -93,7 +94,6 @@ pub struct H1ServiceBuilder { impl H1ServiceBuilder where S: NewService, - S::Service: Clone, S::Error: Debug, { /// Create instance of `ServiceConfigBuilder` @@ -217,7 +217,7 @@ impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, S: NewService, - S::Service: Clone, + S::Service: 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, @@ -235,22 +235,22 @@ where } /// `Service` implementation for HTTP1 transport -pub struct H1ServiceHandler { - srv: S, +pub struct H1ServiceHandler { + srv: CloneableService, cfg: ServiceConfig, _t: PhantomData<(T, B)>, } impl H1ServiceHandler where - S: Service + Clone, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, { fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { H1ServiceHandler { - srv, + srv: CloneableService::new(srv), cfg, _t: PhantomData, } @@ -260,7 +260,7 @@ where impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service + Clone, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index b7339fa1d..5a8c4b855 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -5,6 +5,7 @@ use std::{fmt, mem}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::Service; +use actix_utils::cloneable::CloneableService; use bitflags::bitflags; use bytes::{Bytes, BytesMut}; use futures::{try_ready, Async, Future, Poll, Sink, Stream}; @@ -37,9 +38,9 @@ bitflags! { } /// Dispatcher for HTTP/2 protocol -pub struct Dispatcher { +pub struct Dispatcher { flags: Flags, - service: S, + service: CloneableService, connection: Connection, config: ServiceConfig, ka_expire: Instant, @@ -56,7 +57,7 @@ where B: MessageBody + 'static, { pub fn new( - service: S, + service: CloneableService, connection: Connection, config: ServiceConfig, timeout: Option, diff --git a/src/h2/service.rs b/src/h2/service.rs index a1526375f..16b7e4956 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -4,6 +4,7 @@ use std::{io, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::{IntoNewService, NewService, Service}; +use actix_utils::cloneable::CloneableService; use bytes::Bytes; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, Poll, Stream}; @@ -30,8 +31,8 @@ pub struct H2Service { impl H2Service where - S: NewService> + Clone, - S::Service: Clone + 'static, + S: NewService>, + S::Service: 'static, S::Error: Into + Debug + 'static, S::Response: Into>, B: MessageBody + 'static, @@ -56,8 +57,8 @@ where impl NewService for H2Service where T: AsyncRead + AsyncWrite, - S: NewService> + Clone, - S::Service: Clone + 'static, + S: NewService>, + S::Service: 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody + 'static, @@ -95,7 +96,7 @@ pub struct H2ServiceBuilder { impl H2ServiceBuilder where S: NewService>, - S::Service: Clone + 'static, + S::Service: 'static, S::Error: Into + Debug + 'static, { /// Create instance of `H2ServiceBuilder` @@ -238,7 +239,7 @@ impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite, S: NewService>, - S::Service: Clone + 'static, + S::Service: 'static, S::Response: Into>, S::Error: Into + Debug, B: MessageBody + 'static, @@ -256,23 +257,23 @@ where } /// `Service` implementation for http/2 transport -pub struct H2ServiceHandler { - srv: S, +pub struct H2ServiceHandler { + srv: CloneableService, cfg: ServiceConfig, _t: PhantomData<(T, B)>, } impl H2ServiceHandler where - S: Service> + Clone + 'static, + S: Service> + 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody + 'static, { fn new(cfg: ServiceConfig, srv: S) -> H2ServiceHandler { H2ServiceHandler { - srv, cfg, + srv: CloneableService::new(srv), _t: PhantomData, } } @@ -281,7 +282,7 @@ where impl Service for H2ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service> + Clone + 'static, + S: Service> + 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody + 'static, @@ -309,15 +310,19 @@ where } } -enum State { +enum State { Incoming(Dispatcher), - Handshake(Option, Option, Handshake), + Handshake( + Option>, + Option, + Handshake, + ), } pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service> + Clone + 'static, + S: Service> + 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody + 'static, @@ -328,7 +333,7 @@ where impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service> + Clone, + S: Service> + 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody, diff --git a/src/request.rs b/src/request.rs index 0064de4e0..b9c35c7ac 100644 --- a/src/request.rs +++ b/src/request.rs @@ -153,15 +153,6 @@ impl Request { } self.head().method == Method::CONNECT } - - // #[doc(hidden)] - // /// Note: this method should be called only as part of clone operation - // /// of wrapper type. - // pub fn clone_request(&self) -> Self { - // Request { - // inner: self.inner.clone(), - // } - // } } impl fmt::Debug for Request { From e178db7f74bffad6fa562044c507083c634a3956 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Feb 2019 21:32:44 -0800 Subject: [PATCH 133/208] fix test --- src/h1/dispatcher.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 06d4312a4..6a1762747 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -599,7 +599,9 @@ mod tests { let mut h1 = Dispatcher::new( buf, ServiceConfig::default(), - (|req| ok::<_, Error>(Response::Ok().finish())).into_service(), + CloneableService::new( + (|req| ok::<_, Error>(Response::Ok().finish())).into_service(), + ), ); assert!(h1.poll().is_ok()); assert!(h1.poll().is_ok()); From f9724fa0ec8f1482fa911f24a0ce6c3a37931385 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Feb 2019 09:54:41 -0800 Subject: [PATCH 134/208] add ErrorResponse impl for TimeoutError --- Cargo.toml | 3 +-- src/error.rs | 11 +++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f940ae86c..5bcaea7c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ ssl = ["openssl"] actix-service = "0.2.1" actix-codec = "0.1.0" actix-connector = "0.2.0" -actix-utils = "0.2.1" +actix-utils = "0.2.2" base64 = "0.10" backtrace = "0.3" @@ -80,7 +80,6 @@ openssl = { version="0.10", optional = true } actix-rt = "0.1.0" actix-server = { version="0.2", features=["ssl"] } actix-connector = { version="0.2.0", features=["ssl"] } -actix-utils = "0.2.1" actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" diff --git a/src/error.rs b/src/error.rs index f71b429fd..cd5cabaa6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,7 @@ use std::string::FromUtf8Error; use std::{fmt, io, result}; // use actix::MailboxError; +use actix_utils::timeout::TimeoutError; use backtrace::Backtrace; use cookie; use derive_more::{Display, From}; @@ -187,6 +188,16 @@ impl From for Error { // } // } +/// Return `GATEWAY_TIMEOUT` for `TimeoutError` +impl ResponseError for TimeoutError { + fn error_response(&self) -> Response { + match self { + TimeoutError::Service(e) => e.error_response(), + TimeoutError::Timeout => Response::new(StatusCode::GATEWAY_TIMEOUT), + } + } +} + /// `InternalServerError` for `JsonError` impl ResponseError for JsonError {} From 32021532c398b550ba09e639a334d09bc60e41b1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Feb 2019 09:55:29 -0800 Subject: [PATCH 135/208] export Payload type --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 932b54852..2b9d8c619 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ mod httpcodes; pub mod httpmessage; mod json; mod message; +mod payload; mod request; mod response; mod service; @@ -85,7 +86,6 @@ mod service; pub mod error; pub mod h1; pub mod h2; -pub mod payload; pub mod test; pub mod ws; @@ -95,6 +95,7 @@ pub use self::error::{Error, ResponseError, Result}; pub use self::extensions::Extensions; pub use self::httpmessage::HttpMessage; pub use self::message::{Message, RequestHead, ResponseHead}; +pub use self::payload::Payload; pub use self::request::Request; pub use self::response::Response; pub use self::service::{SendError, SendResponse}; From a41459bf698ca13c56822da1fcf7137c1a452931 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Feb 2019 11:07:42 -0800 Subject: [PATCH 136/208] make payload generic --- src/body.rs | 5 +- src/client/h1proto.rs | 5 +- src/client/h2proto.rs | 3 +- src/client/response.rs | 25 ++++----- src/h1/dispatcher.rs | 2 +- src/h1/service.rs | 4 +- src/httpmessage.rs | 117 +++++++++++++++++++---------------------- src/json.rs | 10 ++-- src/payload.rs | 41 ++++++++++++--- src/request.rs | 38 ++++++------- 10 files changed, 127 insertions(+), 123 deletions(-) diff --git a/src/body.rs b/src/body.rs index 1c54d4ce7..d3e63f9c9 100644 --- a/src/body.rs +++ b/src/body.rs @@ -4,10 +4,7 @@ use std::{fmt, mem}; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll, Stream}; -use crate::error::{Error, PayloadError}; - -/// Type represent streaming payload -pub type PayloadStream = Box>; +use crate::error::Error; #[derive(Debug, PartialEq, Copy, Clone)] /// Different type of body diff --git a/src/client/h1proto.rs b/src/client/h1proto.rs index 59a03ef48..d16491385 100644 --- a/src/client/h1proto.rs +++ b/src/client/h1proto.rs @@ -9,10 +9,11 @@ use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; use super::error::{ConnectorError, SendRequestError}; use super::pool::Acquired; use super::response::ClientResponse; -use crate::body::{BodyLength, MessageBody, PayloadStream}; +use crate::body::{BodyLength, MessageBody}; use crate::error::PayloadError; use crate::h1; use crate::message::RequestHead; +use crate::payload::PayloadStream; pub(crate) fn send_request( io: T, @@ -57,7 +58,7 @@ where release_connection(framed, force_close) } _ => { - res.set_payload(Payload::stream(framed)); + res.set_payload(Payload::stream(framed).into()); } } ok(res) diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index ecd18cf82..697d30a41 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -10,7 +10,6 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCOD use http::{request::Request, HttpTryFrom, Version}; use crate::body::{BodyLength, MessageBody}; -use crate::h2::Payload; use crate::message::{RequestHead, ResponseHead}; use super::connection::{ConnectionType, IoConnection}; @@ -111,7 +110,7 @@ where Ok(ClientResponse { head, - payload: RefCell::new(Some(Box::new(Payload::new(body)))), + payload: RefCell::new(body.into()), }) }) .from_err() diff --git a/src/client/response.rs b/src/client/response.rs index 7a83d825d..f19e2d17a 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,20 +1,19 @@ use std::cell::RefCell; -use std::fmt; +use std::{fmt, mem}; use bytes::Bytes; -use futures::{Async, Poll, Stream}; +use futures::{Poll, Stream}; use http::{HeaderMap, StatusCode, Version}; -use crate::body::PayloadStream; use crate::error::PayloadError; use crate::httpmessage::HttpMessage; use crate::message::{Head, ResponseHead}; +use crate::payload::{Payload, PayloadStream}; /// Client Response -#[derive(Default)] pub struct ClientResponse { pub(crate) head: ResponseHead, - pub(crate) payload: RefCell>, + pub(crate) payload: RefCell, } impl HttpMessage for ClientResponse { @@ -25,8 +24,8 @@ impl HttpMessage for ClientResponse { } #[inline] - fn payload(&self) -> Option { - self.payload.borrow_mut().take() + fn payload(&self) -> Payload { + mem::replace(&mut *self.payload.borrow_mut(), Payload::None) } } @@ -35,7 +34,7 @@ impl ClientResponse { pub fn new() -> ClientResponse { ClientResponse { head: ResponseHead::default(), - payload: RefCell::new(None), + payload: RefCell::new(Payload::None), } } @@ -80,8 +79,8 @@ impl ClientResponse { } /// Set response payload - pub fn set_payload(&mut self, payload: PayloadStream) { - *self.payload.get_mut() = Some(payload); + pub fn set_payload(&mut self, payload: Payload) { + *self.payload.get_mut() = payload; } } @@ -90,11 +89,7 @@ impl Stream for ClientResponse { type Error = PayloadError; fn poll(&mut self) -> Poll, Self::Error> { - if let Some(ref mut payload) = self.payload.get_mut() { - payload.poll() - } else { - Ok(Async::Ready(None)) - } + self.payload.get_mut().poll() } } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 6a1762747..c242333b9 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -316,7 +316,7 @@ where match self.framed.get_codec().message_type() { MessageType::Payload => { let (ps, pl) = Payload::create(false); - req = req.set_payload(pl); + req = req.set_payload(crate::Payload::H1(pl)); self.payload = Some(ps); } MessageType::Stream => { diff --git a/src/h1/service.rs b/src/h1/service.rs index 169a80ebc..381864498 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -12,7 +12,7 @@ use log::error; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, ParseError}; -use crate::payload::Payload; +use crate::payload::PayloadStream; use crate::request::Request; use crate::response::Response; @@ -29,7 +29,7 @@ pub struct H1Service { impl H1Service where - S: NewService>, + S: NewService>, S::Error: Debug, S::Response: Into>, S::Service: 'static, diff --git a/src/httpmessage.rs b/src/httpmessage.rs index f071cd7bc..57ad4bf95 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,3 +1,5 @@ +use std::{mem, str}; + use bytes::{Bytes, BytesMut}; use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; @@ -8,13 +10,13 @@ use http::{header, HeaderMap}; use mime::Mime; use serde::de::DeserializeOwned; use serde_urlencoded; -use std::str; use crate::error::{ ContentTypeError, ParseError, PayloadError, ReadlinesError, UrlencodedError, }; use crate::header::Header; use crate::json::JsonBody; +use crate::payload::Payload; /// Trait that implements general purpose operations on http messages pub trait HttpMessage: Sized { @@ -25,7 +27,7 @@ pub trait HttpMessage: Sized { fn headers(&self) -> &HeaderMap; /// Message payload stream - fn payload(&self) -> Option; + fn payload(&self) -> Payload; #[doc(hidden)] /// Get a header @@ -210,7 +212,7 @@ pub trait HttpMessage: Sized { /// Stream to read request line by line. pub struct Readlines { - stream: Option, + stream: Payload, buff: BytesMut, limit: usize, checked_buff: bool, @@ -244,7 +246,7 @@ impl Readlines { fn err(err: ReadlinesError) -> Self { Readlines { - stream: None, + stream: Payload::None, buff: BytesMut::new(), limit: 262_144, checked_buff: true, @@ -292,65 +294,61 @@ impl Stream for Readlines { self.checked_buff = true; } // poll req for more bytes - if let Some(ref mut stream) = self.stream { - match stream.poll() { - Ok(Async::Ready(Some(mut bytes))) => { - // check if there is a newline in bytes - let mut found: Option = None; - for (ind, b) in bytes.iter().enumerate() { - if *b == b'\n' { - found = Some(ind); - break; - } + match self.stream.poll() { + Ok(Async::Ready(Some(mut bytes))) => { + // check if there is a newline in bytes + let mut found: Option = None; + for (ind, b) in bytes.iter().enumerate() { + if *b == b'\n' { + found = Some(ind); + break; } - if let Some(ind) = found { - // check if line is longer than limit - if ind + 1 > self.limit { - return Err(ReadlinesError::LimitOverflow); - } - let enc: *const Encoding = self.encoding as *const Encoding; - let line = if enc == UTF_8 { - str::from_utf8(&bytes.split_to(ind + 1)) - .map_err(|_| ReadlinesError::EncodingError)? - .to_owned() - } else { - self.encoding - .decode(&bytes.split_to(ind + 1), DecoderTrap::Strict) - .map_err(|_| ReadlinesError::EncodingError)? - }; - // extend buffer with rest of the bytes; - self.buff.extend_from_slice(&bytes); - self.checked_buff = false; - return Ok(Async::Ready(Some(line))); - } - self.buff.extend_from_slice(&bytes); - Ok(Async::NotReady) } - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(None)) => { - if self.buff.is_empty() { - return Ok(Async::Ready(None)); - } - if self.buff.len() > self.limit { + if let Some(ind) = found { + // check if line is longer than limit + if ind + 1 > self.limit { return Err(ReadlinesError::LimitOverflow); } let enc: *const Encoding = self.encoding as *const Encoding; let line = if enc == UTF_8 { - str::from_utf8(&self.buff) + str::from_utf8(&bytes.split_to(ind + 1)) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { self.encoding - .decode(&self.buff, DecoderTrap::Strict) + .decode(&bytes.split_to(ind + 1), DecoderTrap::Strict) .map_err(|_| ReadlinesError::EncodingError)? }; - self.buff.clear(); - Ok(Async::Ready(Some(line))) + // extend buffer with rest of the bytes; + self.buff.extend_from_slice(&bytes); + self.checked_buff = false; + return Ok(Async::Ready(Some(line))); } - Err(e) => Err(ReadlinesError::from(e)), + self.buff.extend_from_slice(&bytes); + Ok(Async::NotReady) } - } else { - Ok(Async::Ready(None)) + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(None)) => { + if self.buff.is_empty() { + return Ok(Async::Ready(None)); + } + if self.buff.len() > self.limit { + return Err(ReadlinesError::LimitOverflow); + } + let enc: *const Encoding = self.encoding as *const Encoding; + let line = if enc == UTF_8 { + str::from_utf8(&self.buff) + .map_err(|_| ReadlinesError::EncodingError)? + .to_owned() + } else { + self.encoding + .decode(&self.buff, DecoderTrap::Strict) + .map_err(|_| ReadlinesError::EncodingError)? + }; + self.buff.clear(); + Ok(Async::Ready(Some(line))) + } + Err(e) => Err(ReadlinesError::from(e)), } } } @@ -359,7 +357,7 @@ impl Stream for Readlines { pub struct MessageBody { limit: usize, length: Option, - stream: Option, + stream: Payload, err: Option, fut: Option>>, } @@ -397,7 +395,7 @@ impl MessageBody { fn err(e: PayloadError) -> Self { MessageBody { - stream: None, + stream: Payload::None, limit: 262_144, fut: None, err: Some(e), @@ -428,16 +426,10 @@ where } } - if self.stream.is_none() { - return Ok(Async::Ready(Bytes::new())); - } - // future let limit = self.limit; self.fut = Some(Box::new( - self.stream - .take() - .expect("Can not be used second time") + mem::replace(&mut self.stream, Payload::None) .from_err() .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { @@ -455,7 +447,7 @@ where /// Future that resolves to a parsed urlencoded values. pub struct UrlEncoded { - stream: Option, + stream: Payload, limit: usize, length: Option, encoding: EncodingRef, @@ -500,7 +492,7 @@ impl UrlEncoded { fn err(e: UrlencodedError) -> Self { UrlEncoded { - stream: None, + stream: Payload::None, limit: 262_144, fut: None, err: Some(e), @@ -543,10 +535,7 @@ where // future let encoding = self.encoding; - let fut = self - .stream - .take() - .expect("UrlEncoded could not be used second time") + let fut = mem::replace(&mut self.stream, Payload::None) .from_err() .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { diff --git a/src/json.rs b/src/json.rs index f750f545d..573fde411 100644 --- a/src/json.rs +++ b/src/json.rs @@ -8,6 +8,7 @@ use serde_json; use crate::error::JsonPayloadError; use crate::httpmessage::HttpMessage; +use crate::payload::Payload; /// Request payload json parser that resolves to a deserialized `T` value. /// @@ -43,7 +44,7 @@ use crate::httpmessage::HttpMessage; pub struct JsonBody { limit: usize, length: Option, - stream: Option, + stream: Payload, err: Option, fut: Option>>, } @@ -61,7 +62,7 @@ impl JsonBody { return JsonBody { limit: 262_144, length: None, - stream: None, + stream: Payload::None, fut: None, err: Some(JsonPayloadError::ContentType), }; @@ -112,10 +113,7 @@ impl Future for JsonBod } } - let fut = self - .stream - .take() - .expect("JsonBody could not be used second time") + let fut = std::mem::replace(&mut self.stream, Payload::None) .from_err() .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { diff --git a/src/payload.rs b/src/payload.rs index ede1281ec..21e415313 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -1,32 +1,57 @@ use bytes::Bytes; -use derive_more::From; -use futures::{Poll, Stream}; +use futures::{Async, Poll, Stream}; use h2::RecvStream; use crate::error::PayloadError; -#[derive(From)] -pub enum Payload { +/// Type represent boxed payload +pub type PayloadStream = Box>; + +/// Type represent streaming payload +pub enum Payload { + None, H1(crate::h1::Payload), H2(crate::h2::Payload), - Dyn(Box>), + Stream(S), } -impl From for Payload { +impl From for Payload { fn from(v: RecvStream) -> Self { Payload::H2(crate::h2::Payload::new(v)) } } -impl Stream for Payload { +impl From for Payload { + fn from(pl: crate::h1::Payload) -> Self { + Payload::H1(pl) + } +} + +impl From for Payload { + fn from(pl: crate::h2::Payload) -> Self { + Payload::H2(pl) + } +} + +impl From for Payload { + fn from(pl: PayloadStream) -> Self { + Payload::Stream(pl) + } +} + +impl Stream for Payload +where + S: Stream, +{ type Item = Bytes; type Error = PayloadError; fn poll(&mut self) -> Poll, Self::Error> { match self { + Payload::None => Ok(Async::Ready(None)), Payload::H1(ref mut pl) => pl.poll(), Payload::H2(ref mut pl) => pl.poll(), - Payload::Dyn(ref mut pl) => pl.poll(), + Payload::Stream(ref mut pl) => pl.poll(), } } } diff --git a/src/request.rs b/src/request.rs index b9c35c7ac..388fe7543 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,5 +1,5 @@ use std::cell::{Ref, RefCell, RefMut}; -use std::fmt; +use std::{fmt, mem}; use bytes::Bytes; use futures::Stream; @@ -9,11 +9,11 @@ use crate::error::PayloadError; use crate::extensions::Extensions; use crate::httpmessage::HttpMessage; use crate::message::{Message, RequestHead}; -use crate::payload::Payload; +use crate::payload::{Payload, PayloadStream}; /// Request -pub struct Request

    { - pub(crate) payload: RefCell>, +pub struct Request

    { + pub(crate) payload: RefCell>, pub(crate) inner: Message, } @@ -28,53 +28,53 @@ where } #[inline] - fn payload(&self) -> Option

    { - self.payload.borrow_mut().take() + fn payload(&self) -> Payload { + mem::replace(&mut *self.payload.borrow_mut(), Payload::None) } } -impl From> for Request { +impl

    From> for Request

    { fn from(msg: Message) -> Self { Request { - payload: RefCell::new(None), + payload: RefCell::new(Payload::None), inner: msg, } } } -impl Request { +impl Request { /// Create new Request instance - pub fn new() -> Request { + pub fn new() -> Request { Request { - payload: RefCell::new(None), + payload: RefCell::new(Payload::None), inner: Message::new(), } } } -impl Request { +impl

    Request

    { /// Create new Request instance - pub fn with_payload(payload: Payload) -> Request { + pub fn with_payload(payload: Payload

    ) -> Request

    { Request { - payload: RefCell::new(Some(payload.into())), + payload: RefCell::new(payload), inner: Message::new(), } } /// Create new Request instance - pub fn set_payload(self, payload: I) -> Request

    + pub fn set_payload(self, payload: I) -> Request where - I: Into

    , + I: Into>, { Request { - payload: RefCell::new(Some(payload.into())), + payload: RefCell::new(payload.into()), inner: self.inner, } } /// Split request into request head and payload - pub fn into_parts(mut self) -> (Message, Option) { - (self.inner, self.payload.get_mut().take()) + pub fn into_parts(self) -> (Message, Payload

    ) { + (self.inner, self.payload.into_inner()) } #[inline] From 8d4ce0c956f6a81d0b8aec50ad220b352f12e5c0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Feb 2019 11:09:58 -0800 Subject: [PATCH 137/208] export PayloadStream --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2b9d8c619..4e6e37955 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,7 +95,7 @@ pub use self::error::{Error, ResponseError, Result}; pub use self::extensions::Extensions; pub use self::httpmessage::HttpMessage; pub use self::message::{Message, RequestHead, ResponseHead}; -pub use self::payload::Payload; +pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; pub use self::response::Response; pub use self::service::{SendError, SendResponse}; From 118606262be9dcfe64b9870308cd2dccb26111d3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Feb 2019 13:52:11 -0800 Subject: [PATCH 138/208] refactor payload handling --- examples/echo.rs | 2 +- examples/echo2.rs | 2 +- src/client/h1proto.rs | 5 ++- src/client/h2proto.rs | 7 ++-- src/client/response.rs | 22 +++++----- src/h1/dispatcher.rs | 4 +- src/h1/service.rs | 3 +- src/httpmessage.rs | 95 ++++++++++++++++++++++++++++-------------- src/json.rs | 31 +++++++++----- src/message.rs | 25 ++++++++++- src/payload.rs | 20 ++++----- src/request.rs | 71 ++++++++++++++++--------------- tests/test_client.rs | 4 +- tests/test_server.rs | 20 ++++----- 14 files changed, 186 insertions(+), 125 deletions(-) diff --git a/examples/echo.rs b/examples/echo.rs index 5b68024f1..03d5b4706 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -18,7 +18,7 @@ fn main() { .client_timeout(1000) .client_disconnect(1000) .server_hostname("localhost") - .finish(|req: Request| { + .finish(|mut req: Request| { req.body().limit(512).and_then(|bytes: Bytes| { info!("request body: {:?}", bytes); let mut res = Response::Ok(); diff --git a/examples/echo2.rs b/examples/echo2.rs index daaafa087..2fd9cbcff 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -8,7 +8,7 @@ use futures::Future; use log::info; use std::env; -fn handle_request(req: Request) -> impl Future { +fn handle_request(mut req: Request) -> impl Future { req.body().limit(512).from_err().and_then(|bytes: Bytes| { info!("request body: {:?}", bytes); let mut res = Response::Ok(); diff --git a/src/client/h1proto.rs b/src/client/h1proto.rs index d16491385..3329fcfec 100644 --- a/src/client/h1proto.rs +++ b/src/client/h1proto.rs @@ -13,7 +13,6 @@ use crate::body::{BodyLength, MessageBody}; use crate::error::PayloadError; use crate::h1; use crate::message::RequestHead; -use crate::payload::PayloadStream; pub(crate) fn send_request( io: T, @@ -205,7 +204,9 @@ pub(crate) struct Payload { } impl Payload { - pub fn stream(framed: Framed) -> PayloadStream { + pub fn stream( + framed: Framed, + ) -> Box> { Box::new(Payload { framed: Some(framed.map_codec(|codec| codec.into_payload_codec())), }) diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index 697d30a41..8804d13f2 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::time; use actix_codec::{AsyncRead, AsyncWrite}; @@ -10,7 +9,7 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCOD use http::{request::Request, HttpTryFrom, Version}; use crate::body::{BodyLength, MessageBody}; -use crate::message::{RequestHead, ResponseHead}; +use crate::message::{Message, RequestHead, ResponseHead}; use super::connection::{ConnectionType, IoConnection}; use super::error::SendRequestError; @@ -103,14 +102,14 @@ where .and_then(|resp| { let (parts, body) = resp.into_parts(); - let mut head = ResponseHead::default(); + let mut head: Message = Message::new(); head.version = parts.version; head.status = parts.status; head.headers = parts.headers; Ok(ClientResponse { head, - payload: RefCell::new(body.into()), + payload: body.into(), }) }) .from_err() diff --git a/src/client/response.rs b/src/client/response.rs index f19e2d17a..104d28ed7 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,5 +1,4 @@ -use std::cell::RefCell; -use std::{fmt, mem}; +use std::fmt; use bytes::Bytes; use futures::{Poll, Stream}; @@ -7,13 +6,13 @@ use http::{HeaderMap, StatusCode, Version}; use crate::error::PayloadError; use crate::httpmessage::HttpMessage; -use crate::message::{Head, ResponseHead}; +use crate::message::{Head, Message, ResponseHead}; use crate::payload::{Payload, PayloadStream}; /// Client Response pub struct ClientResponse { - pub(crate) head: ResponseHead, - pub(crate) payload: RefCell, + pub(crate) head: Message, + pub(crate) payload: Payload, } impl HttpMessage for ClientResponse { @@ -23,9 +22,8 @@ impl HttpMessage for ClientResponse { &self.head.headers } - #[inline] - fn payload(&self) -> Payload { - mem::replace(&mut *self.payload.borrow_mut(), Payload::None) + fn take_payload(&mut self) -> Payload { + std::mem::replace(&mut self.payload, Payload::None) } } @@ -33,8 +31,8 @@ impl ClientResponse { /// Create new Request instance pub fn new() -> ClientResponse { ClientResponse { - head: ResponseHead::default(), - payload: RefCell::new(Payload::None), + head: Message::new(), + payload: Payload::None, } } @@ -80,7 +78,7 @@ impl ClientResponse { /// Set response payload pub fn set_payload(&mut self, payload: Payload) { - *self.payload.get_mut() = payload; + self.payload = payload; } } @@ -89,7 +87,7 @@ impl Stream for ClientResponse { type Error = PayloadError; fn poll(&mut self) -> Poll, Self::Error> { - self.payload.get_mut().poll() + self.payload.poll() } } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index c242333b9..22d7ea865 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -316,7 +316,9 @@ where match self.framed.get_codec().message_type() { MessageType::Payload => { let (ps, pl) = Payload::create(false); - req = req.set_payload(crate::Payload::H1(pl)); + let (req1, _) = + req.replace_payload(crate::Payload::H1(pl)); + req = req1; self.payload = Some(ps); } MessageType::Stream => { diff --git a/src/h1/service.rs b/src/h1/service.rs index 381864498..cb8dae54d 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -12,7 +12,6 @@ use log::error; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, ParseError}; -use crate::payload::PayloadStream; use crate::request::Request; use crate::response::Response; @@ -29,7 +28,7 @@ pub struct H1Service { impl H1Service where - S: NewService>, + S: NewService, S::Error: Debug, S::Response: Into>, S::Service: 'static, diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 57ad4bf95..79f29a723 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,4 +1,4 @@ -use std::{mem, str}; +use std::str; use bytes::{Bytes, BytesMut}; use encoding::all::UTF_8; @@ -21,13 +21,13 @@ use crate::payload::Payload; /// Trait that implements general purpose operations on http messages pub trait HttpMessage: Sized { /// Type of message payload stream - type Stream: Stream + Sized; + type Stream; /// Read the message headers. fn headers(&self) -> &HeaderMap; /// Message payload stream - fn payload(&self) -> Payload; + fn take_payload(&mut self) -> Payload; #[doc(hidden)] /// Get a header @@ -130,7 +130,10 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn body(&self) -> MessageBody { + fn body(&mut self) -> MessageBody + where + Self::Stream: Stream + Sized, + { MessageBody::new(self) } @@ -164,7 +167,10 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn urlencoded(&self) -> UrlEncoded { + fn urlencoded(&mut self) -> UrlEncoded + where + Self::Stream: Stream, + { UrlEncoded::new(self) } @@ -200,12 +206,18 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn json(&self) -> JsonBody { + fn json(&mut self) -> JsonBody + where + Self::Stream: Stream + 'static, + { JsonBody::new(self) } /// Return stream of lines. - fn readlines(&self) -> Readlines { + fn readlines(&mut self) -> Readlines + where + Self::Stream: Stream + 'static, + { Readlines::new(self) } } @@ -220,16 +232,20 @@ pub struct Readlines { err: Option, } -impl Readlines { +impl Readlines +where + T: HttpMessage, + T::Stream: Stream, +{ /// Create a new stream to read request line by line. - fn new(req: &T) -> Self { + fn new(req: &mut T) -> Self { let encoding = match req.encoding() { Ok(enc) => enc, Err(err) => return Self::err(err.into()), }; Readlines { - stream: req.payload(), + stream: req.take_payload(), buff: BytesMut::with_capacity(262_144), limit: 262_144, checked_buff: true, @@ -256,7 +272,11 @@ impl Readlines { } } -impl Stream for Readlines { +impl Stream for Readlines +where + T: HttpMessage, + T::Stream: Stream, +{ type Item = String; type Error = ReadlinesError; @@ -362,9 +382,13 @@ pub struct MessageBody { fut: Option>>, } -impl MessageBody { +impl MessageBody +where + T: HttpMessage, + T::Stream: Stream, +{ /// Create `MessageBody` for request. - pub fn new(req: &T) -> MessageBody { + pub fn new(req: &mut T) -> MessageBody { let mut len = None; if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { @@ -379,9 +403,9 @@ impl MessageBody { } MessageBody { + stream: req.take_payload(), limit: 262_144, length: len, - stream: req.payload(), fut: None, err: None, } @@ -406,7 +430,8 @@ impl MessageBody { impl Future for MessageBody where - T: HttpMessage + 'static, + T: HttpMessage, + T::Stream: Stream + 'static, { type Item = Bytes; type Error = PayloadError; @@ -429,7 +454,7 @@ where // future let limit = self.limit; self.fut = Some(Box::new( - mem::replace(&mut self.stream, Payload::None) + std::mem::replace(&mut self.stream, Payload::None) .from_err() .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { @@ -455,9 +480,13 @@ pub struct UrlEncoded { fut: Option>>, } -impl UrlEncoded { +impl UrlEncoded +where + T: HttpMessage, + T::Stream: Stream, +{ /// Create a new future to URL encode a request - pub fn new(req: &T) -> UrlEncoded { + pub fn new(req: &mut T) -> UrlEncoded { // check content type if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { return Self::err(UrlencodedError::ContentType); @@ -482,7 +511,7 @@ impl UrlEncoded { UrlEncoded { encoding, - stream: req.payload(), + stream: req.take_payload(), limit: 262_144, length: len, fut: None, @@ -510,7 +539,8 @@ impl UrlEncoded { impl Future for UrlEncoded where - T: HttpMessage + 'static, + T: HttpMessage, + T::Stream: Stream + 'static, U: DeserializeOwned + 'static, { type Item = U; @@ -535,7 +565,7 @@ where // future let encoding = self.encoding; - let fut = mem::replace(&mut self.stream, Payload::None) + let fut = std::mem::replace(&mut self.stream, Payload::None) .from_err() .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { @@ -691,7 +721,7 @@ mod tests { #[test] fn test_urlencoded_error() { - let req = TestRequest::with_header( + let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -702,7 +732,7 @@ mod tests { UrlencodedError::UnknownLength ); - let req = TestRequest::with_header( + let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -713,7 +743,7 @@ mod tests { UrlencodedError::Overflow ); - let req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain") + let mut req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain") .header(header::CONTENT_LENGTH, "10") .finish(); assert_eq!( @@ -724,7 +754,7 @@ mod tests { #[test] fn test_urlencoded() { - let req = TestRequest::with_header( + let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -740,7 +770,7 @@ mod tests { }) ); - let req = TestRequest::with_header( + let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8", ) @@ -759,19 +789,20 @@ mod tests { #[test] fn test_message_body() { - let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); + let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); match req.body().poll().err().unwrap() { PayloadError::UnknownLength => (), _ => unreachable!("error"), } - let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); + let mut req = + TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); match req.body().poll().err().unwrap() { PayloadError::Overflow => (), _ => unreachable!("error"), } - let req = TestRequest::default() + let mut req = TestRequest::default() .set_payload(Bytes::from_static(b"test")) .finish(); match req.body().poll().ok().unwrap() { @@ -779,7 +810,7 @@ mod tests { _ => unreachable!("error"), } - let req = TestRequest::default() + let mut req = TestRequest::default() .set_payload(Bytes::from_static(b"11111111111111")) .finish(); match req.body().limit(5).poll().err().unwrap() { @@ -790,14 +821,14 @@ mod tests { #[test] fn test_readlines() { - let req = TestRequest::default() + let mut req = TestRequest::default() .set_payload(Bytes::from_static( b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ industry. Lorem Ipsum has been the industry's standard dummy\n\ Contrary to popular belief, Lorem Ipsum is not simply random text.", )) .finish(); - let mut r = Readlines::new(&req); + let mut r = Readlines::new(&mut req); match r.poll().ok().unwrap() { Async::Ready(Some(s)) => assert_eq!( s, diff --git a/src/json.rs b/src/json.rs index 573fde411..026ecb871 100644 --- a/src/json.rs +++ b/src/json.rs @@ -2,11 +2,12 @@ use bytes::BytesMut; use futures::{Future, Poll, Stream}; use http::header::CONTENT_LENGTH; +use bytes::Bytes; use mime; use serde::de::DeserializeOwned; use serde_json; -use crate::error::JsonPayloadError; +use crate::error::{JsonPayloadError, PayloadError}; use crate::httpmessage::HttpMessage; use crate::payload::Payload; @@ -41,7 +42,7 @@ use crate::payload::Payload; /// } /// # fn main() {} /// ``` -pub struct JsonBody { +pub struct JsonBody { limit: usize, length: Option, stream: Payload, @@ -49,9 +50,14 @@ pub struct JsonBody { fut: Option>>, } -impl JsonBody { +impl JsonBody +where + T: HttpMessage, + T::Stream: Stream + 'static, + U: DeserializeOwned + 'static, +{ /// Create `JsonBody` for request. - pub fn new(req: &T) -> Self { + pub fn new(req: &mut T) -> Self { // check content-type let json = if let Ok(Some(mime)) = req.mime_type() { mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) @@ -80,7 +86,7 @@ impl JsonBody { JsonBody { limit: 262_144, length: len, - stream: req.payload(), + stream: req.take_payload(), fut: None, err: None, } @@ -93,7 +99,12 @@ impl JsonBody { } } -impl Future for JsonBody { +impl Future for JsonBody +where + T: HttpMessage, + T::Stream: Stream + 'static, + U: DeserializeOwned + 'static, +{ type Item = U; type Error = JsonPayloadError; @@ -162,11 +173,11 @@ mod tests { #[test] fn test_json_body() { - let req = TestRequest::default().finish(); + let mut req = TestRequest::default().finish(); let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - let req = TestRequest::default() + let mut req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/text"), @@ -175,7 +186,7 @@ mod tests { let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - let req = TestRequest::default() + let mut req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), @@ -188,7 +199,7 @@ mod tests { let mut json = req.json::().limit(100); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); - let req = TestRequest::default() + let mut req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), diff --git a/src/message.rs b/src/message.rs index 7fb45bcc5..812f099e4 100644 --- a/src/message.rs +++ b/src/message.rs @@ -2,9 +2,8 @@ use std::cell::{Ref, RefCell, RefMut}; use std::collections::VecDeque; use std::rc::Rc; -use http::{HeaderMap, Method, StatusCode, Uri, Version}; - use crate::extensions::Extensions; +use crate::http::{HeaderMap, Method, StatusCode, Uri, Version}; /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] @@ -21,6 +20,12 @@ pub enum ConnectionType { pub trait Head: Default + 'static { fn clear(&mut self); + /// Read the message headers. + fn headers(&self) -> &HeaderMap; + + /// Mutable reference to the message headers. + fn headers_mut(&mut self) -> &mut HeaderMap; + /// Connection type fn connection_type(&self) -> ConnectionType; @@ -68,6 +73,14 @@ impl Head for RequestHead { self.extensions.borrow_mut().clear(); } + fn headers(&self) -> &HeaderMap { + &self.headers + } + + fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.headers + } + fn set_connection_type(&mut self, ctype: ConnectionType) { self.ctype = Some(ctype) } @@ -129,6 +142,14 @@ impl Head for ResponseHead { self.headers.clear(); } + fn headers(&self) -> &HeaderMap { + &self.headers + } + + fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.headers + } + fn set_connection_type(&mut self, ctype: ConnectionType) { self.ctype = Some(ctype) } diff --git a/src/payload.rs b/src/payload.rs index 21e415313..bc40fe807 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -15,21 +15,21 @@ pub enum Payload { Stream(S), } -impl From for Payload { - fn from(v: RecvStream) -> Self { - Payload::H2(crate::h2::Payload::new(v)) - } -} - impl From for Payload { - fn from(pl: crate::h1::Payload) -> Self { - Payload::H1(pl) + fn from(v: crate::h1::Payload) -> Self { + Payload::H1(v) } } impl From for Payload { - fn from(pl: crate::h2::Payload) -> Self { - Payload::H2(pl) + fn from(v: crate::h2::Payload) -> Self { + Payload::H2(v) + } +} + +impl From for Payload { + fn from(v: RecvStream) -> Self { + Payload::H2(crate::h2::Payload::new(v)) } } diff --git a/src/request.rs b/src/request.rs index 388fe7543..e1b893f95 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,11 +1,8 @@ -use std::cell::{Ref, RefCell, RefMut}; -use std::{fmt, mem}; +use std::cell::{Ref, RefMut}; +use std::fmt; -use bytes::Bytes; -use futures::Stream; use http::{header, HeaderMap, Method, Uri, Version}; -use crate::error::PayloadError; use crate::extensions::Extensions; use crate::httpmessage::HttpMessage; use crate::message::{Message, RequestHead}; @@ -13,31 +10,27 @@ use crate::payload::{Payload, PayloadStream}; /// Request pub struct Request

    { - pub(crate) payload: RefCell>, - pub(crate) inner: Message, + pub(crate) payload: Payload

    , + pub(crate) head: Message, } -impl

    HttpMessage for Request

    -where - P: Stream, -{ +impl

    HttpMessage for Request

    { type Stream = P; fn headers(&self) -> &HeaderMap { &self.head().headers } - #[inline] - fn payload(&self) -> Payload { - mem::replace(&mut *self.payload.borrow_mut(), Payload::None) + fn take_payload(&mut self) -> Payload

    { + std::mem::replace(&mut self.payload, Payload::None) } } -impl

    From> for Request

    { - fn from(msg: Message) -> Self { +impl From> for Request { + fn from(head: Message) -> Self { Request { - payload: RefCell::new(Payload::None), - inner: msg, + head, + payload: Payload::None, } } } @@ -46,8 +39,8 @@ impl Request { /// Create new Request instance pub fn new() -> Request { Request { - payload: RefCell::new(Payload::None), - inner: Message::new(), + head: Message::new(), + payload: Payload::None, } } } @@ -56,38 +49,44 @@ impl

    Request

    { /// Create new Request instance pub fn with_payload(payload: Payload

    ) -> Request

    { Request { - payload: RefCell::new(payload), - inner: Message::new(), + payload, + head: Message::new(), } } /// Create new Request instance - pub fn set_payload(self, payload: I) -> Request - where - I: Into>, - { - Request { - payload: RefCell::new(payload.into()), - inner: self.inner, - } + pub fn replace_payload(self, payload: Payload) -> (Request, Payload

    ) { + let pl = self.payload; + ( + Request { + payload, + head: self.head, + }, + pl, + ) + } + + /// Get request's payload + pub fn take_payload(&mut self) -> Payload

    { + std::mem::replace(&mut self.payload, Payload::None) } /// Split request into request head and payload pub fn into_parts(self) -> (Message, Payload

    ) { - (self.inner, self.payload.into_inner()) + (self.head, self.payload) } #[inline] /// Http message part of the request pub fn head(&self) -> &RequestHead { - &*self.inner + &*self.head } #[inline] #[doc(hidden)] /// Mutable reference to a http message part of the request pub fn head_mut(&mut self) -> &mut RequestHead { - &mut *self.inner + &mut *self.head } /// Request's uri. @@ -135,13 +134,13 @@ impl

    Request

    { /// Request extensions #[inline] pub fn extensions(&self) -> Ref { - self.inner.extensions() + self.head.extensions() } /// Mutable reference to a the request's extensions #[inline] pub fn extensions_mut(&self) -> RefMut { - self.inner.extensions_mut() + self.head.extensions_mut() } /// Check if request requires connection upgrade @@ -155,7 +154,7 @@ impl

    Request

    { } } -impl fmt::Debug for Request { +impl

    fmt::Debug for Request

    { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!( f, diff --git a/tests/test_client.rs b/tests/test_client.rs index 606bac22a..6f502b0af 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -47,7 +47,7 @@ fn test_h1_v2() { assert!(repr.contains("ClientRequest")); assert!(repr.contains("x-test")); - let response = srv.block_on(request.send(&mut connector)).unwrap(); + let mut response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response @@ -55,7 +55,7 @@ fn test_h1_v2() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); let request = srv.post().finish().unwrap(); - let response = srv.block_on(request.send(&mut connector)).unwrap(); + let mut response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response diff --git a/tests/test_server.rs b/tests/test_server.rs index 9fa27e71b..53db38403 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -89,7 +89,7 @@ fn test_h2_body() -> std::io::Result<()> { .map_err(|e| println!("Openssl error: {}", e)) .and_then( h2::H2Service::build() - .finish(|req: Request<_>| { + .finish(|mut req: Request<_>| { req.body() .limit(1024 * 1024) .and_then(|body| Ok(Response::Ok().body(body))) @@ -101,7 +101,7 @@ fn test_h2_body() -> std::io::Result<()> { let req = client::ClientRequest::get(srv.surl("/")) .body(data.clone()) .unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); let body = srv.block_on(response.body().limit(1024 * 1024)).unwrap(); @@ -350,7 +350,7 @@ fn test_headers() { let req = srv.get().finish().unwrap(); - let response = srv.block_on(req.send(&mut connector)).unwrap(); + let mut response = srv.block_on(req.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response @@ -387,7 +387,7 @@ fn test_body() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -402,7 +402,7 @@ fn test_head_empty() { }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -428,7 +428,7 @@ fn test_head_binary() { }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -477,7 +477,7 @@ fn test_body_length() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -496,7 +496,7 @@ fn test_body_chunked_explicit() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -517,7 +517,7 @@ fn test_body_chunked_implicit() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -540,7 +540,7 @@ fn test_response_http_error_handling() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response From e6e83ea57e82b59ad504420b23e2d00950756ce8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Feb 2019 17:01:35 -0800 Subject: [PATCH 139/208] add Response::map_body --- src/lib.rs | 3 +-- src/response.rs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4e6e37955..8750b24ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,12 +89,11 @@ pub mod h2; pub mod test; pub mod ws; -pub use self::body::{Body, MessageBody}; pub use self::config::{KeepAlive, ServiceConfig, ServiceConfigBuilder}; pub use self::error::{Error, ResponseError, Result}; pub use self::extensions::Extensions; pub use self::httpmessage::HttpMessage; -pub use self::message::{Message, RequestHead, ResponseHead}; +pub use self::message::{Head, Message, RequestHead, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; pub use self::response::Response; diff --git a/src/response.rs b/src/response.rs index d84100fa2..0295758b2 100644 --- a/src/response.rs +++ b/src/response.rs @@ -242,6 +242,20 @@ impl Response { self.body, ) } + + /// Set a body and return previous body value + pub fn map_body(mut self, f: F) -> Response + where + F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, + { + let body = f(&mut self.head, self.body); + + Response { + head: self.head, + body: body, + error: self.error, + } + } } impl fmt::Debug for Response { From 037c3da1729d7fd61e5edb2cf89a29463a5263c2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Feb 2019 18:40:40 -0800 Subject: [PATCH 140/208] enable ssl for connector --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5bcaea7c3..e4dd6c58c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ default = ["session"] session = ["cookie/secure"] # openssl -ssl = ["openssl"] +ssl = ["openssl", "actix-connector/ssl"] [dependencies] actix-service = "0.2.1" From d180b2a1e357d353187a9f67d5b382f63ba77bd0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Feb 2019 18:46:30 -0800 Subject: [PATCH 141/208] update tests --- test-server/src/lib.rs | 2 +- tests/test_client.rs | 6 +++--- tests/test_server.rs | 47 +++++++++++++++++++++--------------------- tests/test_ws.rs | 2 +- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 3d6d917e5..a13e86cf8 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -53,7 +53,7 @@ pub struct TestServerRuntime { impl TestServer { /// Start new test server with application factory - pub fn with_factory( + pub fn new( factory: F, ) -> TestServerRuntime< impl Service diff --git a/tests/test_client.rs b/tests/test_client.rs index 6f502b0af..f44c45cbc 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -31,7 +31,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_h1_v2() { env_logger::init(); - let mut srv = TestServer::with_factory(move || { + let mut srv = TestServer::new(move || { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) @@ -65,7 +65,7 @@ fn test_h1_v2() { #[test] fn test_connection_close() { - let mut srv = TestServer::with_factory(move || { + let mut srv = TestServer::new(move || { h1::H1Service::build() .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) @@ -79,7 +79,7 @@ fn test_connection_close() { #[test] fn test_with_query_parameter() { - let mut srv = TestServer::with_factory(move || { + let mut srv = TestServer::new(move || { h1::H1Service::build() .finish(|req: Request| { if req.uri().query().unwrap().contains("qp=") { diff --git a/tests/test_server.rs b/tests/test_server.rs index 53db38403..dc1ebcfbc 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -8,14 +8,15 @@ use bytes::Bytes; use futures::future::{self, ok, Future}; use futures::stream::once; +use actix_http::body::Body; use actix_http::{ - body, client, h1, h2, http, Body, Error, HttpMessage as HttpMessage2, KeepAlive, - Request, Response, + body, client, h1, h2, http, Error, HttpMessage as HttpMessage2, KeepAlive, Request, + Response, }; #[test] fn test_h1() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) @@ -57,7 +58,7 @@ fn ssl_acceptor() -> std::io::Result> { #[test] fn test_h2() -> std::io::Result<()> { let openssl = ssl_acceptor()?; - let mut srv = TestServer::with_factory(move || { + let mut srv = TestServer::new(move || { openssl .clone() .map_err(|e| println!("Openssl error: {}", e)) @@ -83,7 +84,7 @@ fn test_h2_body() -> std::io::Result<()> { let data = "HELLOWORLD".to_owned().repeat(64 * 1024); let openssl = ssl_acceptor()?; - let mut srv = TestServer::with_factory(move || { + let mut srv = TestServer::new(move || { openssl .clone() .map_err(|e| println!("Openssl error: {}", e)) @@ -111,7 +112,7 @@ fn test_h2_body() -> std::io::Result<()> { #[test] fn test_slow_request() { - let srv = TestServer::with_factory(|| { + let srv = TestServer::new(|| { h1::H1Service::build() .client_timeout(100) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) @@ -127,7 +128,7 @@ fn test_slow_request() { #[test] fn test_malformed_request() { - let srv = TestServer::with_factory(|| { + let srv = TestServer::new(|| { h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().finish())).map(|_| ()) }); @@ -140,7 +141,7 @@ fn test_malformed_request() { #[test] fn test_keepalive() { - let srv = TestServer::with_factory(|| { + let srv = TestServer::new(|| { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .map(|_| ()) @@ -160,7 +161,7 @@ fn test_keepalive() { #[test] fn test_keepalive_timeout() { - let srv = TestServer::with_factory(|| { + let srv = TestServer::new(|| { h1::H1Service::build() .keep_alive(1) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) @@ -181,7 +182,7 @@ fn test_keepalive_timeout() { #[test] fn test_keepalive_close() { - let srv = TestServer::with_factory(|| { + let srv = TestServer::new(|| { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .map(|_| ()) @@ -201,7 +202,7 @@ fn test_keepalive_close() { #[test] fn test_keepalive_http10_default_close() { - let srv = TestServer::with_factory(|| { + let srv = TestServer::new(|| { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .map(|_| ()) @@ -220,7 +221,7 @@ fn test_keepalive_http10_default_close() { #[test] fn test_keepalive_http10() { - let srv = TestServer::with_factory(|| { + let srv = TestServer::new(|| { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .map(|_| ()) @@ -246,7 +247,7 @@ fn test_keepalive_http10() { #[test] fn test_keepalive_disabled() { - let srv = TestServer::with_factory(|| { + let srv = TestServer::new(|| { h1::H1Service::build() .keep_alive(KeepAlive::Disabled) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) @@ -271,7 +272,7 @@ fn test_content_length() { StatusCode, }; - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|req: Request| { let indx: usize = req.uri().path()[1..].parse().unwrap(); let statuses = [ @@ -320,7 +321,7 @@ fn test_headers() { let data = STR.repeat(10); let data2 = data.clone(); - let mut srv = TestServer::with_factory(move || { + let mut srv = TestServer::new(move || { let data = data.clone(); h1::H1Service::new(move |_| { let mut builder = Response::Ok(); @@ -382,7 +383,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_body() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); @@ -397,7 +398,7 @@ fn test_body() { #[test] fn test_head_empty() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); @@ -420,7 +421,7 @@ fn test_head_empty() { #[test] fn test_head_binary() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|_| { ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) }) @@ -446,7 +447,7 @@ fn test_head_binary() { #[test] fn test_head_binary2() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); @@ -465,7 +466,7 @@ fn test_head_binary2() { #[test] fn test_body_length() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( @@ -487,7 +488,7 @@ fn test_body_length() { #[test] fn test_body_chunked_explicit() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) @@ -508,7 +509,7 @@ fn test_body_chunked_explicit() { #[test] fn test_body_chunked_implicit() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) @@ -527,7 +528,7 @@ fn test_body_chunked_implicit() { #[test] fn test_response_http_error_handling() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( diff --git a/tests/test_ws.rs b/tests/test_ws.rs index e5a54c7c1..4111ca3db 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -34,7 +34,7 @@ fn ws_service(req: ws::Frame) -> impl Future)| { From 842da939dc490a255a9f1a8b4d3d28b3dcc6647a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Feb 2019 20:24:50 -0800 Subject: [PATCH 142/208] fix chunked transfer encoding handling --- src/body.rs | 3 +-- src/client/h2proto.rs | 2 +- src/h1/encoder.rs | 27 ++++++++++++++++++++++----- src/h2/dispatcher.rs | 2 +- src/message.rs | 6 ++++++ src/response.rs | 9 +++++++++ 6 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/body.rs b/src/body.rs index d3e63f9c9..1f218c4ba 100644 --- a/src/body.rs +++ b/src/body.rs @@ -13,7 +13,6 @@ pub enum BodyLength { Empty, Sized(usize), Sized64(u64), - Chunked, Stream, } @@ -331,7 +330,7 @@ where E: Into, { fn length(&self) -> BodyLength { - BodyLength::Chunked + BodyLength::Stream } fn poll_next(&mut self) -> Poll, Error> { diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index 8804d13f2..617c21b60 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -47,7 +47,7 @@ where // Content length let _ = match length { - BodyLength::Chunked | BodyLength::None => None, + BodyLength::None => None, BodyLength::Stream => { skip_len = false; None diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 32c8f9c48..7627f6979 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -45,6 +45,8 @@ pub(crate) trait MessageType: Sized { fn headers(&self) -> &HeaderMap; + fn chunked(&self) -> bool; + fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()>; fn encode_headers( @@ -71,8 +73,10 @@ pub(crate) trait MessageType: Sized { } } match length { - BodyLength::Chunked => { - dst.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") + BodyLength::Stream => { + if self.chunked() { + dst.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") + } } BodyLength::Empty => { dst.extend_from_slice(b"\r\ncontent-length: 0\r\n"); @@ -83,7 +87,7 @@ pub(crate) trait MessageType: Sized { write!(dst.writer(), "{}", len)?; dst.extend_from_slice(b"\r\n"); } - BodyLength::None | BodyLength::Stream => dst.extend_from_slice(b"\r\n"), + BodyLength::None => dst.extend_from_slice(b"\r\n"), } // Connection @@ -159,6 +163,10 @@ impl MessageType for Response<()> { Some(self.head().status) } + fn chunked(&self) -> bool { + !self.head().no_chunking + } + fn connection_type(&self) -> Option { self.head().ctype } @@ -188,6 +196,10 @@ impl MessageType for RequestHead { self.ctype } + fn chunked(&self) -> bool { + !self.no_chunking + } + fn headers(&self) -> &HeaderMap { &self.headers } @@ -236,8 +248,13 @@ impl MessageEncoder { BodyLength::Empty => TransferEncoding::empty(), BodyLength::Sized(len) => TransferEncoding::length(len as u64), BodyLength::Sized64(len) => TransferEncoding::length(len), - BodyLength::Chunked => TransferEncoding::chunked(), - BodyLength::Stream => TransferEncoding::eof(), + BodyLength::Stream => { + if message.chunked() { + TransferEncoding::chunked() + } else { + TransferEncoding::eof() + } + } BodyLength::None => TransferEncoding::empty(), }; } else { diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index 5a8c4b855..ea8756d27 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -181,7 +181,7 @@ where _ => (), } let _ = match length { - BodyLength::Chunked | BodyLength::None | BodyLength::Stream => None, + BodyLength::None | BodyLength::Stream => None, BodyLength::Empty => res .headers_mut() .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), diff --git a/src/message.rs b/src/message.rs index 812f099e4..3a1ac1306 100644 --- a/src/message.rs +++ b/src/message.rs @@ -36,6 +36,7 @@ pub trait Head: Default + 'static { self.connection_type() == ConnectionType::Upgrade } + /// Check if keep-alive is enabled fn keep_alive(&self) -> bool { self.connection_type() == ConnectionType::KeepAlive } @@ -50,6 +51,7 @@ pub struct RequestHead { pub version: Version, pub headers: HeaderMap, pub ctype: Option, + pub no_chunking: bool, pub extensions: RefCell, } @@ -61,6 +63,7 @@ impl Default for RequestHead { version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), ctype: None, + no_chunking: false, extensions: RefCell::new(Extensions::new()), } } @@ -120,6 +123,7 @@ pub struct ResponseHead { pub status: StatusCode, pub headers: HeaderMap, pub reason: Option<&'static str>, + pub no_chunking: bool, pub(crate) ctype: Option, } @@ -130,6 +134,7 @@ impl Default for ResponseHead { status: StatusCode::OK, headers: HeaderMap::with_capacity(16), reason: None, + no_chunking: false, ctype: None, } } @@ -139,6 +144,7 @@ impl Head for ResponseHead { fn clear(&mut self) { self.ctype = None; self.reason = None; + self.no_chunking = false; self.headers.clear(); } diff --git a/src/response.rs b/src/response.rs index 0295758b2..2c627d53d 100644 --- a/src/response.rs +++ b/src/response.rs @@ -459,6 +459,15 @@ impl ResponseBuilder { self } + /// Disable chunked transfer encoding for HTTP/1.1 streaming responses. + #[inline] + pub fn no_chunking(&mut self) -> &mut Self { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.no_chunking = true; + } + self + } + /// Set response content type #[inline] pub fn content_type(&mut self, value: V) -> &mut Self From c8713d045cf5969df0daa3c81b14b272a4492fc6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Feb 2019 21:41:38 -0800 Subject: [PATCH 143/208] poll payload again if framed object get flushed during same iteration --- src/h1/dispatcher.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 22d7ea865..9ae8cd2a1 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -167,7 +167,7 @@ where } /// Flush stream - fn poll_flush(&mut self) -> Poll<(), DispatchError> { + fn poll_flush(&mut self) -> Poll> { if !self.framed.is_write_buf_empty() { match self.framed.poll_complete() { Ok(Async::NotReady) => Ok(Async::NotReady), @@ -180,11 +180,11 @@ where if self.payload.is_some() && self.state.is_empty() { return Err(DispatchError::PayloadIsNotConsumed); } - Ok(Async::Ready(())) + Ok(Async::Ready(true)) } } } else { - Ok(Async::Ready(())) + Ok(Async::Ready(false)) } } @@ -482,8 +482,12 @@ where } else { inner.poll_keepalive()?; inner.poll_request()?; - inner.poll_response()?; - inner.poll_flush()?; + loop { + inner.poll_response()?; + if let Async::Ready(false) = inner.poll_flush()? { + break; + } + } if inner.flags.contains(Flags::DISCONNECTED) { return Ok(Async::Ready(H1ServiceResult::Disconnected)); From 781f1a3fef466a820bdcf4f841616f0a8c9fa55f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Feb 2019 22:20:00 -0800 Subject: [PATCH 144/208] do not skip content length is no chunking is selected --- src/h1/encoder.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 7627f6979..d4f54d247 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -57,6 +57,7 @@ pub(crate) trait MessageType: Sized { ctype: ConnectionType, config: &ServiceConfig, ) -> io::Result<()> { + let chunked = self.chunked(); let mut skip_len = length != BodyLength::Stream; // Content length @@ -74,8 +75,10 @@ pub(crate) trait MessageType: Sized { } match length { BodyLength::Stream => { - if self.chunked() { + if chunked { dst.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") + } else { + skip_len = false; } } BodyLength::Empty => { From 7f749ac9cccf01ad63f82648dcbfef5d15d715ff Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Feb 2019 22:34:22 -0800 Subject: [PATCH 145/208] add missing end of line --- src/h1/encoder.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index d4f54d247..9fe5ba69a 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -79,6 +79,7 @@ pub(crate) trait MessageType: Sized { dst.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } else { skip_len = false; + dst.extend_from_slice(b"\r\n"); } } BodyLength::Empty => { From 60a8da5c05e71a20e214fc6bc392197a2ce70eb0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Feb 2019 21:02:23 -0800 Subject: [PATCH 146/208] remove Response constraint --- src/response.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/response.rs b/src/response.rs index 2c627d53d..8d21d6b3f 100644 --- a/src/response.rs +++ b/src/response.rs @@ -16,7 +16,7 @@ use crate::header::{Header, IntoHeaderValue}; use crate::message::{ConnectionType, Head, Message, ResponseHead}; /// An HTTP Response -pub struct Response { +pub struct Response { head: Message, body: ResponseBody, error: Option, From 2f89b12f4faa178e71b2465106ef8454d5c44bcf Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Feb 2019 21:05:37 -0800 Subject: [PATCH 147/208] remove more response containts --- src/response.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/response.rs b/src/response.rs index 8d21d6b3f..aab881062 100644 --- a/src/response.rs +++ b/src/response.rs @@ -92,7 +92,7 @@ impl Response { } } -impl Response { +impl Response { #[inline] /// Http message part of the response pub fn head(&self) -> &ResponseHead { From b80ee71785c467958c41d5bd0be55e9b13034491 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 22 Feb 2019 14:21:35 -0800 Subject: [PATCH 148/208] use new new service api --- Cargo.toml | 20 +++++++++++++++----- src/h1/service.rs | 6 +++--- src/h2/service.rs | 4 ++-- src/service/senderror.rs | 4 ++-- src/ws/service.rs | 2 +- test-server/Cargo.toml | 10 +++++++--- tests/test_server.rs | 2 -- 7 files changed, 30 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e4dd6c58c..edf572af4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,10 +37,18 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix-service = "0.2.1" +#actix-service = "0.2.1" actix-codec = "0.1.0" -actix-connector = "0.2.0" -actix-utils = "0.2.2" +#actix-connector = "0.2.0" +#actix-utils = "0.2.2" + +actix-service = { git = "https://github.com/actix/actix-net" } +actix-connector = { git = "https://github.com/actix/actix-net" } +actix-utils = { git = "https://github.com/actix/actix-net" } + +#actix-service = { path = "../actix-net/actix-service" } +#actix-connector = { path = "../actix-net/actix-connector" } +#actix-utils = { path = "../actix-net/actix-utils" } base64 = "0.10" backtrace = "0.3" @@ -78,8 +86,10 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.1.0" -actix-server = { version="0.2", features=["ssl"] } -actix-connector = { version="0.2.0", features=["ssl"] } +actix-server = { git = "https://github.com/actix/actix-net", features=["ssl"] } +#actix-server = { path = "../actix-net/actix-server", features=["ssl"] } +#actix-connector = { path = "../actix-net/actix-connector", features=["ssl"] } +actix-connector = { git = "https://github.com/actix/actix-net", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" diff --git a/src/h1/service.rs b/src/h1/service.rs index cb8dae54d..4beb4c9e4 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -67,9 +67,9 @@ where type Service = H1ServiceHandler; type Future = H1ServiceResponse; - fn new_service(&self) -> Self::Future { + fn new_service(&self, _: &()) -> Self::Future { H1ServiceResponse { - fut: self.srv.new_service(), + fut: self.srv.new_service(&()), cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -309,7 +309,7 @@ where type Service = OneRequestService; type Future = FutureResult; - fn new_service(&self) -> Self::Future { + fn new_service(&self, _: &()) -> Self::Future { ok(OneRequestService { config: self.config.clone(), _t: PhantomData, diff --git a/src/h2/service.rs b/src/h2/service.rs index 16b7e4956..fcfc0be22 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -70,9 +70,9 @@ where type Service = H2ServiceHandler; type Future = H2ServiceResponse; - fn new_service(&self) -> Self::Future { + fn new_service(&self, _: &()) -> Self::Future { H2ServiceResponse { - fut: self.srv.new_service(), + fut: self.srv.new_service(&()), cfg: Some(self.cfg.clone()), _t: PhantomData, } diff --git a/src/service/senderror.rs b/src/service/senderror.rs index b469a61e6..44d362593 100644 --- a/src/service/senderror.rs +++ b/src/service/senderror.rs @@ -34,7 +34,7 @@ where type Service = SendError; type Future = FutureResult; - fn new_service(&self) -> Self::Future { + fn new_service(&self, _: &()) -> Self::Future { ok(SendError(PhantomData)) } } @@ -142,7 +142,7 @@ where type Service = SendResponse; type Future = FutureResult; - fn new_service(&self) -> Self::Future { + fn new_service(&self, _: &()) -> Self::Future { ok(SendResponse(PhantomData)) } } diff --git a/src/ws/service.rs b/src/ws/service.rs index 137d41d43..f3b066053 100644 --- a/src/ws/service.rs +++ b/src/ws/service.rs @@ -28,7 +28,7 @@ impl NewService for VerifyWebSockets { type Service = VerifyWebSockets; type Future = FutureResult; - fn new_service(&self) -> Self::Future { + fn new_service(&self, _: &()) -> Self::Future { ok(VerifyWebSockets { _t: PhantomData }) } } diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 9c71a25c0..f5e8afd22 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -33,12 +33,16 @@ ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] [dependencies] actix-codec = "0.1" -actix-service = "0.2.0" actix-rt = "0.1.0" -actix-server = "0.2.0" -actix-utils = "0.2.0" actix-http = { path=".." } +#actix-service = "0.2.0" +#actix-server = "0.2.0" +#actix-utils = "0.2.0" +actix-service = { git = "https://github.com/actix/actix-net" } +actix-server = { git = "https://github.com/actix/actix-net" } +actix-utils = { git = "https://github.com/actix/actix-net" } + base64 = "0.10" bytes = "0.4" cookie = { version="0.11", features=["percent-encode"] } diff --git a/tests/test_server.rs b/tests/test_server.rs index dc1ebcfbc..fd848b82b 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -514,7 +514,6 @@ fn test_body_chunked_implicit() { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) }) - .map(|_| ()) }); let req = srv.get().finish().unwrap(); @@ -537,7 +536,6 @@ fn test_response_http_error_handling() { .body(STR), ) }) - .map(|_| ()) }); let req = srv.get().finish().unwrap(); From 38c86d4683ba661e29be84aeb0a2e845d326e697 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Mar 2019 20:33:31 -0800 Subject: [PATCH 149/208] update tarpaulin travis config --- .travis.yml | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/.travis.yml b/.travis.yml index c9c9db14f..b97272588 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,12 @@ language: rust sudo: required dist: trusty +addons: + apt: + packages: + - libssl-dev -cache: - cargo: true - apt: true +cache: cargo matrix: include: @@ -14,32 +16,22 @@ matrix: allow_failures: - rust: nightly -env: - global: - - RUSTFLAGS="-C link-dead-code" - - OPENSSL_VERSION=openssl-1.0.2 - -before_install: - - sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl - - sudo apt-get update -qq - - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev - -before_script: - - export PATH=$PATH:~/.cargo/bin +before_cache: | + if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f + fi script: - - | - if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then - cargo clean - cargo test --features="ssl" - fi - - | - if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin - RUST_BACKTRACE=1 cargo tarpaulin --features="ssl" --out Xml +- cargo clean +- cargo build --features="ssl" +- cargo test --features="ssl" + +after_success: | + if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then + cargo tarpaulin --features="ssl" --out Xml bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" - fi + fi # Upload docs #after_success: From 650474ca39b0613a14bd2b4d67084b9f6c92a31c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Mar 2019 21:02:56 -0800 Subject: [PATCH 150/208] choose openssl version for travis --- .travis.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index b97272588..4f35c6564 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,10 @@ language: rust sudo: required dist: trusty -addons: - apt: - packages: - - libssl-dev -cache: cargo +cache: + cargo: true + apt: true matrix: include: @@ -16,6 +14,16 @@ matrix: allow_failures: - rust: nightly +env: + global: + - RUSTFLAGS="-C link-dead-code" + - OPENSSL_VERSION=openssl-1.0.2 + +before_install: + - sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl + - sudo apt-get update -qq + - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev + before_cache: | if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f From 5fff07402efe32c180ffba8415e9c39a00b40d25 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Mar 2019 21:36:37 -0800 Subject: [PATCH 151/208] downgrade tarpaulin --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4f35c6564..283437ce6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ before_install: before_cache: | if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f --version 0.6.7 fi script: From de9b38295f2778e1f5a62e6971727257727679ab Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 15:08:10 -0800 Subject: [PATCH 152/208] update deps --- Cargo.toml | 20 +++++--------------- test-server/Cargo.toml | 10 +++------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index edf572af4..403f3303c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,18 +37,10 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -#actix-service = "0.2.1" +actix-service = "0.3.0" actix-codec = "0.1.0" -#actix-connector = "0.2.0" -#actix-utils = "0.2.2" - -actix-service = { git = "https://github.com/actix/actix-net" } -actix-connector = { git = "https://github.com/actix/actix-net" } -actix-utils = { git = "https://github.com/actix/actix-net" } - -#actix-service = { path = "../actix-net/actix-service" } -#actix-connector = { path = "../actix-net/actix-connector" } -#actix-utils = { path = "../actix-net/actix-utils" } +actix-connector = "0.3.0" +actix-utils = "0.3.0" base64 = "0.10" backtrace = "0.3" @@ -86,10 +78,8 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.1.0" -actix-server = { git = "https://github.com/actix/actix-net", features=["ssl"] } -#actix-server = { path = "../actix-net/actix-server", features=["ssl"] } -#actix-connector = { path = "../actix-net/actix-connector", features=["ssl"] } -actix-connector = { git = "https://github.com/actix/actix-net", features=["ssl"] } +actix-server = { version = "0.3.0", features=["ssl"] } +actix-connector = { version = "0.3.0", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index f5e8afd22..cf4364c49 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -35,13 +35,9 @@ ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] actix-codec = "0.1" actix-rt = "0.1.0" actix-http = { path=".." } - -#actix-service = "0.2.0" -#actix-server = "0.2.0" -#actix-utils = "0.2.0" -actix-service = { git = "https://github.com/actix/actix-net" } -actix-server = { git = "https://github.com/actix/actix-net" } -actix-utils = { git = "https://github.com/actix/actix-net" } +actix-service = "0.3.0" +actix-server = "0.3.0" +actix-utils = "0.3.0" base64 = "0.10" bytes = "0.4" From 0081b9d4467e04a719e1715e8d141e05960ebc40 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 15:59:05 -0800 Subject: [PATCH 153/208] improve ergomonics of TestRequest --- src/response.rs | 1 - src/test.rs | 55 +++++++++++++++++++++++++++---------------------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/response.rs b/src/response.rs index aab881062..807487460 100644 --- a/src/response.rs +++ b/src/response.rs @@ -669,7 +669,6 @@ impl ResponseBuilder { } #[inline] -#[allow(clippy::borrowed_box)] fn parts<'a>( parts: &'a mut Option>, err: &Option, diff --git a/src/test.rs b/src/test.rs index b0e308728..74e5da71d 100644 --- a/src/test.rs +++ b/src/test.rs @@ -37,7 +37,9 @@ use crate::Request; /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` -pub struct TestRequest { +pub struct TestRequest(Option); + +struct Inner { version: Version, method: Method, uri: Uri, @@ -49,7 +51,7 @@ pub struct TestRequest { impl Default for TestRequest { fn default() -> TestRequest { - TestRequest { + TestRequest(Some(Inner { method: Method::GET, uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, @@ -57,19 +59,19 @@ impl Default for TestRequest { _cookies: None, payload: None, prefix: 0, - } + })) } } impl TestRequest { /// Create TestRequest and set request uri pub fn with_uri(path: &str) -> TestRequest { - TestRequest::default().uri(path) + TestRequest::default().uri(path).take() } /// Create TestRequest and set header pub fn with_hdr(hdr: H) -> TestRequest { - TestRequest::default().set(hdr) + TestRequest::default().set(hdr).take() } /// Create TestRequest and set header @@ -78,45 +80,45 @@ impl TestRequest { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - TestRequest::default().header(key, value) + TestRequest::default().header(key, value).take() } /// Set HTTP version of this request - pub fn version(mut self, ver: Version) -> Self { - self.version = ver; + pub fn version(&mut self, ver: Version) -> &mut Self { + parts(&mut self.0).version = ver; self } /// Set HTTP method of this request - pub fn method(mut self, meth: Method) -> Self { - self.method = meth; + pub fn method(&mut self, meth: Method) -> &mut Self { + parts(&mut self.0).method = meth; self } /// Set HTTP Uri of this request - pub fn uri(mut self, path: &str) -> Self { - self.uri = Uri::from_str(path).unwrap(); + pub fn uri(&mut self, path: &str) -> &mut Self { + parts(&mut self.0).uri = Uri::from_str(path).unwrap(); self } /// Set a header - pub fn set(mut self, hdr: H) -> Self { + pub fn set(&mut self, hdr: H) -> &mut Self { if let Ok(value) = hdr.try_into() { - self.headers.append(H::name(), value); + parts(&mut self.0).headers.append(H::name(), value); return self; } panic!("Can not set header"); } /// Set a header - pub fn header(mut self, key: K, value: V) -> Self + pub fn header(&mut self, key: K, value: V) -> &mut Self where HeaderName: HttpTryFrom, V: IntoHeaderValue, { if let Ok(key) = HeaderName::try_from(key) { if let Ok(value) = value.try_into() { - self.headers.append(key, value); + parts(&mut self.0).headers.append(key, value); return self; } } @@ -124,29 +126,27 @@ impl TestRequest { } /// Set request payload - pub fn set_payload>(mut self, data: B) -> Self { + pub fn set_payload>(&mut self, data: B) -> &mut Self { let mut payload = crate::h1::Payload::empty(); payload.unread_data(data.into()); - self.payload = Some(payload.into()); + parts(&mut self.0).payload = Some(payload.into()); self } - /// Set request's prefix - pub fn prefix(mut self, prefix: u16) -> Self { - self.prefix = prefix; - self + pub(crate) fn take(&mut self) -> TestRequest { + TestRequest(self.0.take()) } /// Complete request creation and generate `Request` instance - pub fn finish(self) -> Request { - let TestRequest { + pub fn finish(&mut self) -> Request { + let Inner { method, uri, version, headers, payload, .. - } = self; + } = self.0.take().expect("cannot reuse test request builder");; let mut req = if let Some(pl) = payload { Request::with_payload(pl) @@ -251,3 +251,8 @@ impl TestRequest { // } // } } + +#[inline] +fn parts<'a>(parts: &'a mut Option) -> &'a mut Inner { + parts.as_mut().expect("cannot reuse test request builder") +} From 00ea195601d9d1daf2336329c18154038097c6dc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 16:04:43 -0800 Subject: [PATCH 154/208] TestRequest::take public --- src/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test.rs b/src/test.rs index 74e5da71d..4b925d020 100644 --- a/src/test.rs +++ b/src/test.rs @@ -133,7 +133,7 @@ impl TestRequest { self } - pub(crate) fn take(&mut self) -> TestRequest { + pub fn take(&mut self) -> TestRequest { TestRequest(self.0.take()) } From 2d0495093c29b63daeed0a88aba3c7856ccb5733 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 18:37:09 -0800 Subject: [PATCH 155/208] add Payload::take method --- src/payload.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/payload.rs b/src/payload.rs index bc40fe807..8f96bab95 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -39,6 +39,13 @@ impl From for Payload { } } +impl Payload { + /// Takes current payload and replaces it with `None` value + fn take(&mut self) -> Payload { + std::mem::replace(self, Payload::None) + } +} + impl Stream for Payload where S: Stream, From b535adf637ea85176bfaabc78e7f6eed7358ef41 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 21:22:01 -0800 Subject: [PATCH 156/208] add IntoFuture impl for Response and ResponseBuilder --- src/header/common/mod.rs | 6 +++--- src/response.rs | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs index adc7484a9..30dfcaa6d 100644 --- a/src/header/common/mod.rs +++ b/src/header/common/mod.rs @@ -81,7 +81,7 @@ macro_rules! test_header { let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); let mut req = test::TestRequest::default(); for item in a { - req = req.header(HeaderField::name(), item); + req = req.header(HeaderField::name(), item).take(); } let req = req.finish(); let value = HeaderField::parse(&req); @@ -104,11 +104,11 @@ macro_rules! test_header { #[test] fn $id() { use $crate::test; - + let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); let mut req = test::TestRequest::default(); for item in a { - req = req.header(HeaderField::name(), item); + req.header(HeaderField::name(), item); } let req = req.finish(); let val = HeaderField::parse(&req); diff --git a/src/response.rs b/src/response.rs index 807487460..9b503de1b 100644 --- a/src/response.rs +++ b/src/response.rs @@ -4,6 +4,7 @@ use std::{fmt, str}; use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; +use futures::future::{ok, FutureResult, IntoFuture}; use futures::Stream; use http::header::{self, HeaderName, HeaderValue}; use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode}; @@ -276,6 +277,16 @@ impl fmt::Debug for Response { } } +impl IntoFuture for Response { + type Item = Response; + type Error = Error; + type Future = FutureResult; + + fn into_future(self) -> Self::Future { + ok(self) + } +} + pub struct CookieIter<'a> { iter: header::ValueIter<'a, HeaderValue>, } @@ -679,6 +690,16 @@ fn parts<'a>( parts.as_mut() } +impl IntoFuture for ResponseBuilder { + type Item = Response; + type Error = Error; + type Future = FutureResult; + + fn into_future(mut self) -> Self::Future { + ok(self.finish()) + } +} + /// Helper converters impl, E: Into> From> for Response { fn from(res: Result) -> Self { From a88b3b090d4bea412b48cc6be6713ebc87cf0b8b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Mar 2019 15:58:39 -0800 Subject: [PATCH 157/208] allow to specify service config for h1 service --- src/config.rs | 4 ++-- src/h1/service.rs | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/config.rs b/src/config.rs index 960f13706..a9e705c95 100644 --- a/src/config.rs +++ b/src/config.rs @@ -65,7 +65,7 @@ impl Default for ServiceConfig { impl ServiceConfig { /// Create instance of `ServiceConfig` - pub(crate) fn new( + pub fn new( keep_alive: KeepAlive, client_timeout: u64, client_disconnect: u64, @@ -282,7 +282,7 @@ impl ServiceConfigBuilder { } /// Finish service configuration and create `ServiceConfig` object. - pub fn finish(self) -> ServiceConfig { + pub fn finish(&mut self) -> ServiceConfig { ServiceConfig::new(self.keep_alive, self.client_timeout, self.client_disconnect) } } diff --git a/src/h1/service.rs b/src/h1/service.rs index 4beb4c9e4..4c1fb9a82 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -34,7 +34,7 @@ where S::Service: 'static, B: MessageBody, { - /// Create new `HttpService` instance. + /// Create new `HttpService` instance with default config. pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); @@ -45,6 +45,15 @@ where } } + /// Create new `HttpService` instance with config. + pub fn with_config>(cfg: ServiceConfig, service: F) -> Self { + H1Service { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } + /// Create builder for `HttpService` instance. pub fn build() -> H1ServiceBuilder { H1ServiceBuilder::new() From 65a313c78b16272bf7918459111fdb1117ad2ca2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Mar 2019 19:51:09 -0800 Subject: [PATCH 158/208] update utils dep --- Cargo.toml | 4 ++-- src/client/connector.rs | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 403f3303c..35342b8e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,10 +37,10 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix-service = "0.3.0" +actix-service = "0.3.1" actix-codec = "0.1.0" actix-connector = "0.3.0" -actix-utils = "0.3.0" +actix-utils = "0.3.1" base64 = "0.10" backtrace = "0.3" diff --git a/src/client/connector.rs b/src/client/connector.rs index 32ba50121..ccb5dbce5 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -2,7 +2,7 @@ use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; use actix_connector::{Resolver, TcpConnector}; -use actix_service::{Apply, Service, ServiceExt}; +use actix_service::{Service, ServiceExt}; use actix_utils::timeout::{TimeoutError, TimeoutService}; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; @@ -140,8 +140,8 @@ impl Connector { > + Clone { #[cfg(not(feature = "ssl"))] { - let connector = Apply::new( - TimeoutService::new(self.timeout), + let connector = TimeoutService::new( + self.timeout, self.resolver.map_err(ConnectorError::from).and_then( TcpConnector::default() .from_err() @@ -168,8 +168,8 @@ impl Connector { const H2: &[u8] = b"h2"; use actix_connector::ssl::OpensslConnector; - let ssl_service = Apply::new( - TimeoutService::new(self.timeout), + let ssl_service = TimeoutService::new( + self.timeout, self.resolver .clone() .map_err(ConnectorError::from) @@ -197,8 +197,8 @@ impl Connector { TimeoutError::Timeout => ConnectorError::Timeout, }); - let tcp_service = Apply::new( - TimeoutService::new(self.timeout), + let tcp_service = TimeoutService::new( + self.timeout, self.resolver.map_err(ConnectorError::from).and_then( TcpConnector::default() .from_err() From 3a456ec1489cc83a5a5bee74071d7139be44b8f6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Mar 2019 20:46:33 -0800 Subject: [PATCH 159/208] update actix-service dependency --- Cargo.toml | 5 +++-- src/h1/service.rs | 6 +++--- src/h2/service.rs | 6 +++--- test-server/Cargo.toml | 7 ++++--- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 35342b8e2..86a9c29cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix-service = "0.3.1" +actix-service = "0.3.2" actix-codec = "0.1.0" actix-connector = "0.3.0" actix-utils = "0.3.1" @@ -78,7 +78,8 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.1.0" -actix-server = { version = "0.3.0", features=["ssl"] } +#actix-server = { version = "0.3.0", features=["ssl"] } +actix-server = { git="https://github.com/actix/actix-net.git", features=["ssl"] } actix-connector = { version = "0.3.0", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" diff --git a/src/h1/service.rs b/src/h1/service.rs index 4c1fb9a82..acc7217b0 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -6,7 +6,7 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use futures::future::{ok, FutureResult}; -use futures::{try_ready, Async, Future, Poll, Stream}; +use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream}; use log::error; use crate::body::MessageBody; @@ -78,7 +78,7 @@ where fn new_service(&self, _: &()) -> Self::Future { H1ServiceResponse { - fut: self.srv.new_service(&()), + fut: self.srv.new_service(&()).into_future(), cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -216,7 +216,7 @@ where #[doc(hidden)] pub struct H1ServiceResponse { - fut: S::Future, + fut: ::Future, cfg: Option, _t: PhantomData<(T, B)>, } diff --git a/src/h2/service.rs b/src/h2/service.rs index fcfc0be22..583f5edda 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -7,7 +7,7 @@ use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use bytes::Bytes; use futures::future::{ok, FutureResult}; -use futures::{try_ready, Async, Future, Poll, Stream}; +use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream}; use h2::server::{self, Connection, Handshake}; use h2::RecvStream; use log::error; @@ -72,7 +72,7 @@ where fn new_service(&self, _: &()) -> Self::Future { H2ServiceResponse { - fut: self.srv.new_service(&()), + fut: self.srv.new_service(&()).into_future(), cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -230,7 +230,7 @@ where #[doc(hidden)] pub struct H2ServiceResponse { - fut: S::Future, + fut: ::Future, cfg: Option, _t: PhantomData<(T, B)>, } diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index cf4364c49..df56b8f3d 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -35,9 +35,10 @@ ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] actix-codec = "0.1" actix-rt = "0.1.0" actix-http = { path=".." } -actix-service = "0.3.0" -actix-server = "0.3.0" -actix-utils = "0.3.0" +actix-service = "0.3.2" +#actix-server = "0.3.0" +actix-server = { git="https://github.com/actix/actix-net.git" } +actix-utils = "0.3.2" base64 = "0.10" bytes = "0.4" From ce0b17259858a88ef7cec46f29053c617701c896 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 09:30:11 -0800 Subject: [PATCH 160/208] update actix-service --- Cargo.toml | 13 +++++++---- src/client/connector.rs | 50 ++++++++++++---------------------------- src/client/pool.rs | 15 +++--------- src/client/request.rs | 2 +- src/h1/dispatcher.rs | 14 +++++------ src/h1/service.rs | 35 ++++++++++++++-------------- src/h2/dispatcher.rs | 18 +++++++++------ src/h2/service.rs | 34 ++++++++++++++------------- src/service/senderror.rs | 12 ++++------ src/ws/client/service.rs | 11 ++++----- src/ws/service.rs | 6 ++--- src/ws/transport.rs | 10 ++++---- test-server/Cargo.toml | 6 +++-- test-server/src/lib.rs | 12 ++++------ 14 files changed, 105 insertions(+), 133 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 86a9c29cd..1705479bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,10 +37,14 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix-service = "0.3.2" +#actix-service = "0.3.2" actix-codec = "0.1.0" -actix-connector = "0.3.0" -actix-utils = "0.3.1" +#actix-connector = "0.3.0" +#actix-utils = "0.3.1" + +actix-connector = { git="https://github.com/actix/actix-net.git" } +actix-service = { git="https://github.com/actix/actix-net.git" } +actix-utils = { git="https://github.com/actix/actix-net.git" } base64 = "0.10" backtrace = "0.3" @@ -80,7 +84,8 @@ openssl = { version="0.10", optional = true } actix-rt = "0.1.0" #actix-server = { version = "0.3.0", features=["ssl"] } actix-server = { git="https://github.com/actix/actix-net.git", features=["ssl"] } -actix-connector = { version = "0.3.0", features=["ssl"] } +#actix-connector = { version = "0.3.0", features=["ssl"] } +actix-connector = { git="https://github.com/actix/actix-net.git", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" diff --git a/src/client/connector.rs b/src/client/connector.rs index ccb5dbce5..b35e6af91 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -133,11 +133,8 @@ impl Connector { /// Finish configuration process and create connector service. pub fn service( self, - ) -> impl Service< - Request = Connect, - Response = impl Connection, - Error = ConnectorError, - > + Clone { + ) -> impl Service + Clone + { #[cfg(not(feature = "ssl"))] { let connector = TimeoutService::new( @@ -314,12 +311,12 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Request = Connect, + Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, >, T2: Service< - Request = Connect, + Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, >, @@ -333,12 +330,12 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Request = Connect, + Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, > + Clone, T2: Service< - Request = Connect, + Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, > + Clone, @@ -351,22 +348,21 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Request = Connect, + Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, >, T2: Service< - Request = Connect, + Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, >, { - type Request = Connect; type Response = EitherConnection; type Error = ConnectorError; type Future = Either< @@ -401,23 +397,15 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io1, Protocol), - Error = ConnectorError, - >, + T: Service, { - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } impl Future for InnerConnectorResponseA where - T: Service< - Request = Connect, - Response = (Connect, Io1, Protocol), - Error = ConnectorError, - >, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { @@ -435,23 +423,15 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io2, Protocol), - Error = ConnectorError, - >, + T: Service, { - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } impl Future for InnerConnectorResponseB where - T: Service< - Request = Connect, - Response = (Connect, Io2, Protocol), - Error = ConnectorError, - >, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { diff --git a/src/client/pool.rs b/src/client/pool.rs index 188980cb3..425e89395 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -48,11 +48,7 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - >, + T: Service, { pub(crate) fn new( connector: T, @@ -88,16 +84,11 @@ where } } -impl Service for ConnectionPool +impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - >, + T: Service, { - type Request = Connect; type Response = IoConnection; type Error = ConnectorError; type Future = Either< diff --git a/src/client/request.rs b/src/client/request.rs index 7e971756d..637635d0f 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -176,7 +176,7 @@ where ) -> impl Future where B: 'static, - T: Service, + T: Service, I: Connection, { let Self { head, body } = self; diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 9ae8cd2a1..7024ce3af 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -36,14 +36,14 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher +pub struct Dispatcher + 'static, B: MessageBody> where S::Error: Debug, { inner: Option>, } -struct InnerDispatcher +struct InnerDispatcher + 'static, B: MessageBody> where S::Error: Debug, { @@ -67,13 +67,13 @@ enum DispatcherMessage { Error(Response<()>), } -enum State { +enum State, B: MessageBody> { None, ServiceCall(S::Future), SendPayload(ResponseBody), } -impl State { +impl, B: MessageBody> State { fn is_empty(&self) -> bool { if let State::None = self { true @@ -86,7 +86,7 @@ impl State { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, @@ -141,7 +141,7 @@ where impl InnerDispatcher where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, @@ -464,7 +464,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, diff --git a/src/h1/service.rs b/src/h1/service.rs index acc7217b0..1c4f1ae3e 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -28,14 +28,14 @@ pub struct H1Service { impl H1Service where - S: NewService, + S: NewService, S::Error: Debug, S::Response: Into>, S::Service: 'static, B: MessageBody, { /// Create new `HttpService` instance with default config. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H1Service { @@ -46,7 +46,10 @@ where } /// Create new `HttpService` instance with config. - pub fn with_config>(cfg: ServiceConfig, service: F) -> Self { + pub fn with_config>( + cfg: ServiceConfig, + service: F, + ) -> Self { H1Service { cfg, srv: service.into_new_service(), @@ -60,16 +63,15 @@ where } } -impl NewService for H1Service +impl NewService for H1Service where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService, S::Error: Debug, S::Response: Into>, S::Service: 'static, B: MessageBody, { - type Request = T; type Response = H1ServiceResult; type Error = DispatchError; type InitError = S::InitError; @@ -101,7 +103,7 @@ pub struct H1ServiceBuilder { impl H1ServiceBuilder where - S: NewService, + S: NewService, S::Error: Debug, { /// Create instance of `ServiceConfigBuilder` @@ -199,7 +201,7 @@ where pub fn finish(self, service: F) -> H1Service where B: MessageBody, - F: IntoNewService, + F: IntoNewService, { let cfg = ServiceConfig::new( self.keep_alive, @@ -215,7 +217,7 @@ where } #[doc(hidden)] -pub struct H1ServiceResponse { +pub struct H1ServiceResponse, B> { fut: ::Future, cfg: Option, _t: PhantomData<(T, B)>, @@ -224,7 +226,7 @@ pub struct H1ServiceResponse { impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService, S::Service: 'static, S::Error: Debug, S::Response: Into>, @@ -251,7 +253,7 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where - S: Service, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, @@ -265,15 +267,14 @@ where } } -impl Service for H1ServiceHandler +impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, { - type Request = T; type Response = H1ServiceResult; type Error = DispatchError; type Future = Dispatcher; @@ -307,11 +308,10 @@ where } } -impl NewService for OneRequest +impl NewService for OneRequest where T: AsyncRead + AsyncWrite, { - type Request = T; type Response = (Request, Framed); type Error = ParseError; type InitError = (); @@ -333,11 +333,10 @@ pub struct OneRequestService { _t: PhantomData, } -impl Service for OneRequestService +impl Service for OneRequestService where T: AsyncRead + AsyncWrite, { - type Request = T; type Response = (Request, Framed); type Error = ParseError; type Future = OneRequestServiceResponse; diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index ea8756d27..7f5409c68 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -38,7 +38,11 @@ bitflags! { } /// Dispatcher for HTTP/2 protocol -pub struct Dispatcher { +pub struct Dispatcher< + T: AsyncRead + AsyncWrite, + S: Service> + 'static, + B: MessageBody, +> { flags: Flags, service: CloneableService, connection: Connection, @@ -51,7 +55,7 @@ pub struct Dispatcher Dispatcher where T: AsyncRead + AsyncWrite, - S: Service> + 'static, + S: Service> + 'static, S::Error: Into + fmt::Debug, S::Response: Into>, B: MessageBody + 'static, @@ -93,7 +97,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service> + 'static, + S: Service> + 'static, S::Error: Into + fmt::Debug, S::Response: Into>, B: MessageBody + 'static, @@ -139,20 +143,20 @@ where } } -struct ServiceResponse { +struct ServiceResponse>, B> { state: ServiceResponseState, config: ServiceConfig, buffer: Option, } -enum ServiceResponseState { +enum ServiceResponseState>, B> { ServiceCall(S::Future, Option>), SendPayload(SendStream, ResponseBody), } impl ServiceResponse where - S: Service> + 'static, + S: Service> + 'static, S::Error: Into + fmt::Debug, S::Response: Into>, B: MessageBody + 'static, @@ -220,7 +224,7 @@ where impl Future for ServiceResponse where - S: Service> + 'static, + S: Service> + 'static, S::Error: Into + fmt::Debug, S::Response: Into>, B: MessageBody + 'static, diff --git a/src/h2/service.rs b/src/h2/service.rs index 583f5edda..e225e9fcb 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -31,14 +31,14 @@ pub struct H2Service { impl H2Service where - S: NewService>, + S: NewService>, S::Service: 'static, S::Error: Into + Debug + 'static, S::Response: Into>, B: MessageBody + 'static, { /// Create new `HttpService` instance. - pub fn new>(service: F) -> Self { + pub fn new>>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H2Service { @@ -54,16 +54,15 @@ where } } -impl NewService for H2Service +impl NewService for H2Service where T: AsyncRead + AsyncWrite, - S: NewService>, + S: NewService>, S::Service: 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody + 'static, { - type Request = T; type Response = (); type Error = DispatchError<()>; type InitError = S::InitError; @@ -95,7 +94,7 @@ pub struct H2ServiceBuilder { impl H2ServiceBuilder where - S: NewService>, + S: NewService>, S::Service: 'static, S::Error: Into + Debug + 'static, { @@ -213,7 +212,7 @@ where pub fn finish(self, service: F) -> H2Service where B: MessageBody, - F: IntoNewService, + F: IntoNewService>, { let cfg = ServiceConfig::new( self.keep_alive, @@ -229,7 +228,7 @@ where } #[doc(hidden)] -pub struct H2ServiceResponse { +pub struct H2ServiceResponse>, B> { fut: ::Future, cfg: Option, _t: PhantomData<(T, B)>, @@ -238,7 +237,7 @@ pub struct H2ServiceResponse { impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService>, + S: NewService>, S::Service: 'static, S::Response: Into>, S::Error: Into + Debug, @@ -265,7 +264,7 @@ pub struct H2ServiceHandler { impl H2ServiceHandler where - S: Service> + 'static, + S: Service> + 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody + 'static, @@ -279,15 +278,14 @@ where } } -impl Service for H2ServiceHandler +impl Service for H2ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service> + 'static, + S: Service> + 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody + 'static, { - type Request = T; type Response = (); type Error = DispatchError<()>; type Future = H2ServiceHandlerResponse; @@ -310,7 +308,11 @@ where } } -enum State { +enum State< + T: AsyncRead + AsyncWrite, + S: Service> + 'static, + B: MessageBody, +> { Incoming(Dispatcher), Handshake( Option>, @@ -322,7 +324,7 @@ enum State { pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service> + 'static, + S: Service> + 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody + 'static, @@ -333,7 +335,7 @@ where impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service> + 'static, + S: Service> + 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody, diff --git a/src/service/senderror.rs b/src/service/senderror.rs index 44d362593..8268c6660 100644 --- a/src/service/senderror.rs +++ b/src/service/senderror.rs @@ -22,12 +22,11 @@ where } } -impl NewService for SendError +impl NewService)>> for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, { - type Request = Result)>; type Response = R; type Error = (E, Framed); type InitError = (); @@ -39,12 +38,11 @@ where } } -impl Service for SendError +impl Service)>> for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, { - type Request = Result)>; type Response = R; type Error = (E, Framed); type Future = Either)>, SendErrorFut>; @@ -130,12 +128,11 @@ where } } -impl NewService for SendResponse +impl NewService<(Response, Framed)> for SendResponse where T: AsyncRead + AsyncWrite, B: MessageBody, { - type Request = (Response, Framed); type Response = Framed; type Error = Error; type InitError = (); @@ -147,12 +144,11 @@ where } } -impl Service for SendResponse +impl Service<(Response, Framed)> for SendResponse where T: AsyncRead + AsyncWrite, B: MessageBody, { - type Request = (Response, Framed); type Response = Framed; type Error = Error; type Future = SendResponseFut; diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 586873d19..c48b6e0c1 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -26,7 +26,7 @@ pub type DefaultClient = Client; /// WebSocket's client pub struct Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { connector: T, @@ -34,7 +34,7 @@ where impl Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { /// Create new websocket's client factory @@ -51,7 +51,7 @@ impl Default for Client { impl Clone for Client where - T: Service + Clone, + T: Service + Clone, T::Response: AsyncRead + AsyncWrite, { fn clone(&self) -> Self { @@ -61,13 +61,12 @@ where } } -impl Service for Client +impl Service for Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite + 'static, T::Future: 'static, { - type Request = Connect; type Response = Framed; type Error = ClientError; type Future = Either< diff --git a/src/ws/service.rs b/src/ws/service.rs index f3b066053..bbd9f7826 100644 --- a/src/ws/service.rs +++ b/src/ws/service.rs @@ -20,8 +20,7 @@ impl Default for VerifyWebSockets { } } -impl NewService for VerifyWebSockets { - type Request = (Request, Framed); +impl NewService<(Request, Framed)> for VerifyWebSockets { type Response = (Request, Framed); type Error = (HandshakeError, Framed); type InitError = (); @@ -33,8 +32,7 @@ impl NewService for VerifyWebSockets { } } -impl Service for VerifyWebSockets { - type Request = (Request, Framed); +impl Service<(Request, Framed)> for VerifyWebSockets { type Response = (Request, Framed); type Error = (HandshakeError, Framed); type Future = FutureResult; diff --git a/src/ws/transport.rs b/src/ws/transport.rs index da7782be5..6a4f4d227 100644 --- a/src/ws/transport.rs +++ b/src/ws/transport.rs @@ -7,7 +7,7 @@ use super::{Codec, Frame, Message}; pub struct Transport where - S: Service + 'static, + S: Service + 'static, T: AsyncRead + AsyncWrite, { inner: FramedTransport, @@ -16,17 +16,17 @@ where impl Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { - pub fn new>(io: T, service: F) -> Self { + pub fn new>(io: T, service: F) -> Self { Transport { inner: FramedTransport::new(Framed::new(io, Codec::new()), service), } } - pub fn with>(framed: Framed, service: F) -> Self { + pub fn with>(framed: Framed, service: F) -> Self { Transport { inner: FramedTransport::new(framed, service), } @@ -36,7 +36,7 @@ where impl Future for Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index df56b8f3d..6a401cc58 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -35,10 +35,12 @@ ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] actix-codec = "0.1" actix-rt = "0.1.0" actix-http = { path=".." } -actix-service = "0.3.2" +#actix-service = "0.3.2" +actix-service = { git="https://github.com/actix/actix-net.git" } #actix-server = "0.3.0" actix-server = { git="https://github.com/actix/actix-net.git" } -actix-utils = "0.3.2" +#actix-utils = "0.3.2" +actix-utils = { git="https://github.com/actix/actix-net.git" } base64 = "0.10" bytes = "0.4" diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index a13e86cf8..3afee682f 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -56,8 +56,7 @@ impl TestServer { pub fn new( factory: F, ) -> TestServerRuntime< - impl Service - + Clone, + impl Service + Clone, > { let (tx, rx) = mpsc::channel(); @@ -89,11 +88,8 @@ impl TestServer { } fn new_connector( - ) -> impl Service< - Request = Connect, - Response = impl Connection, - Error = ConnectorError, - > + Clone { + ) -> impl Service + Clone + { #[cfg(feature = "ssl")] { use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; @@ -206,7 +202,7 @@ impl TestServerRuntime { impl TestServerRuntime where - T: Service + Clone, + T: Service + Clone, T::Response: Connection, { /// Connect to websocket server at a given path From 01329af1c2c6f5cbeff5631598f772e68c51b65f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 10:18:46 -0800 Subject: [PATCH 161/208] fix non ssl code --- src/client/connector.rs | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index b35e6af91..d1fd802e9 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -238,11 +238,7 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - >, + T: Service, { pub(crate) tcp_pool: ConnectionPool, } @@ -250,11 +246,8 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - > + Clone, + T: Service + + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -263,20 +256,15 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - >, + T: Service, { - type Request = Connect; type Response = IoConnection; type Error = ConnectorError; type Future = Either< - as Service>::Future, + as Service>::Future, FutureResult, ConnectorError>, >; From 96477d42cb0c97660a89a7b9eea1076936712048 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 13:16:26 -0800 Subject: [PATCH 162/208] extend HttpMessage trait, add api to work with requests cookies --- Cargo.toml | 5 +-- src/client/response.rs | 19 +++++++++- src/h1/decoder.rs | 3 +- src/httpmessage.rs | 50 +++++++++++++++++++++++- src/message.rs | 16 ++++++++ src/request.rs | 42 +++++++++------------ src/response.rs | 86 +++++++++++++++++------------------------- src/ws/mod.rs | 1 + 8 files changed, 140 insertions(+), 82 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1705479bd..6ab1b0903 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,10 +28,7 @@ name = "actix_http" path = "src/lib.rs" [features] -default = ["session"] - -# sessions feature, session require "ring" crate and c compiler -session = ["cookie/secure"] +default = [] # openssl ssl = ["openssl", "actix-connector/ssl"] diff --git a/src/client/response.rs b/src/client/response.rs index 104d28ed7..236a63382 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,3 +1,4 @@ +use std::cell::{Ref, RefMut}; use std::fmt; use bytes::Bytes; @@ -5,6 +6,7 @@ use futures::{Poll, Stream}; use http::{HeaderMap, StatusCode, Version}; use crate::error::PayloadError; +use crate::extensions::Extensions; use crate::httpmessage::HttpMessage; use crate::message::{Head, Message, ResponseHead}; use crate::payload::{Payload, PayloadStream}; @@ -22,6 +24,18 @@ impl HttpMessage for ClientResponse { &self.head.headers } + fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.head.headers + } + + fn extensions(&self) -> Ref { + self.head.extensions() + } + + fn extensions_mut(&self) -> RefMut { + self.head.extensions_mut() + } + fn take_payload(&mut self) -> Payload { std::mem::replace(&mut self.payload, Payload::None) } @@ -30,8 +44,11 @@ impl HttpMessage for ClientResponse { impl ClientResponse { /// Create new Request instance pub fn new() -> ClientResponse { + let head: Message = Message::new(); + head.extensions_mut().clear(); + ClientResponse { - head: Message::new(), + head, payload: Payload::None, } } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 80bca94c5..77b76c242 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -163,7 +163,7 @@ impl MessageType for Request { } fn headers_mut(&mut self) -> &mut HeaderMap { - self.headers_mut() + &mut self.head_mut().headers } fn decode(src: &mut BytesMut) -> Result, ParseError> { @@ -832,6 +832,7 @@ mod tests { "GET /test HTTP/1.0\r\n\ connection: close\r\n\r\n", ); + let req = parse_ready!(&mut buf); assert_eq!(req.head().ctype, Some(ConnectionType::Close)); diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 79f29a723..17447af63 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,6 +1,8 @@ +use std::cell::{Ref, RefMut}; use std::str; use bytes::{Bytes, BytesMut}; +use cookie::Cookie; use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; use encoding::types::{DecoderTrap, Encoding}; @@ -12,12 +14,16 @@ use serde::de::DeserializeOwned; use serde_urlencoded; use crate::error::{ - ContentTypeError, ParseError, PayloadError, ReadlinesError, UrlencodedError, + ContentTypeError, CookieParseError, ParseError, PayloadError, ReadlinesError, + UrlencodedError, }; +use crate::extensions::Extensions; use crate::header::Header; use crate::json::JsonBody; use crate::payload::Payload; +struct Cookies(Vec>); + /// Trait that implements general purpose operations on http messages pub trait HttpMessage: Sized { /// Type of message payload stream @@ -26,9 +32,18 @@ pub trait HttpMessage: Sized { /// Read the message headers. fn headers(&self) -> &HeaderMap; + /// Mutable reference to the message's headers. + fn headers_mut(&mut self) -> &mut HeaderMap; + /// Message payload stream fn take_payload(&mut self) -> Payload; + /// Request's extensions container + fn extensions(&self) -> Ref; + + /// Mutable reference to a the request's extensions container + fn extensions_mut(&self) -> RefMut; + #[doc(hidden)] /// Get a header fn get_header(&self) -> Option @@ -100,6 +115,39 @@ pub trait HttpMessage: Sized { } } + /// Load request cookies. + #[inline] + fn cookies(&self) -> Result>>, CookieParseError> { + if self.extensions().get::().is_none() { + let mut cookies = Vec::new(); + for hdr in self.headers().get_all(header::COOKIE) { + let s = + str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; + for cookie_str in s.split(';').map(|s| s.trim()) { + if !cookie_str.is_empty() { + cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned()); + } + } + } + self.extensions_mut().insert(Cookies(cookies)); + } + Ok(Ref::map(self.extensions(), |ext| { + &ext.get::().unwrap().0 + })) + } + + /// Return request cookie. + fn cookie(&self, name: &str) -> Option> { + if let Ok(cookies) = self.cookies() { + for cookie in cookies.iter() { + if cookie.name() == name { + return Some(cookie.to_owned()); + } + } + } + None + } + /// Load http message body. /// /// By default only 256Kb payload reads to a memory, then diff --git a/src/message.rs b/src/message.rs index 3a1ac1306..f9dfe9736 100644 --- a/src/message.rs +++ b/src/message.rs @@ -125,6 +125,7 @@ pub struct ResponseHead { pub reason: Option<&'static str>, pub no_chunking: bool, pub(crate) ctype: Option, + pub(crate) extensions: RefCell, } impl Default for ResponseHead { @@ -136,10 +137,25 @@ impl Default for ResponseHead { reason: None, no_chunking: false, ctype: None, + extensions: RefCell::new(Extensions::new()), } } } +impl ResponseHead { + /// Message extensions + #[inline] + pub fn extensions(&self) -> Ref { + self.extensions.borrow() + } + + /// Mutable reference to a the message's extensions + #[inline] + pub fn extensions_mut(&self) -> RefMut { + self.extensions.borrow_mut() + } +} + impl Head for ResponseHead { fn clear(&mut self) { self.ctype = None; diff --git a/src/request.rs b/src/request.rs index e1b893f95..761a159d8 100644 --- a/src/request.rs +++ b/src/request.rs @@ -17,10 +17,28 @@ pub struct Request

    { impl

    HttpMessage for Request

    { type Stream = P; + #[inline] fn headers(&self) -> &HeaderMap { &self.head().headers } + #[inline] + fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.head_mut().headers + } + + /// Request extensions + #[inline] + fn extensions(&self) -> Ref { + self.head.extensions() + } + + /// Mutable reference to a the request's extensions + #[inline] + fn extensions_mut(&self) -> RefMut { + self.head.extensions_mut() + } + fn take_payload(&mut self) -> Payload

    { std::mem::replace(&mut self.payload, Payload::None) } @@ -119,30 +137,6 @@ impl

    Request

    { self.head().uri.path() } - #[inline] - /// Returns Request's headers. - pub fn headers(&self) -> &HeaderMap { - &self.head().headers - } - - #[inline] - /// Returns mutable Request's headers. - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head_mut().headers - } - - /// Request extensions - #[inline] - pub fn extensions(&self) -> Ref { - self.head.extensions() - } - - /// Mutable reference to a the request's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut { - self.head.extensions_mut() - } - /// Check if request requires connection upgrade pub fn upgrade(&self) -> bool { if let Some(conn) = self.head().headers.get(header::CONNECTION) { diff --git a/src/response.rs b/src/response.rs index 9b503de1b..649e7fd88 100644 --- a/src/response.rs +++ b/src/response.rs @@ -766,12 +766,14 @@ impl From for Response { #[cfg(test)] mod tests { + use time::Duration; + use super::*; use crate::body::Body; use crate::http; use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; - - // use test::TestRequest; + use crate::httpmessage::HttpMessage; + use crate::test::TestRequest; #[test] fn test_debug() { @@ -783,38 +785,39 @@ mod tests { assert!(dbg.contains("Response")); } - // #[test] - // fn test_response_cookies() { - // let req = TestRequest::default() - // .header(COOKIE, "cookie1=value1") - // .header(COOKIE, "cookie2=value2") - // .finish(); - // let cookies = req.cookies().unwrap(); + #[test] + fn test_response_cookies() { + let req = TestRequest::default() + .header(COOKIE, "cookie1=value1") + .header(COOKIE, "cookie2=value2") + .finish(); + let cookies = req.cookies().unwrap(); - // let resp = Response::Ok() - // .cookie( - // http::Cookie::build("name", "value") - // .domain("www.rust-lang.org") - // .path("/test") - // .http_only(true) - // .max_age(Duration::days(1)) - // .finish(), - // ).del_cookie(&cookies[0]) - // .finish(); + let resp = Response::Ok() + .cookie( + http::Cookie::build("name", "value") + .domain("www.rust-lang.org") + .path("/test") + .http_only(true) + .max_age(Duration::days(1)) + .finish(), + ) + .del_cookie(&cookies[0]) + .finish(); - // let mut val: Vec<_> = resp - // .headers() - // .get_all("Set-Cookie") - // .iter() - // .map(|v| v.to_str().unwrap().to_owned()) - // .collect(); - // val.sort(); - // assert!(val[0].starts_with("cookie1=; Max-Age=0;")); - // assert_eq!( - // val[1], - // "name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400" - // ); - // } + let mut val: Vec<_> = resp + .headers() + .get_all("Set-Cookie") + .iter() + .map(|v| v.to_str().unwrap().to_owned()) + .collect(); + val.sort(); + assert!(val[0].starts_with("cookie1=; Max-Age=0;")); + assert_eq!( + val[1], + "name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400" + ); + } #[test] fn test_update_response_cookies() { @@ -871,25 +874,6 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } - // #[test] - // fn test_content_encoding() { - // let resp = Response::build(StatusCode::OK).finish(); - // assert_eq!(resp.content_encoding(), None); - - // #[cfg(feature = "brotli")] - // { - // let resp = Response::build(StatusCode::OK) - // .content_encoding(ContentEncoding::Br) - // .finish(); - // assert_eq!(resp.content_encoding(), Some(ContentEncoding::Br)); - // } - - // let resp = Response::build(StatusCode::OK) - // .content_encoding(ContentEncoding::Gzip) - // .finish(); - // assert_eq!(resp.content_encoding(), Some(ContentEncoding::Gzip)); - // } - #[test] fn test_json() { let resp = Response::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]); diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 2d629c73b..a8de59dd4 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -9,6 +9,7 @@ use derive_more::{Display, From}; use http::{header, Method, StatusCode}; use crate::error::ResponseError; +use crate::httpmessage::HttpMessage; use crate::request::Request; use crate::response::{Response, ResponseBuilder}; From 200cae19a9e21e2d24e57bd37f434cfce6c36a7d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 14:39:06 -0800 Subject: [PATCH 163/208] add HttpMessage impl &mut T --- src/httpmessage.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 17447af63..d95f82f5d 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -270,6 +270,37 @@ pub trait HttpMessage: Sized { } } +impl<'a, T> HttpMessage for &'a mut T +where + T: HttpMessage, +{ + type Stream = T::Stream; + + fn headers(&self) -> &HeaderMap { + (**self).headers() + } + + /// Mutable reference to the message's headers. + fn headers_mut(&mut self) -> &mut HeaderMap { + (**self).headers_mut() + } + + /// Message payload stream + fn take_payload(&mut self) -> Payload { + (**self).take_payload() + } + + /// Request's extensions container + fn extensions(&self) -> Ref { + (**self).extensions() + } + + /// Mutable reference to a the request's extensions container + fn extensions_mut(&self) -> RefMut { + (**self).extensions_mut() + } +} + /// Stream to read request line by line. pub struct Readlines { stream: Payload, From 0d2116156a0fa90590e0be2e496e9406db8aa3f3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 17:24:24 -0800 Subject: [PATCH 164/208] Messagebody constraint is not required from Response::into_body --- src/response.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/response.rs b/src/response.rs index 649e7fd88..8d32570a5 100644 --- a/src/response.rs +++ b/src/response.rs @@ -80,7 +80,7 @@ impl Response { } /// Convert response to response with body - pub fn into_body(self) -> Response { + pub fn into_body(self) -> Response { let b = match self.body { ResponseBody::Body(b) => b, ResponseBody::Other(b) => b, From 496ee8d0395d8ac7714b6c60d012e6e6283e8422 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 18:14:30 -0800 Subject: [PATCH 165/208] remove more MessageBody constraints from Response --- src/response.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/response.rs b/src/response.rs index 8d32570a5..277890e40 100644 --- a/src/response.rs +++ b/src/response.rs @@ -212,7 +212,7 @@ impl Response { } /// Set a body - pub(crate) fn set_body(self, body: B2) -> Response { + pub(crate) fn set_body(self, body: B2) -> Response { Response { head: self.head, body: ResponseBody::Body(body), @@ -230,10 +230,7 @@ impl Response { } /// Set a body and return previous body value - pub(crate) fn replace_body( - self, - body: B2, - ) -> (Response, ResponseBody) { + pub(crate) fn replace_body(self, body: B2) -> (Response, ResponseBody) { ( Response { head: self.head, @@ -245,7 +242,7 @@ impl Response { } /// Set a body and return previous body value - pub fn map_body(mut self, f: F) -> Response + pub fn map_body(mut self, f: F) -> Response where F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, { @@ -597,7 +594,7 @@ impl ResponseBuilder { /// Set a body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. - pub fn message_body(&mut self, body: B) -> Response { + pub fn message_body(&mut self, body: B) -> Response { if let Some(e) = self.err.take() { return Response::from(Error::from(e)).into_body(); } From d85468f7e107ef5116e3fad40dc9b9ed57bfdb04 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 19:07:07 -0800 Subject: [PATCH 166/208] do not expose headers_mut via HttpMessage --- src/client/response.rs | 4 ---- src/httpmessage.rs | 8 -------- src/request.rs | 10 +++++----- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/client/response.rs b/src/client/response.rs index 236a63382..7c6cdf643 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -24,10 +24,6 @@ impl HttpMessage for ClientResponse { &self.head.headers } - fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head.headers - } - fn extensions(&self) -> Ref { self.head.extensions() } diff --git a/src/httpmessage.rs b/src/httpmessage.rs index d95f82f5d..3c049c09b 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -32,9 +32,6 @@ pub trait HttpMessage: Sized { /// Read the message headers. fn headers(&self) -> &HeaderMap; - /// Mutable reference to the message's headers. - fn headers_mut(&mut self) -> &mut HeaderMap; - /// Message payload stream fn take_payload(&mut self) -> Payload; @@ -280,11 +277,6 @@ where (**self).headers() } - /// Mutable reference to the message's headers. - fn headers_mut(&mut self) -> &mut HeaderMap { - (**self).headers_mut() - } - /// Message payload stream fn take_payload(&mut self) -> Payload { (**self).take_payload() diff --git a/src/request.rs b/src/request.rs index 761a159d8..0ea251ea0 100644 --- a/src/request.rs +++ b/src/request.rs @@ -22,11 +22,6 @@ impl

    HttpMessage for Request

    { &self.head().headers } - #[inline] - fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head_mut().headers - } - /// Request extensions #[inline] fn extensions(&self) -> Ref { @@ -107,6 +102,11 @@ impl

    Request

    { &mut *self.head } + /// Mutable reference to the message's headers. + fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.head_mut().headers + } + /// Request's uri. #[inline] pub fn uri(&self) -> &Uri { From 34c8b95a35a8830e70a888cbb063678ee8b16b32 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 21:15:18 -0800 Subject: [PATCH 167/208] allow to extract body from response --- src/body.rs | 6 ++++++ src/response.rs | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/src/body.rs b/src/body.rs index 1f218c4ba..d72f6c378 100644 --- a/src/body.rs +++ b/src/body.rs @@ -59,6 +59,12 @@ impl ResponseBody { } } +impl ResponseBody { + pub fn take_body(&mut self) -> ResponseBody { + std::mem::replace(self, ResponseBody::Other(Body::None)) + } +} + impl ResponseBody { pub fn as_ref(&self) -> Option<&B> { if let ResponseBody::Body(ref b) = self { diff --git a/src/response.rs b/src/response.rs index 277890e40..4e1fe2142 100644 --- a/src/response.rs +++ b/src/response.rs @@ -254,6 +254,11 @@ impl Response { error: self.error, } } + + /// Extract response body + pub fn take_body(&mut self) -> ResponseBody { + self.body.take_body() + } } impl fmt::Debug for Response { From 889d67a356a163fb444a4e7921394c765695427a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 21:19:12 -0800 Subject: [PATCH 168/208] add Stream impl for ResponseBody --- src/body.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/body.rs b/src/body.rs index d72f6c378..b7e8ec98a 100644 --- a/src/body.rs +++ b/src/body.rs @@ -91,6 +91,15 @@ impl MessageBody for ResponseBody { } } +impl Stream for ResponseBody { + type Item = Bytes; + type Error = Error; + + fn poll(&mut self) -> Poll, Self::Error> { + self.poll_next() + } +} + /// Represents various types of http message body. pub enum Body { /// Empty response. `Content-Length` header is not set. From ad08e856d736897c6a591f87ad315806af44fac3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 10:30:17 -0800 Subject: [PATCH 169/208] update actix-rt --- Cargo.toml | 2 +- test-server/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6ab1b0903..6493404dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false } openssl = { version="0.10", optional = true } [dev-dependencies] -actix-rt = "0.1.0" +actix-rt = "0.2.0" #actix-server = { version = "0.3.0", features=["ssl"] } actix-server = { git="https://github.com/actix/actix-net.git", features=["ssl"] } #actix-connector = { version = "0.3.0", features=["ssl"] } diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 6a401cc58..554ab20e3 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -33,7 +33,7 @@ ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] [dependencies] actix-codec = "0.1" -actix-rt = "0.1.0" +actix-rt = "0.2.0" actix-http = { path=".." } #actix-service = "0.3.2" actix-service = { git="https://github.com/actix/actix-net.git" } From b689bb92608a51c708edd764111702134e1cf788 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 11:45:33 -0800 Subject: [PATCH 170/208] add failure support --- Cargo.toml | 9 +++++- src/error.rs | 78 +++++++++++++++------------------------------------- 2 files changed, 30 insertions(+), 57 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6493404dd..a0eb17312 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,11 +28,15 @@ name = "actix_http" path = "src/lib.rs" [features] -default = [] +default = ["fail"] # openssl ssl = ["openssl", "actix-connector/ssl"] +# failure integration. it is on by default, it will be off in future versions +# actix itself does not use failure anymore +fail = ["failure"] + [dependencies] #actix-service = "0.3.2" actix-codec = "0.1.0" @@ -77,6 +81,9 @@ trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false } # openssl openssl = { version="0.10", optional = true } +# failure is optional +failure = { version = "0.1.5", optional = true } + [dev-dependencies] actix-rt = "0.2.0" #actix-server = { version = "0.3.0", features=["ssl"] } diff --git a/src/error.rs b/src/error.rs index cd5cabaa6..4762f27ff 100644 --- a/src/error.rs +++ b/src/error.rs @@ -66,28 +66,6 @@ impl Error { } } - // /// Attempts to downcast this `Error` to a particular `Fail` type by - // /// reference. - // /// - // /// If the underlying error is not of type `T`, this will return `None`. - // pub fn downcast_ref(&self) -> Option<&T> { - // // in the most trivial way the cause is directly of the requested type. - // if let Some(rv) = Fail::downcast_ref(self.cause.as_fail()) { - // return Some(rv); - // } - - // // in the more complex case the error has been constructed from a failure - // // error. This happens because we implement From by - // // calling compat() and then storing it here. In failure this is - // // represented by a failure::Error being wrapped in a failure::Compat. - // // - // // So we first downcast into that compat, to then further downcast through - // // the failure's Error downcasting system into the original failure. - // let compat: Option<&failure::Compat> = - // Fail::downcast_ref(self.cause.as_fail()); - // compat.and_then(|e| e.get_ref().downcast_ref()) - // } - /// Converts error to a response instance and set error message as response body pub fn response_with_message(self) -> Response { let message = format!("{}", self); @@ -96,28 +74,6 @@ impl Error { } } -// /// Helper trait to downcast a response error into a fail. -// /// -// /// This is currently not exposed because it's unclear if this is the best way -// /// to achieve the downcasting on `Error` for which this is needed. -// #[doc(hidden)] -// pub trait InternalResponseErrorAsFail { -// #[doc(hidden)] -// fn as_fail(&self) -> &Fail; -// #[doc(hidden)] -// fn as_mut_fail(&mut self) -> &mut Fail; -// } - -// #[doc(hidden)] -// impl InternalResponseErrorAsFail for T { -// fn as_fail(&self) -> &Fail { -// self -// } -// fn as_mut_fail(&mut self) -> &mut Fail { -// self -// } -// } - /// Error that can be converted to `Response` pub trait ResponseError: fmt::Debug + fmt::Display { /// Create response for error @@ -176,18 +132,6 @@ impl From for Error { } } -// /// Compatibility for `failure::Error` -// impl ResponseError for failure::Compat where -// T: fmt::Display + fmt::Debug + Sync + Send + 'static -// { -// } - -// impl From for Error { -// fn from(err: failure::Error) -> Error { -// err.compat().into() -// } -// } - /// Return `GATEWAY_TIMEOUT` for `TimeoutError` impl ResponseError for TimeoutError { fn error_response(&self) -> Response { @@ -1023,6 +967,28 @@ where InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into() } +#[cfg(feature = "fail")] +mod failure_integration { + use super::*; + use failure::{self, Fail}; + + /// Compatibility for `failure::Error` + impl ResponseError for failure::Compat + where + T: fmt::Display + fmt::Debug + Sync + Send + 'static, + { + fn error_response(&self) -> Response { + Response::new(StatusCode::INTERNAL_SERVER_ERROR) + } + } + + impl From for Error { + fn from(err: failure::Error) -> Error { + err.compat().into() + } + } +} + #[cfg(test)] mod tests { use super::*; From e25483a0d58ebf6b688fb195d94471223a082cc1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 21:12:35 -0800 Subject: [PATCH 171/208] fix warnings --- src/error.rs | 12 +----------- src/header/common/content_disposition.rs | 4 ++-- src/header/shared/quality_item.rs | 2 +- src/request.rs | 2 +- src/test.rs | 2 -- src/ws/client/mod.rs | 14 -------------- 6 files changed, 5 insertions(+), 31 deletions(-) diff --git a/src/error.rs b/src/error.rs index 4762f27ff..a3037b985 100644 --- a/src/error.rs +++ b/src/error.rs @@ -970,23 +970,13 @@ where #[cfg(feature = "fail")] mod failure_integration { use super::*; - use failure::{self, Fail}; /// Compatibility for `failure::Error` - impl ResponseError for failure::Compat - where - T: fmt::Display + fmt::Debug + Sync + Send + 'static, - { + impl ResponseError for failure::Error { fn error_response(&self) -> Response { Response::new(StatusCode::INTERNAL_SERVER_ERROR) } } - - impl From for Error { - fn from(err: failure::Error) -> Error { - err.compat().into() - } - } } #[cfg(test)] diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs index e04f9c89f..700400dad 100644 --- a/src/header/common/content_disposition.rs +++ b/src/header/common/content_disposition.rs @@ -27,7 +27,7 @@ fn split_once(haystack: &str, needle: char) -> (&str, &str) { /// first part and the left of the last part. fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) { let (first, last) = split_once(haystack, needle); - (first.trim_right(), last.trim_left()) + (first.trim_end(), last.trim_start()) } /// The implied disposition of the content of the HTTP body. @@ -324,7 +324,7 @@ impl ContentDisposition { } } left = &left[end.ok_or(crate::error::ParseError::Header)? + 1..]; - left = split_once(left, ';').1.trim_left(); + left = split_once(left, ';').1.trim_start(); // In fact, it should not be Err if the above code is correct. String::from_utf8(quoted_string) .map_err(|_| crate::error::ParseError::Header)? diff --git a/src/header/shared/quality_item.rs b/src/header/shared/quality_item.rs index 07c206581..fc3930c5e 100644 --- a/src/header/shared/quality_item.rs +++ b/src/header/shared/quality_item.rs @@ -58,7 +58,7 @@ impl fmt::Display for QualityItem { match self.quality.0 { 1000 => Ok(()), 0 => f.write_str("; q=0"), - x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')), + x => write!(f, "; q=0.{}", format!("{:03}", x).trim_end_matches('0')), } } } diff --git a/src/request.rs b/src/request.rs index 0ea251ea0..a645c7aeb 100644 --- a/src/request.rs +++ b/src/request.rs @@ -103,7 +103,7 @@ impl

    Request

    { } /// Mutable reference to the message's headers. - fn headers_mut(&mut self) -> &mut HeaderMap { + pub fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.head_mut().headers } diff --git a/src/test.rs b/src/test.rs index 4b925d020..e2e0fd76e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -46,7 +46,6 @@ struct Inner { headers: HeaderMap, _cookies: Option>>, payload: Option, - prefix: u16, } impl Default for TestRequest { @@ -58,7 +57,6 @@ impl Default for TestRequest { headers: HeaderMap::new(), _cookies: None, payload: None, - prefix: 0, })) } } diff --git a/src/ws/client/mod.rs b/src/ws/client/mod.rs index 12c7229b9..8e59e846d 100644 --- a/src/ws/client/mod.rs +++ b/src/ws/client/mod.rs @@ -25,20 +25,6 @@ impl Protocol { } } - fn is_http(self) -> bool { - match self { - Protocol::Https | Protocol::Http => true, - _ => false, - } - } - - fn is_secure(self) -> bool { - match self { - Protocol::Https | Protocol::Wss => true, - _ => false, - } - } - fn port(self) -> u16 { match self { Protocol::Http | Protocol::Ws => 80, From 3b069e0568cbd2ecc129afd0ace04a968cc4cb0c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 22:56:34 -0800 Subject: [PATCH 172/208] added combined http1/2 service --- Cargo.toml | 3 +- src/error.rs | 25 +-- src/h1/dispatcher.rs | 53 +++-- src/h1/mod.rs | 27 +-- src/h1/service.rs | 15 +- src/h2/dispatcher.rs | 28 ++- src/h2/mod.rs | 18 +- src/h2/service.rs | 72 +++---- src/lib.rs | 1 + src/service/mod.rs | 2 + src/service/service.rs | 446 +++++++++++++++++++++++++++++++++++++++++ test-server/src/lib.rs | 2 +- tests/test_server.rs | 52 ++++- 13 files changed, 575 insertions(+), 169 deletions(-) create mode 100644 src/service/service.rs diff --git a/Cargo.toml b/Cargo.toml index a0eb17312..9d55b85c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,8 @@ fail = ["failure"] [dependencies] #actix-service = "0.3.2" -actix-codec = "0.1.0" +actix-codec = "0.1.1" + #actix-connector = "0.3.0" #actix-utils = "0.3.1" diff --git a/src/error.rs b/src/error.rs index a3037b985..696162f86 100644 --- a/src/error.rs +++ b/src/error.rs @@ -328,12 +328,11 @@ impl ResponseError for cookie::ParseError { } } -#[derive(Debug, Display)] +#[derive(Debug, Display, From)] /// A set of errors that can occur during dispatching http requests -pub enum DispatchError { +pub enum DispatchError { /// Service error - #[display(fmt = "Service specific error: {:?}", _0)] - Service(E), + Service, /// An `io::Error` that occurred while trying to read or write to a network /// stream. @@ -373,24 +372,6 @@ pub enum DispatchError { Unknown, } -impl From for DispatchError { - fn from(err: ParseError) -> Self { - DispatchError::Parse(err) - } -} - -impl From for DispatchError { - fn from(err: io::Error) -> Self { - DispatchError::Io(err) - } -} - -impl From for DispatchError { - fn from(err: h2::Error) -> Self { - DispatchError::H2(err) - } -} - /// A set of error that can occure during parsing content type #[derive(PartialEq, Debug, Display)] pub enum ContentTypeError { diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 7024ce3af..42ab33e79 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -20,7 +20,7 @@ use crate::response::Response; use super::codec::Codec; use super::payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; -use super::{H1ServiceResult, Message, MessageType}; +use super::{Message, MessageType}; const MAX_PIPELINED_MESSAGES: usize = 16; @@ -50,7 +50,7 @@ where service: CloneableService, flags: Flags, framed: Framed, - error: Option>, + error: Option, config: ServiceConfig, state: State, @@ -93,12 +93,17 @@ where { /// Create http/1 dispatcher. pub fn new(stream: T, config: ServiceConfig, service: CloneableService) -> Self { - Dispatcher::with_timeout(stream, config, None, service) + Dispatcher::with_timeout( + Framed::new(stream, Codec::new(config.clone())), + config, + None, + service, + ) } /// Create http/1 dispatcher with slow request timeout. pub fn with_timeout( - stream: T, + framed: Framed, config: ServiceConfig, timeout: Option, service: CloneableService, @@ -109,7 +114,6 @@ where } else { Flags::empty() }; - let framed = Framed::new(stream, Codec::new(config.clone())); // keep-alive timer let (ka_expire, ka_timer) = if let Some(delay) = timeout { @@ -167,7 +171,7 @@ where } /// Flush stream - fn poll_flush(&mut self) -> Poll> { + fn poll_flush(&mut self) -> Poll { if !self.framed.is_write_buf_empty() { match self.framed.poll_complete() { Ok(Async::NotReady) => Ok(Async::NotReady), @@ -192,7 +196,7 @@ where &mut self, message: Response<()>, body: ResponseBody, - ) -> Result, DispatchError> { + ) -> Result, DispatchError> { self.framed .force_send(Message::Item((message, body.length()))) .map_err(|err| { @@ -210,7 +214,7 @@ where } } - fn poll_response(&mut self) -> Result<(), DispatchError> { + fn poll_response(&mut self) -> Result<(), DispatchError> { let mut retry = self.can_read(); loop { let state = match mem::replace(&mut self.state, State::None) { @@ -225,7 +229,7 @@ where None => None, }, State::ServiceCall(mut fut) => { - match fut.poll().map_err(DispatchError::Service)? { + match fut.poll().map_err(|_| DispatchError::Service)? { Async::Ready(res) => { let (res, body) = res.into().replace_body(()); Some(self.send_response(res, body)?) @@ -283,12 +287,9 @@ where Ok(()) } - fn handle_request( - &mut self, - req: Request, - ) -> Result, DispatchError> { + fn handle_request(&mut self, req: Request) -> Result, DispatchError> { let mut task = self.service.call(req); - match task.poll().map_err(DispatchError::Service)? { + match task.poll().map_err(|_| DispatchError::Service)? { Async::Ready(res) => { let (res, body) = res.into().replace_body(()); self.send_response(res, body) @@ -298,7 +299,7 @@ where } /// Process one incoming requests - pub(self) fn poll_request(&mut self) -> Result> { + pub(self) fn poll_request(&mut self) -> Result { // limit a mount of non processed requests if self.messages.len() >= MAX_PIPELINED_MESSAGES { return Ok(false); @@ -400,7 +401,7 @@ where } /// keep-alive timer - fn poll_keepalive(&mut self) -> Result<(), DispatchError> { + fn poll_keepalive(&mut self) -> Result<(), DispatchError> { if self.ka_timer.is_none() { return Ok(()); } @@ -469,8 +470,8 @@ where S::Response: Into>, B: MessageBody, { - type Item = H1ServiceResult; - type Error = DispatchError; + type Item = (); + type Error = DispatchError; #[inline] fn poll(&mut self) -> Poll { @@ -490,7 +491,7 @@ where } if inner.flags.contains(Flags::DISCONNECTED) { - return Ok(Async::Ready(H1ServiceResult::Disconnected)); + return Ok(Async::Ready(())); } // keep-alive and stream errors @@ -523,14 +524,12 @@ where }; let mut inner = self.inner.take().unwrap(); - if shutdown { - Ok(Async::Ready(H1ServiceResult::Shutdown( - inner.framed.into_inner(), - ))) - } else { - let req = inner.unhandled.take().unwrap(); - Ok(Async::Ready(H1ServiceResult::Unhandled(req, inner.framed))) - } + + // TODO: shutdown + Ok(Async::Ready(())) + //Ok(Async::Ready(HttpServiceResult::Shutdown( + // inner.framed.into_inner(), + //))) } } diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 9054c2665..e3d63c521 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -1,7 +1,4 @@ //! HTTP/1 implementation -use std::fmt; - -use actix_codec::Framed; use bytes::Bytes; mod client; @@ -18,29 +15,6 @@ pub use self::dispatcher::Dispatcher; pub use self::payload::{Payload, PayloadBuffer}; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; -use crate::request::Request; - -/// H1 service response type -pub enum H1ServiceResult { - Disconnected, - Shutdown(T), - Unhandled(Request, Framed), -} - -impl fmt::Debug for H1ServiceResult { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - H1ServiceResult::Disconnected => write!(f, "H1ServiceResult::Disconnected"), - H1ServiceResult::Shutdown(ref v) => { - write!(f, "H1ServiceResult::Shutdown({:?})", v) - } - H1ServiceResult::Unhandled(ref req, _) => { - write!(f, "H1ServiceResult::Unhandled({:?})", req) - } - } - } -} - #[derive(Debug)] /// Codec message pub enum Message { @@ -67,6 +41,7 @@ pub enum MessageType { #[cfg(test)] mod tests { use super::*; + use crate::request::Request; impl Message { pub fn message(self) -> Request { diff --git a/src/h1/service.rs b/src/h1/service.rs index 1c4f1ae3e..229229e69 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -17,7 +17,7 @@ use crate::response::Response; use super::codec::Codec; use super::dispatcher::Dispatcher; -use super::{H1ServiceResult, Message}; +use super::Message; /// `NewService` implementation for HTTP1 transport pub struct H1Service { @@ -72,8 +72,8 @@ where S::Service: 'static, B: MessageBody, { - type Response = H1ServiceResult; - type Error = DispatchError; + type Response = (); + type Error = DispatchError; type InitError = S::InitError; type Service = H1ServiceHandler; type Future = H1ServiceResponse; @@ -275,12 +275,15 @@ where S::Response: Into>, B: MessageBody, { - type Response = H1ServiceResult; - type Error = DispatchError; + type Response = (); + type Error = DispatchError; type Future = Dispatcher; fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.srv.poll_ready().map_err(DispatchError::Service) + self.srv.poll_ready().map_err(|e| { + log::error!("Http service readiness error: {:?}", e); + DispatchError::Service + }) } fn call(&mut self, req: T) -> Self::Future { diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index 7f5409c68..be813bd57 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -26,8 +26,6 @@ use crate::payload::Payload; use crate::request::Request; use crate::response::Response; -use super::H2ServiceResult; - const CHUNK_SIZE: usize = 16_384; bitflags! { @@ -40,7 +38,7 @@ bitflags! { /// Dispatcher for HTTP/2 protocol pub struct Dispatcher< T: AsyncRead + AsyncWrite, - S: Service> + 'static, + S: Service + 'static, B: MessageBody, > { flags: Flags, @@ -55,8 +53,8 @@ pub struct Dispatcher< impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service> + 'static, - S::Error: Into + fmt::Debug, + S: Service + 'static, + S::Error: fmt::Debug, S::Response: Into>, B: MessageBody + 'static, { @@ -97,13 +95,13 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service> + 'static, - S::Error: Into + fmt::Debug, + S: Service + 'static, + S::Error: fmt::Debug, S::Response: Into>, B: MessageBody + 'static, { type Item = (); - type Error = DispatchError<()>; + type Error = DispatchError; #[inline] fn poll(&mut self) -> Poll { @@ -143,21 +141,21 @@ where } } -struct ServiceResponse>, B> { +struct ServiceResponse, B> { state: ServiceResponseState, config: ServiceConfig, buffer: Option, } -enum ServiceResponseState>, B> { +enum ServiceResponseState, B> { ServiceCall(S::Future, Option>), SendPayload(SendStream, ResponseBody), } impl ServiceResponse where - S: Service> + 'static, - S::Error: Into + fmt::Debug, + S: Service + 'static, + S::Error: fmt::Debug, S::Response: Into>, B: MessageBody + 'static, { @@ -224,8 +222,8 @@ where impl Future for ServiceResponse where - S: Service> + 'static, - S::Error: Into + fmt::Debug, + S: Service + 'static, + S::Error: fmt::Debug, S::Response: Into>, B: MessageBody + 'static, { @@ -258,7 +256,7 @@ where } Ok(Async::NotReady) => Ok(Async::NotReady), Err(e) => { - let res: Response = e.into().into(); + let res: Response = Response::InternalServerError().finish(); let (res, body) = res.replace_body(()); let mut send = send.take().unwrap(); diff --git a/src/h2/mod.rs b/src/h2/mod.rs index 55e057607..c5972123f 100644 --- a/src/h2/mod.rs +++ b/src/h2/mod.rs @@ -9,26 +9,10 @@ use h2::RecvStream; mod dispatcher; mod service; +pub use self::dispatcher::Dispatcher; pub use self::service::H2Service; use crate::error::PayloadError; -/// H1 service response type -pub enum H2ServiceResult { - Disconnected, - Shutdown(T), -} - -impl fmt::Debug for H2ServiceResult { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - H2ServiceResult::Disconnected => write!(f, "H2ServiceResult::Disconnected"), - H2ServiceResult::Shutdown(ref v) => { - write!(f, "H2ServiceResult::Shutdown({:?})", v) - } - } - } -} - /// H2 receive stream pub struct Payload { pl: RecvStream, diff --git a/src/h2/service.rs b/src/h2/service.rs index e225e9fcb..a47f507b5 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -20,7 +20,6 @@ use crate::request::Request; use crate::response::Response; use super::dispatcher::Dispatcher; -use super::H2ServiceResult; /// `NewService` implementation for HTTP2 transport pub struct H2Service { @@ -31,14 +30,14 @@ pub struct H2Service { impl H2Service where - S: NewService>, + S: NewService, S::Service: 'static, - S::Error: Into + Debug + 'static, + S::Error: Debug + 'static, S::Response: Into>, B: MessageBody + 'static, { /// Create new `HttpService` instance. - pub fn new>>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H2Service { @@ -57,14 +56,14 @@ where impl NewService for H2Service where T: AsyncRead + AsyncWrite, - S: NewService>, + S: NewService, S::Service: 'static, - S::Error: Into + Debug, + S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { type Response = (); - type Error = DispatchError<()>; + type Error = DispatchError; type InitError = S::InitError; type Service = H2ServiceHandler; type Future = H2ServiceResponse; @@ -94,9 +93,9 @@ pub struct H2ServiceBuilder { impl H2ServiceBuilder where - S: NewService>, + S: NewService, S::Service: 'static, - S::Error: Into + Debug + 'static, + S::Error: Debug + 'static, { /// Create instance of `H2ServiceBuilder` pub fn new() -> H2ServiceBuilder { @@ -189,30 +188,11 @@ where self } - // #[cfg(feature = "ssl")] - // /// Configure alpn protocols for SslAcceptorBuilder. - // pub fn configure_openssl( - // builder: &mut openssl::ssl::SslAcceptorBuilder, - // ) -> io::Result<()> { - // let protos: &[u8] = b"\x02h2"; - // builder.set_alpn_select_callback(|_, protos| { - // const H2: &[u8] = b"\x02h2"; - // if protos.windows(3).any(|window| window == H2) { - // Ok(b"h2") - // } else { - // Err(openssl::ssl::AlpnError::NOACK) - // } - // }); - // builder.set_alpn_protos(&protos)?; - - // Ok(()) - // } - /// Finish service configuration and create `H1Service` instance. pub fn finish(self, service: F) -> H2Service where B: MessageBody, - F: IntoNewService>, + F: IntoNewService, { let cfg = ServiceConfig::new( self.keep_alive, @@ -228,7 +208,7 @@ where } #[doc(hidden)] -pub struct H2ServiceResponse>, B> { +pub struct H2ServiceResponse, B> { fut: ::Future, cfg: Option, _t: PhantomData<(T, B)>, @@ -237,10 +217,10 @@ pub struct H2ServiceResponse>, B> { impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService>, + S: NewService, S::Service: 'static, S::Response: Into>, - S::Error: Into + Debug, + S::Error: Debug, B: MessageBody + 'static, { type Item = H2ServiceHandler; @@ -264,8 +244,8 @@ pub struct H2ServiceHandler { impl H2ServiceHandler where - S: Service> + 'static, - S::Error: Into + Debug, + S: Service + 'static, + S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { @@ -281,19 +261,19 @@ where impl Service for H2ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service> + 'static, - S::Error: Into + Debug, + S: Service + 'static, + S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { type Response = (); - type Error = DispatchError<()>; + type Error = DispatchError; type Future = H2ServiceHandlerResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.srv.poll_ready().map_err(|e| { error!("Service readiness error: {:?}", e); - DispatchError::Service(()) + DispatchError::Service }) } @@ -308,11 +288,7 @@ where } } -enum State< - T: AsyncRead + AsyncWrite, - S: Service> + 'static, - B: MessageBody, -> { +enum State + 'static, B: MessageBody> { Incoming(Dispatcher), Handshake( Option>, @@ -324,8 +300,8 @@ enum State< pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service> + 'static, - S::Error: Into + Debug, + S: Service + 'static, + S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { @@ -335,13 +311,13 @@ where impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service> + 'static, - S::Error: Into + Debug, + S: Service + 'static, + S::Error: Debug, S::Response: Into>, B: MessageBody, { type Item = (); - type Error = DispatchError<()>; + type Error = DispatchError; fn poll(&mut self) -> Poll { match self.state { diff --git a/src/lib.rs b/src/lib.rs index 8750b24ca..74a46fd17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,6 +97,7 @@ pub use self::message::{Head, Message, RequestHead, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; pub use self::response::Response; +pub use self::service::HttpService; pub use self::service::{SendError, SendResponse}; pub mod dev { diff --git a/src/service/mod.rs b/src/service/mod.rs index 83a40bd12..25e95bf60 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -1,3 +1,5 @@ mod senderror; +mod service; pub use self::senderror::{SendError, SendResponse}; +pub use self::service::HttpService; diff --git a/src/service/service.rs b/src/service/service.rs new file mode 100644 index 000000000..5f6ee8190 --- /dev/null +++ b/src/service/service.rs @@ -0,0 +1,446 @@ +use std::fmt::Debug; +use std::marker::PhantomData; +use std::{fmt, io, net}; + +use actix_codec::{AsyncRead, AsyncWrite, Framed, FramedParts}; +use actix_service::{IntoNewService, NewService, Service}; +use actix_utils::cloneable::CloneableService; +use bytes::{Buf, BufMut, Bytes, BytesMut}; +use futures::{try_ready, Async, Future, IntoFuture, Poll}; +use h2::server::{self, Handshake}; +use log::error; + +use crate::body::MessageBody; +use crate::config::{KeepAlive, ServiceConfig}; +use crate::error::DispatchError; +use crate::request::Request; +use crate::response::Response; + +use crate::{h1, h2::Dispatcher}; + +/// `NewService` HTTP1.1/HTTP2 transport implementation +pub struct HttpService { + srv: S, + cfg: ServiceConfig, + _t: PhantomData<(T, B)>, +} + +impl HttpService +where + S: NewService, + S::Service: 'static, + S::Error: Debug + 'static, + S::Response: Into>, + B: MessageBody + 'static, +{ + /// Create new `HttpService` instance. + pub fn new>(service: F) -> Self { + let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); + + HttpService { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } + + /// Create builder for `HttpService` instance. + pub fn build() -> HttpServiceBuilder { + HttpServiceBuilder::new() + } +} + +impl NewService for HttpService +where + T: AsyncRead + AsyncWrite + 'static, + S: NewService, + S::Service: 'static, + S::Error: Debug, + S::Response: Into>, + B: MessageBody + 'static, +{ + type Response = (); + type Error = DispatchError; + type InitError = S::InitError; + type Service = HttpServiceHandler; + type Future = HttpServiceResponse; + + fn new_service(&self, _: &()) -> Self::Future { + HttpServiceResponse { + fut: self.srv.new_service(&()).into_future(), + cfg: Some(self.cfg.clone()), + _t: PhantomData, + } + } +} + +/// A http service factory builder +/// +/// This type can be used to construct an instance of `ServiceConfig` through a +/// builder-like pattern. +pub struct HttpServiceBuilder { + keep_alive: KeepAlive, + client_timeout: u64, + client_disconnect: u64, + host: String, + addr: net::SocketAddr, + secure: bool, + _t: PhantomData<(T, S)>, +} + +impl HttpServiceBuilder +where + S: NewService, + S::Service: 'static, + S::Error: Debug + 'static, +{ + /// Create instance of `HttpServiceBuilder` type + pub fn new() -> HttpServiceBuilder { + HttpServiceBuilder { + keep_alive: KeepAlive::Timeout(5), + client_timeout: 5000, + client_disconnect: 0, + secure: false, + host: "localhost".to_owned(), + addr: "127.0.0.1:8080".parse().unwrap(), + _t: PhantomData, + } + } + + /// Enable secure flag for current server. + /// This flags also enables `client disconnect timeout`. + /// + /// By default this flag is set to false. + pub fn secure(mut self) -> Self { + self.secure = true; + if self.client_disconnect == 0 { + self.client_disconnect = 3000; + } + self + } + + /// Set server keep-alive setting. + /// + /// By default keep alive is set to a 5 seconds. + pub fn keep_alive>(mut self, val: U) -> Self { + self.keep_alive = val.into(); + self + } + + /// Set server client timeout in milliseconds for first request. + /// + /// Defines a timeout for reading client request header. If a client does not transmit + /// the entire set headers within this time, the request is terminated with + /// the 408 (Request Time-out) error. + /// + /// To disable timeout set value to 0. + /// + /// By default client timeout is set to 5000 milliseconds. + pub fn client_timeout(mut self, val: u64) -> Self { + self.client_timeout = val; + self + } + + /// Set server connection disconnect timeout in milliseconds. + /// + /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete + /// within this time, the request get dropped. This timeout affects secure connections. + /// + /// To disable timeout set value to 0. + /// + /// By default disconnect timeout is set to 3000 milliseconds. + pub fn client_disconnect(mut self, val: u64) -> Self { + self.client_disconnect = val; + self + } + + /// Set server host name. + /// + /// Host name is used by application router aa a hostname for url + /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. + /// html#method.host) documentation for more information. + /// + /// By default host name is set to a "localhost" value. + pub fn server_hostname(mut self, val: &str) -> Self { + self.host = val.to_owned(); + self + } + + /// Set server ip address. + /// + /// Host name is used by application router aa a hostname for url + /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. + /// html#method.host) documentation for more information. + /// + /// By default server address is set to a "127.0.0.1:8080" + pub fn server_address(mut self, addr: U) -> Self { + match addr.to_socket_addrs() { + Err(err) => error!("Can not convert to SocketAddr: {}", err), + Ok(mut addrs) => { + if let Some(addr) = addrs.next() { + self.addr = addr; + } + } + } + self + } + + // #[cfg(feature = "ssl")] + // /// Configure alpn protocols for SslAcceptorBuilder. + // pub fn configure_openssl( + // builder: &mut openssl::ssl::SslAcceptorBuilder, + // ) -> io::Result<()> { + // let protos: &[u8] = b"\x02h2"; + // builder.set_alpn_select_callback(|_, protos| { + // const H2: &[u8] = b"\x02h2"; + // if protos.windows(3).any(|window| window == H2) { + // Ok(b"h2") + // } else { + // Err(openssl::ssl::AlpnError::NOACK) + // } + // }); + // builder.set_alpn_protos(&protos)?; + + // Ok(()) + // } + + /// Finish service configuration and create `HttpService` instance. + pub fn finish(self, service: F) -> HttpService + where + B: MessageBody, + F: IntoNewService, + { + let cfg = ServiceConfig::new( + self.keep_alive, + self.client_timeout, + self.client_disconnect, + ); + HttpService { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } +} + +#[doc(hidden)] +pub struct HttpServiceResponse, B> { + fut: ::Future, + cfg: Option, + _t: PhantomData<(T, B)>, +} + +impl Future for HttpServiceResponse +where + T: AsyncRead + AsyncWrite, + S: NewService, + S::Service: 'static, + S::Response: Into>, + S::Error: Debug, + B: MessageBody + 'static, +{ + type Item = HttpServiceHandler; + type Error = S::InitError; + + fn poll(&mut self) -> Poll { + let service = try_ready!(self.fut.poll()); + Ok(Async::Ready(HttpServiceHandler::new( + self.cfg.take().unwrap(), + service, + ))) + } +} + +/// `Service` implementation for http transport +pub struct HttpServiceHandler { + srv: CloneableService, + cfg: ServiceConfig, + _t: PhantomData<(T, B)>, +} + +impl HttpServiceHandler +where + S: Service + 'static, + S::Error: Debug, + S::Response: Into>, + B: MessageBody + 'static, +{ + fn new(cfg: ServiceConfig, srv: S) -> HttpServiceHandler { + HttpServiceHandler { + cfg, + srv: CloneableService::new(srv), + _t: PhantomData, + } + } +} + +impl Service for HttpServiceHandler +where + T: AsyncRead + AsyncWrite + 'static, + S: Service + 'static, + S::Error: Debug, + S::Response: Into>, + B: MessageBody + 'static, +{ + type Response = (); + type Error = DispatchError; + type Future = HttpServiceHandlerResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.srv.poll_ready().map_err(|e| { + error!("Service readiness error: {:?}", e); + DispatchError::Service + }) + } + + fn call(&mut self, req: T) -> Self::Future { + HttpServiceHandlerResponse { + state: State::Unknown(Some(( + req, + BytesMut::with_capacity(14), + self.cfg.clone(), + self.srv.clone(), + ))), + } + } +} + +enum State + 'static, B: MessageBody> +where + S::Error: fmt::Debug, + T: AsyncRead + AsyncWrite + 'static, +{ + H1(h1::Dispatcher), + H2(Dispatcher, S, B>), + Unknown(Option<(T, BytesMut, ServiceConfig, CloneableService)>), + Handshake(Option<(Handshake, Bytes>, ServiceConfig, CloneableService)>), +} + +pub struct HttpServiceHandlerResponse +where + T: AsyncRead + AsyncWrite + 'static, + S: Service + 'static, + S::Error: Debug, + S::Response: Into>, + B: MessageBody + 'static, +{ + state: State, +} + +const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; + +impl Future for HttpServiceHandlerResponse +where + T: AsyncRead + AsyncWrite, + S: Service + 'static, + S::Error: Debug, + S::Response: Into>, + B: MessageBody, +{ + type Item = (); + type Error = DispatchError; + + fn poll(&mut self) -> Poll { + match self.state { + State::H1(ref mut disp) => disp.poll(), + State::H2(ref mut disp) => disp.poll(), + State::Unknown(ref mut data) => { + if let Some(ref mut item) = data { + loop { + unsafe { + let b = item.1.bytes_mut(); + let n = { try_ready!(item.0.poll_read(b)) }; + item.1.advance_mut(n); + if item.1.len() >= HTTP2_PREFACE.len() { + break; + } + } + } + } else { + panic!() + } + let (io, buf, cfg, srv) = data.take().unwrap(); + if buf[..14] == HTTP2_PREFACE[..] { + let io = Io { + inner: io, + unread: Some(buf), + }; + self.state = + State::Handshake(Some((server::handshake(io), cfg, srv))); + } else { + let framed = Framed::from_parts(FramedParts::with_read_buf( + io, + h1::Codec::new(cfg.clone()), + buf, + )); + self.state = + State::H1(h1::Dispatcher::with_timeout(framed, cfg, None, srv)) + } + self.poll() + } + State::Handshake(ref mut data) => { + let conn = if let Some(ref mut item) = data { + match item.0.poll() { + Ok(Async::Ready(conn)) => conn, + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(err) => { + trace!("H2 handshake error: {}", err); + return Err(err.into()); + } + } + } else { + panic!() + }; + let (_, cfg, srv) = data.take().unwrap(); + self.state = State::H2(Dispatcher::new(srv, conn, cfg, None)); + self.poll() + } + } + } +} + +/// Wrapper for `AsyncRead + AsyncWrite` types +struct Io { + unread: Option, + inner: T, +} + +impl io::Read for Io { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if let Some(mut bytes) = self.unread.take() { + let size = std::cmp::min(buf.len(), bytes.len()); + buf[..size].copy_from_slice(&bytes[..size]); + if bytes.len() > size { + bytes.split_to(size); + self.unread = Some(bytes); + } + Ok(size) + } else { + self.inner.read(buf) + } + } +} + +impl io::Write for Io { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +impl AsyncRead for Io { + unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { + self.inner.prepare_uninitialized_buffer(buf) + } +} + +impl AsyncWrite for Io { + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.inner.shutdown() + } + fn write_buf(&mut self, buf: &mut B) -> Poll { + self.inner.write_buf(buf) + } +} diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 3afee682f..695a477d2 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -73,7 +73,7 @@ impl TestServer { .start(); tx.send((System::current(), local_addr)).unwrap(); - sys.run(); + sys.run() }); let (system, addr) = rx.recv().unwrap(); diff --git a/tests/test_server.rs b/tests/test_server.rs index fd848b82b..4cffdd096 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -10,8 +10,8 @@ use futures::stream::once; use actix_http::body::Body; use actix_http::{ - body, client, h1, h2, http, Error, HttpMessage as HttpMessage2, KeepAlive, Request, - Response, + body, client, h1, h2, http, Error, HttpMessage as HttpMessage2, HttpService, + KeepAlive, Request, Response, }; #[test] @@ -31,6 +31,26 @@ fn test_h1() { assert!(response.status().is_success()); } +#[test] +fn test_h1_2() { + let mut srv = TestServer::new(|| { + HttpService::build() + .keep_alive(KeepAlive::Disabled) + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .finish(|req: Request| { + assert_eq!(req.version(), http::Version::HTTP_11); + future::ok::<_, ()>(Response::Ok().finish()) + }) + .map(|_| ()) + }); + + let req = client::ClientRequest::get(srv.url("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); +} + #[cfg(feature = "ssl")] fn ssl_acceptor() -> std::io::Result> { use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; @@ -71,7 +91,30 @@ fn test_h2() -> std::io::Result<()> { let req = client::ClientRequest::get(srv.surl("/")).finish().unwrap(); let response = srv.send_request(req).unwrap(); - println!("RES: {:?}", response); + assert!(response.status().is_success()); + Ok(()) +} + +#[cfg(feature = "ssl")] +#[test] +fn test_h2_1() -> std::io::Result<()> { + let openssl = ssl_acceptor()?; + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .finish(|req: Request| { + assert_eq!(req.version(), http::Version::HTTP_2); + future::ok::<_, Error>(Response::Ok().finish()) + }) + .map_err(|_| ()), + ) + }); + + let req = client::ClientRequest::get(srv.surl("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); Ok(()) } @@ -79,9 +122,6 @@ fn test_h2() -> std::io::Result<()> { #[cfg(feature = "ssl")] #[test] fn test_h2_body() -> std::io::Result<()> { - // std::env::set_var("RUST_LOG", "actix_http=trace"); - // env_logger::init(); - let data = "HELLOWORLD".to_owned().repeat(64 * 1024); let openssl = ssl_acceptor()?; let mut srv = TestServer::new(move || { From 6d639ae3df10be9e10449774ab96c46c7ca11483 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 22:59:56 -0800 Subject: [PATCH 173/208] allow to create http services with config --- src/h2/service.rs | 12 ++++++++++++ src/service/service.rs | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/h2/service.rs b/src/h2/service.rs index a47f507b5..530629947 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -47,6 +47,18 @@ where } } + /// Create new `HttpService` instance with config. + pub fn with_config>( + cfg: ServiceConfig, + service: F, + ) -> Self { + H2Service { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } + /// Create builder for `HttpService` instance. pub fn build() -> H2ServiceBuilder { H2ServiceBuilder::new() diff --git a/src/service/service.rs b/src/service/service.rs index 5f6ee8190..3d0009ed0 100644 --- a/src/service/service.rs +++ b/src/service/service.rs @@ -44,6 +44,18 @@ where } } + /// Create new `HttpService` instance with config. + pub fn with_config>( + cfg: ServiceConfig, + service: F, + ) -> Self { + HttpService { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } + /// Create builder for `HttpService` instance. pub fn build() -> HttpServiceBuilder { HttpServiceBuilder::new() From e3245223893a648db8c4cd5ccc426a4aef9e16e6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 8 Mar 2019 22:47:49 -0800 Subject: [PATCH 174/208] listen method has different signature --- test-server/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 695a477d2..dd7bc362a 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -67,7 +67,7 @@ impl TestServer { let local_addr = tcp.local_addr().unwrap(); Server::build() - .listen("test", tcp, factory) + .listen("test", tcp, factory)? .workers(1) .disable_signals() .start(); From ca73f178c927d9e3d786952826005e652f00fa35 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 07:37:23 -0800 Subject: [PATCH 175/208] revert generic service request; add ServerConfig to service factories --- Cargo.toml | 2 ++ examples/echo.rs | 10 +++--- examples/echo2.rs | 10 +++--- examples/framed_hello.rs | 10 +++--- examples/hello-world.rs | 10 +++--- src/client/connector.rs | 68 ++++++++++++++++++++++++++++------------ src/client/pool.rs | 15 +++++++-- src/client/request.rs | 2 +- src/h1/dispatcher.rs | 14 ++++----- src/h1/service.rs | 39 +++++++++++++---------- src/h2/dispatcher.rs | 14 ++++----- src/h2/service.rs | 41 ++++++++++++++---------- src/lib.rs | 3 +- src/service/senderror.rs | 12 ++++--- src/service/service.rs | 37 ++++++++++++---------- src/ws/client/mod.rs | 14 +++++++++ src/ws/client/service.rs | 11 ++++--- src/ws/service.rs | 6 ++-- src/ws/transport.rs | 10 +++--- test-server/src/lib.rs | 12 ++++--- tests/test_server.rs | 21 ++++++++----- 21 files changed, 222 insertions(+), 139 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9d55b85c5..594ab88db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ actix-codec = "0.1.1" actix-connector = { git="https://github.com/actix/actix-net.git" } actix-service = { git="https://github.com/actix/actix-net.git" } actix-utils = { git="https://github.com/actix/actix-net.git" } +actix-server-config = { git="https://github.com/actix/actix-net.git" } base64 = "0.10" backtrace = "0.3" @@ -92,6 +93,7 @@ actix-server = { git="https://github.com/actix/actix-net.git", features=["ssl"] #actix-connector = { version = "0.3.0", features=["ssl"] } actix-connector = { git="https://github.com/actix/actix-net.git", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } + env_logger = "0.6" serde_derive = "1.0" openssl = { version="0.10" } diff --git a/examples/echo.rs b/examples/echo.rs index 03d5b4706..eb4aaa657 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -1,3 +1,5 @@ +use std::{env, io}; + use actix_http::HttpMessage; use actix_http::{h1, Request, Response}; use actix_server::Server; @@ -6,9 +8,8 @@ use bytes::Bytes; use futures::Future; use http::header::HeaderValue; use log::info; -use std::env; -fn main() { +fn main() -> io::Result<()> { env::set_var("RUST_LOG", "echo=info"); env_logger::init(); @@ -27,7 +28,6 @@ fn main() { }) }) .map(|_| ()) - }) - .unwrap() - .run(); + })? + .run() } diff --git a/examples/echo2.rs b/examples/echo2.rs index 2fd9cbcff..3bb8d83d5 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -1,3 +1,5 @@ +use std::{env, io}; + use actix_http::http::HeaderValue; use actix_http::HttpMessage; use actix_http::{h1, Error, Request, Response}; @@ -6,7 +8,6 @@ use actix_service::NewService; use bytes::Bytes; use futures::Future; use log::info; -use std::env; fn handle_request(mut req: Request) -> impl Future { req.body().limit(512).from_err().and_then(|bytes: Bytes| { @@ -17,7 +18,7 @@ fn handle_request(mut req: Request) -> impl Future io::Result<()> { env::set_var("RUST_LOG", "echo=info"); env_logger::init(); @@ -29,7 +30,6 @@ fn main() { .server_hostname("localhost") .finish(|_req: Request| handle_request(_req)) .map(|_| ()) - }) - .unwrap() - .run(); + })? + .run() } diff --git a/examples/framed_hello.rs b/examples/framed_hello.rs index 5bbc3be9b..ff3977854 100644 --- a/examples/framed_hello.rs +++ b/examples/framed_hello.rs @@ -1,3 +1,5 @@ +use std::{env, io}; + use actix_codec::Framed; use actix_http::{h1, Response, SendResponse, ServiceConfig}; use actix_server::Server; @@ -5,9 +7,8 @@ use actix_service::NewService; use actix_utils::framed::IntoFramed; use actix_utils::stream::TakeItem; use futures::Future; -use std::env; -fn main() { +fn main() -> io::Result<()> { env::set_var("RUST_LOG", "framed_hello=info"); env_logger::init(); @@ -20,7 +21,6 @@ fn main() { .map_err(|_| ()) .map(|_| ()) }) - }) - .unwrap() - .run(); + })? + .run() } diff --git a/examples/hello-world.rs b/examples/hello-world.rs index e0c322a22..05d69fed8 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -1,12 +1,13 @@ +use std::{env, io}; + use actix_http::{h1, Response}; use actix_server::Server; use actix_service::NewService; use futures::future; use http::header::HeaderValue; use log::info; -use std::env; -fn main() { +fn main() -> io::Result<()> { env::set_var("RUST_LOG", "hello_world=info"); env_logger::init(); @@ -23,7 +24,6 @@ fn main() { future::ok::<_, ()>(res.body("Hello world!")) }) .map(|_| ()) - }) - .unwrap() - .run(); + })? + .run() } diff --git a/src/client/connector.rs b/src/client/connector.rs index d1fd802e9..5eb85ba61 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -133,8 +133,11 @@ impl Connector { /// Finish configuration process and create connector service. pub fn service( self, - ) -> impl Service + Clone - { + ) -> impl Service< + Request = Connect, + Response = impl Connection, + Error = ConnectorError, + > + Clone { #[cfg(not(feature = "ssl"))] { let connector = TimeoutService::new( @@ -238,7 +241,11 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + >, { pub(crate) tcp_pool: ConnectionPool, } @@ -246,8 +253,11 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service - + Clone, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + > + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -256,15 +266,16 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, T: Service, { + type Request = Connect; type Response = IoConnection; type Error = ConnectorError; type Future = Either< - as Service>::Future, + as Service>::Future, FutureResult, ConnectorError>, >; @@ -299,12 +310,12 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Connect, + Request = Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, >, T2: Service< - Connect, + Request = Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, >, @@ -318,12 +329,12 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Connect, + Request = Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, > + Clone, T2: Service< - Connect, + Request = Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, > + Clone, @@ -336,21 +347,22 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Connect, + Request = Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, >, T2: Service< - Connect, + Request = Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, >, { + type Request = Connect; type Response = EitherConnection; type Error = ConnectorError; type Future = Either< @@ -385,15 +397,23 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io1, Protocol), + Error = ConnectorError, + >, { - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } impl Future for InnerConnectorResponseA where - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io1, Protocol), + Error = ConnectorError, + >, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { @@ -411,15 +431,23 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io2, Protocol), + Error = ConnectorError, + >, { - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } impl Future for InnerConnectorResponseB where - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io2, Protocol), + Error = ConnectorError, + >, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { diff --git a/src/client/pool.rs b/src/client/pool.rs index 425e89395..188980cb3 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -48,7 +48,11 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + >, { pub(crate) fn new( connector: T, @@ -84,11 +88,16 @@ where } } -impl Service for ConnectionPool +impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + >, { + type Request = Connect; type Response = IoConnection; type Error = ConnectorError; type Future = Either< diff --git a/src/client/request.rs b/src/client/request.rs index 637635d0f..7e971756d 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -176,7 +176,7 @@ where ) -> impl Future where B: 'static, - T: Service, + T: Service, I: Connection, { let Self { head, body } = self; diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 42ab33e79..a7eb96c3f 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -36,14 +36,14 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher + 'static, B: MessageBody> +pub struct Dispatcher + 'static, B: MessageBody> where S::Error: Debug, { inner: Option>, } -struct InnerDispatcher + 'static, B: MessageBody> +struct InnerDispatcher + 'static, B: MessageBody> where S::Error: Debug, { @@ -67,13 +67,13 @@ enum DispatcherMessage { Error(Response<()>), } -enum State, B: MessageBody> { +enum State, B: MessageBody> { None, ServiceCall(S::Future), SendPayload(ResponseBody), } -impl, B: MessageBody> State { +impl, B: MessageBody> State { fn is_empty(&self) -> bool { if let State::None = self { true @@ -86,7 +86,7 @@ impl, B: MessageBody> State { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, @@ -145,7 +145,7 @@ where impl InnerDispatcher where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, @@ -465,7 +465,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, diff --git a/src/h1/service.rs b/src/h1/service.rs index 229229e69..6a36678b3 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; use std::net; use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_server_config::ServerConfig as SrvConfig; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use futures::future::{ok, FutureResult}; @@ -28,14 +29,14 @@ pub struct H1Service { impl H1Service where - S: NewService, + S: NewService, S::Error: Debug, S::Response: Into>, S::Service: 'static, B: MessageBody, { /// Create new `HttpService` instance with default config. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H1Service { @@ -46,7 +47,7 @@ where } /// Create new `HttpService` instance with config. - pub fn with_config>( + pub fn with_config>( cfg: ServiceConfig, service: F, ) -> Self { @@ -63,24 +64,25 @@ where } } -impl NewService for H1Service +impl NewService for H1Service where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService, S::Error: Debug, S::Response: Into>, S::Service: 'static, B: MessageBody, { + type Request = T; type Response = (); type Error = DispatchError; type InitError = S::InitError; type Service = H1ServiceHandler; type Future = H1ServiceResponse; - fn new_service(&self, _: &()) -> Self::Future { + fn new_service(&self, cfg: &SrvConfig) -> Self::Future { H1ServiceResponse { - fut: self.srv.new_service(&()).into_future(), + fut: self.srv.new_service(cfg).into_future(), cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -103,7 +105,7 @@ pub struct H1ServiceBuilder { impl H1ServiceBuilder where - S: NewService, + S: NewService, S::Error: Debug, { /// Create instance of `ServiceConfigBuilder` @@ -201,7 +203,7 @@ where pub fn finish(self, service: F) -> H1Service where B: MessageBody, - F: IntoNewService, + F: IntoNewService, { let cfg = ServiceConfig::new( self.keep_alive, @@ -217,7 +219,7 @@ where } #[doc(hidden)] -pub struct H1ServiceResponse, B> { +pub struct H1ServiceResponse, B> { fut: ::Future, cfg: Option, _t: PhantomData<(T, B)>, @@ -226,7 +228,7 @@ pub struct H1ServiceResponse, B> { impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService, S::Service: 'static, S::Error: Debug, S::Response: Into>, @@ -253,7 +255,7 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where - S: Service, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, @@ -267,14 +269,15 @@ where } } -impl Service for H1ServiceHandler +impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, { + type Request = T; type Response = (); type Error = DispatchError; type Future = Dispatcher; @@ -311,17 +314,18 @@ where } } -impl NewService for OneRequest +impl NewService for OneRequest where T: AsyncRead + AsyncWrite, { + type Request = T; type Response = (Request, Framed); type Error = ParseError; type InitError = (); type Service = OneRequestService; type Future = FutureResult; - fn new_service(&self, _: &()) -> Self::Future { + fn new_service(&self, _: &SrvConfig) -> Self::Future { ok(OneRequestService { config: self.config.clone(), _t: PhantomData, @@ -336,10 +340,11 @@ pub struct OneRequestService { _t: PhantomData, } -impl Service for OneRequestService +impl Service for OneRequestService where T: AsyncRead + AsyncWrite, { + type Request = T; type Response = (Request, Framed); type Error = ParseError; type Future = OneRequestServiceResponse; diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index be813bd57..a3c731ebb 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -38,7 +38,7 @@ bitflags! { /// Dispatcher for HTTP/2 protocol pub struct Dispatcher< T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, B: MessageBody, > { flags: Flags, @@ -53,7 +53,7 @@ pub struct Dispatcher< impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: fmt::Debug, S::Response: Into>, B: MessageBody + 'static, @@ -95,7 +95,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: fmt::Debug, S::Response: Into>, B: MessageBody + 'static, @@ -141,20 +141,20 @@ where } } -struct ServiceResponse, B> { +struct ServiceResponse { state: ServiceResponseState, config: ServiceConfig, buffer: Option, } -enum ServiceResponseState, B> { +enum ServiceResponseState { ServiceCall(S::Future, Option>), SendPayload(SendStream, ResponseBody), } impl ServiceResponse where - S: Service + 'static, + S: Service + 'static, S::Error: fmt::Debug, S::Response: Into>, B: MessageBody + 'static, @@ -222,7 +222,7 @@ where impl Future for ServiceResponse where - S: Service + 'static, + S: Service + 'static, S::Error: fmt::Debug, S::Response: Into>, B: MessageBody + 'static, diff --git a/src/h2/service.rs b/src/h2/service.rs index 530629947..57515d4e8 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; use std::{io, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_server_config::ServerConfig as SrvConfig; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use bytes::Bytes; @@ -30,14 +31,14 @@ pub struct H2Service { impl H2Service where - S: NewService, + S: NewService, S::Service: 'static, S::Error: Debug + 'static, S::Response: Into>, B: MessageBody + 'static, { /// Create new `HttpService` instance. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H2Service { @@ -48,7 +49,7 @@ where } /// Create new `HttpService` instance with config. - pub fn with_config>( + pub fn with_config>( cfg: ServiceConfig, service: F, ) -> Self { @@ -65,24 +66,25 @@ where } } -impl NewService for H2Service +impl NewService for H2Service where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService, S::Service: 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { + type Request = T; type Response = (); type Error = DispatchError; type InitError = S::InitError; type Service = H2ServiceHandler; type Future = H2ServiceResponse; - fn new_service(&self, _: &()) -> Self::Future { + fn new_service(&self, cfg: &SrvConfig) -> Self::Future { H2ServiceResponse { - fut: self.srv.new_service(&()).into_future(), + fut: self.srv.new_service(cfg).into_future(), cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -105,7 +107,7 @@ pub struct H2ServiceBuilder { impl H2ServiceBuilder where - S: NewService, + S: NewService, S::Service: 'static, S::Error: Debug + 'static, { @@ -204,7 +206,7 @@ where pub fn finish(self, service: F) -> H2Service where B: MessageBody, - F: IntoNewService, + F: IntoNewService, { let cfg = ServiceConfig::new( self.keep_alive, @@ -220,7 +222,7 @@ where } #[doc(hidden)] -pub struct H2ServiceResponse, B> { +pub struct H2ServiceResponse, B> { fut: ::Future, cfg: Option, _t: PhantomData<(T, B)>, @@ -229,7 +231,7 @@ pub struct H2ServiceResponse, B> { impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService, S::Service: 'static, S::Response: Into>, S::Error: Debug, @@ -256,7 +258,7 @@ pub struct H2ServiceHandler { impl H2ServiceHandler where - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, @@ -270,14 +272,15 @@ where } } -impl Service for H2ServiceHandler +impl Service for H2ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { + type Request = T; type Response = (); type Error = DispatchError; type Future = H2ServiceHandlerResponse; @@ -300,7 +303,11 @@ where } } -enum State + 'static, B: MessageBody> { +enum State< + T: AsyncRead + AsyncWrite, + S: Service + 'static, + B: MessageBody, +> { Incoming(Dispatcher), Handshake( Option>, @@ -312,7 +319,7 @@ enum State + 'static, B: MessageB pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, @@ -323,7 +330,7 @@ where impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, diff --git a/src/lib.rs b/src/lib.rs index 74a46fd17..9d8bc50f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,8 +97,7 @@ pub use self::message::{Head, Message, RequestHead, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; pub use self::response::Response; -pub use self::service::HttpService; -pub use self::service::{SendError, SendResponse}; +pub use self::service::{HttpService, SendError, SendResponse}; pub mod dev { //! The `actix-web` prelude for library developers diff --git a/src/service/senderror.rs b/src/service/senderror.rs index 8268c6660..44d362593 100644 --- a/src/service/senderror.rs +++ b/src/service/senderror.rs @@ -22,11 +22,12 @@ where } } -impl NewService)>> for SendError +impl NewService for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, { + type Request = Result)>; type Response = R; type Error = (E, Framed); type InitError = (); @@ -38,11 +39,12 @@ where } } -impl Service)>> for SendError +impl Service for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, { + type Request = Result)>; type Response = R; type Error = (E, Framed); type Future = Either)>, SendErrorFut>; @@ -128,11 +130,12 @@ where } } -impl NewService<(Response, Framed)> for SendResponse +impl NewService for SendResponse where T: AsyncRead + AsyncWrite, B: MessageBody, { + type Request = (Response, Framed); type Response = Framed; type Error = Error; type InitError = (); @@ -144,11 +147,12 @@ where } } -impl Service<(Response, Framed)> for SendResponse +impl Service for SendResponse where T: AsyncRead + AsyncWrite, B: MessageBody, { + type Request = (Response, Framed); type Response = Framed; type Error = Error; type Future = SendResponseFut; diff --git a/src/service/service.rs b/src/service/service.rs index 3d0009ed0..0fa5d6653 100644 --- a/src/service/service.rs +++ b/src/service/service.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; use std::{fmt, io, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed, FramedParts}; +use actix_server_config::ServerConfig as SrvConfig; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use bytes::{Buf, BufMut, Bytes, BytesMut}; @@ -27,14 +28,14 @@ pub struct HttpService { impl HttpService where - S: NewService, + S: NewService, S::Service: 'static, S::Error: Debug + 'static, S::Response: Into>, B: MessageBody + 'static, { /// Create new `HttpService` instance. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); HttpService { @@ -45,7 +46,7 @@ where } /// Create new `HttpService` instance with config. - pub fn with_config>( + pub fn with_config>( cfg: ServiceConfig, service: F, ) -> Self { @@ -62,24 +63,25 @@ where } } -impl NewService for HttpService +impl NewService for HttpService where T: AsyncRead + AsyncWrite + 'static, - S: NewService, + S: NewService, S::Service: 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { + type Request = T; type Response = (); type Error = DispatchError; type InitError = S::InitError; type Service = HttpServiceHandler; type Future = HttpServiceResponse; - fn new_service(&self, _: &()) -> Self::Future { + fn new_service(&self, cfg: &SrvConfig) -> Self::Future { HttpServiceResponse { - fut: self.srv.new_service(&()).into_future(), + fut: self.srv.new_service(cfg).into_future(), cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -102,7 +104,7 @@ pub struct HttpServiceBuilder { impl HttpServiceBuilder where - S: NewService, + S: NewService, S::Service: 'static, S::Error: Debug + 'static, { @@ -220,7 +222,7 @@ where pub fn finish(self, service: F) -> HttpService where B: MessageBody, - F: IntoNewService, + F: IntoNewService, { let cfg = ServiceConfig::new( self.keep_alive, @@ -236,7 +238,7 @@ where } #[doc(hidden)] -pub struct HttpServiceResponse, B> { +pub struct HttpServiceResponse, B> { fut: ::Future, cfg: Option, _t: PhantomData<(T, B)>, @@ -245,7 +247,7 @@ pub struct HttpServiceResponse, B> { impl Future for HttpServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService, S::Service: 'static, S::Response: Into>, S::Error: Debug, @@ -272,7 +274,7 @@ pub struct HttpServiceHandler { impl HttpServiceHandler where - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, @@ -286,14 +288,15 @@ where } } -impl Service for HttpServiceHandler +impl Service for HttpServiceHandler where T: AsyncRead + AsyncWrite + 'static, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { + type Request = T; type Response = (); type Error = DispatchError; type Future = HttpServiceHandlerResponse; @@ -317,7 +320,7 @@ where } } -enum State + 'static, B: MessageBody> +enum State + 'static, B: MessageBody> where S::Error: fmt::Debug, T: AsyncRead + AsyncWrite + 'static, @@ -331,7 +334,7 @@ where pub struct HttpServiceHandlerResponse where T: AsyncRead + AsyncWrite + 'static, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, @@ -344,7 +347,7 @@ const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; impl Future for HttpServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, diff --git a/src/ws/client/mod.rs b/src/ws/client/mod.rs index 8e59e846d..12c7229b9 100644 --- a/src/ws/client/mod.rs +++ b/src/ws/client/mod.rs @@ -25,6 +25,20 @@ impl Protocol { } } + fn is_http(self) -> bool { + match self { + Protocol::Https | Protocol::Http => true, + _ => false, + } + } + + fn is_secure(self) -> bool { + match self { + Protocol::Https | Protocol::Wss => true, + _ => false, + } + } + fn port(self) -> u16 { match self { Protocol::Http | Protocol::Ws => 80, diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index c48b6e0c1..586873d19 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -26,7 +26,7 @@ pub type DefaultClient = Client; /// WebSocket's client pub struct Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { connector: T, @@ -34,7 +34,7 @@ where impl Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { /// Create new websocket's client factory @@ -51,7 +51,7 @@ impl Default for Client { impl Clone for Client where - T: Service + Clone, + T: Service + Clone, T::Response: AsyncRead + AsyncWrite, { fn clone(&self) -> Self { @@ -61,12 +61,13 @@ where } } -impl Service for Client +impl Service for Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite + 'static, T::Future: 'static, { + type Request = Connect; type Response = Framed; type Error = ClientError; type Future = Either< diff --git a/src/ws/service.rs b/src/ws/service.rs index bbd9f7826..f3b066053 100644 --- a/src/ws/service.rs +++ b/src/ws/service.rs @@ -20,7 +20,8 @@ impl Default for VerifyWebSockets { } } -impl NewService<(Request, Framed)> for VerifyWebSockets { +impl NewService for VerifyWebSockets { + type Request = (Request, Framed); type Response = (Request, Framed); type Error = (HandshakeError, Framed); type InitError = (); @@ -32,7 +33,8 @@ impl NewService<(Request, Framed)> for VerifyWebSockets { } } -impl Service<(Request, Framed)> for VerifyWebSockets { +impl Service for VerifyWebSockets { + type Request = (Request, Framed); type Response = (Request, Framed); type Error = (HandshakeError, Framed); type Future = FutureResult; diff --git a/src/ws/transport.rs b/src/ws/transport.rs index 6a4f4d227..da7782be5 100644 --- a/src/ws/transport.rs +++ b/src/ws/transport.rs @@ -7,7 +7,7 @@ use super::{Codec, Frame, Message}; pub struct Transport where - S: Service + 'static, + S: Service + 'static, T: AsyncRead + AsyncWrite, { inner: FramedTransport, @@ -16,17 +16,17 @@ where impl Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { - pub fn new>(io: T, service: F) -> Self { + pub fn new>(io: T, service: F) -> Self { Transport { inner: FramedTransport::new(Framed::new(io, Codec::new()), service), } } - pub fn with>(framed: Framed, service: F) -> Self { + pub fn with>(framed: Framed, service: F) -> Self { Transport { inner: FramedTransport::new(framed, service), } @@ -36,7 +36,7 @@ where impl Future for Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index dd7bc362a..6b70fbc10 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -56,7 +56,8 @@ impl TestServer { pub fn new( factory: F, ) -> TestServerRuntime< - impl Service + Clone, + impl Service + + Clone, > { let (tx, rx) = mpsc::channel(); @@ -88,8 +89,11 @@ impl TestServer { } fn new_connector( - ) -> impl Service + Clone - { + ) -> impl Service< + Request = Connect, + Response = impl Connection, + Error = ConnectorError, + > + Clone { #[cfg(feature = "ssl")] { use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; @@ -202,7 +206,7 @@ impl TestServerRuntime { impl TestServerRuntime where - T: Service + Clone, + T: Service + Clone, T::Response: Connection, { /// Connect to websocket server at a given path diff --git a/tests/test_server.rs b/tests/test_server.rs index 4cffdd096..b7cd5557b 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -565,17 +565,22 @@ fn test_body_chunked_implicit() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +use actix_server_config::ServerConfig; +use actix_service::fn_cfg_factory; + #[test] fn test_response_http_error_handling() { let mut srv = TestServer::new(|| { - h1::H1Service::new(|_| { - let broken_header = Bytes::from_static(b"\0\0\0"); - ok::<_, ()>( - Response::Ok() - .header(http::header::CONTENT_TYPE, broken_header) - .body(STR), - ) - }) + h1::H1Service::new(fn_cfg_factory(|_: &ServerConfig| { + Ok::<_, ()>(|_| { + let broken_header = Bytes::from_static(b"\0\0\0"); + ok::<_, ()>( + Response::Ok() + .header(http::header::CONTENT_TYPE, broken_header) + .body(STR), + ) + }) + })) }); let req = srv.get().finish().unwrap(); From d026821924cc97482c334952e214f0f86b1ee0e0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 10:39:06 -0800 Subject: [PATCH 176/208] unify service builders --- examples/echo.rs | 7 +- examples/echo2.rs | 10 +- examples/hello-world.rs | 7 +- src/builder.rs | 141 ++++++++++++++++++++++++++++ src/config.rs | 118 +----------------------- src/h1/service.rs | 136 --------------------------- src/h2/service.rs | 135 --------------------------- src/lib.rs | 4 +- src/service/service.rs | 155 +------------------------------ tests/test_client.rs | 8 +- tests/test_server.rs | 198 ++++++++++++++++++++++++++++------------ 11 files changed, 300 insertions(+), 619 deletions(-) create mode 100644 src/builder.rs diff --git a/examples/echo.rs b/examples/echo.rs index eb4aaa657..8ec0e6a97 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -1,9 +1,8 @@ use std::{env, io}; use actix_http::HttpMessage; -use actix_http::{h1, Request, Response}; +use actix_http::{HttpService, Request, Response}; use actix_server::Server; -use actix_service::NewService; use bytes::Bytes; use futures::Future; use http::header::HeaderValue; @@ -15,10 +14,9 @@ fn main() -> io::Result<()> { Server::build() .bind("echo", "127.0.0.1:8080", || { - h1::H1Service::build() + HttpService::build() .client_timeout(1000) .client_disconnect(1000) - .server_hostname("localhost") .finish(|mut req: Request| { req.body().limit(512).and_then(|bytes: Bytes| { info!("request body: {:?}", bytes); @@ -27,7 +25,6 @@ fn main() -> io::Result<()> { Ok(res.body(bytes)) }) }) - .map(|_| ()) })? .run() } diff --git a/examples/echo2.rs b/examples/echo2.rs index 3bb8d83d5..101adc1cf 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -2,9 +2,8 @@ use std::{env, io}; use actix_http::http::HeaderValue; use actix_http::HttpMessage; -use actix_http::{h1, Error, Request, Response}; +use actix_http::{Error, HttpService, Request, Response}; use actix_server::Server; -use actix_service::NewService; use bytes::Bytes; use futures::Future; use log::info; @@ -24,12 +23,7 @@ fn main() -> io::Result<()> { Server::build() .bind("echo", "127.0.0.1:8080", || { - h1::H1Service::build() - .client_timeout(1000) - .client_disconnect(1000) - .server_hostname("localhost") - .finish(|_req: Request| handle_request(_req)) - .map(|_| ()) + HttpService::build().finish(|_req: Request| handle_request(_req)) })? .run() } diff --git a/examples/hello-world.rs b/examples/hello-world.rs index 05d69fed8..6e3820390 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -1,8 +1,7 @@ use std::{env, io}; -use actix_http::{h1, Response}; +use actix_http::{HttpService, Response}; use actix_server::Server; -use actix_service::NewService; use futures::future; use http::header::HeaderValue; use log::info; @@ -13,17 +12,15 @@ fn main() -> io::Result<()> { Server::build() .bind("hello-world", "127.0.0.1:8080", || { - h1::H1Service::build() + HttpService::build() .client_timeout(1000) .client_disconnect(1000) - .server_hostname("localhost") .finish(|_req| { info!("{:?}", _req); let mut res = Response::Ok(); res.header("x-head", HeaderValue::from_static("dummy value!")); future::ok::<_, ()>(res.body("Hello world!")) }) - .map(|_| ()) })? .run() } diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 000000000..c55b8d5fc --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,141 @@ +use std::fmt::Debug; +use std::marker::PhantomData; + +use actix_server_config::ServerConfig as SrvConfig; +use actix_service::{IntoNewService, NewService}; + +use crate::body::MessageBody; +use crate::config::{KeepAlive, ServiceConfig}; +use crate::request::Request; +use crate::response::Response; + +use crate::h1::H1Service; +use crate::h2::H2Service; +use crate::service::HttpService; + +/// A http service builder +/// +/// This type can be used to construct an instance of `http service` through a +/// builder-like pattern. +pub struct HttpServiceBuilder { + keep_alive: KeepAlive, + client_timeout: u64, + client_disconnect: u64, + _t: PhantomData<(T, S)>, +} + +impl HttpServiceBuilder +where + S: NewService, + S::Error: Debug + 'static, + S::Service: 'static, +{ + /// Create instance of `ServiceConfigBuilder` + pub fn new() -> HttpServiceBuilder { + HttpServiceBuilder { + keep_alive: KeepAlive::Timeout(5), + client_timeout: 5000, + client_disconnect: 0, + _t: PhantomData, + } + } + + /// Set server keep-alive setting. + /// + /// By default keep alive is set to a 5 seconds. + pub fn keep_alive>(mut self, val: U) -> Self { + self.keep_alive = val.into(); + self + } + + /// Set server client timeout in milliseconds for first request. + /// + /// Defines a timeout for reading client request header. If a client does not transmit + /// the entire set headers within this time, the request is terminated with + /// the 408 (Request Time-out) error. + /// + /// To disable timeout set value to 0. + /// + /// By default client timeout is set to 5000 milliseconds. + pub fn client_timeout(mut self, val: u64) -> Self { + self.client_timeout = val; + self + } + + /// Set server connection disconnect timeout in milliseconds. + /// + /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete + /// within this time, the request get dropped. This timeout affects secure connections. + /// + /// To disable timeout set value to 0. + /// + /// By default disconnect timeout is set to 3000 milliseconds. + pub fn client_disconnect(mut self, val: u64) -> Self { + self.client_disconnect = val; + self + } + + // #[cfg(feature = "ssl")] + // /// Configure alpn protocols for SslAcceptorBuilder. + // pub fn configure_openssl( + // builder: &mut openssl::ssl::SslAcceptorBuilder, + // ) -> io::Result<()> { + // let protos: &[u8] = b"\x02h2"; + // builder.set_alpn_select_callback(|_, protos| { + // const H2: &[u8] = b"\x02h2"; + // if protos.windows(3).any(|window| window == H2) { + // Ok(b"h2") + // } else { + // Err(openssl::ssl::AlpnError::NOACK) + // } + // }); + // builder.set_alpn_protos(&protos)?; + + // Ok(()) + // } + + /// Finish service configuration and create *http service* for HTTP/1 protocol. + pub fn h1(self, service: F) -> H1Service + where + B: MessageBody + 'static, + F: IntoNewService, + S::Response: Into>, + { + let cfg = ServiceConfig::new( + self.keep_alive, + self.client_timeout, + self.client_disconnect, + ); + H1Service::with_config(cfg, service.into_new_service()) + } + + /// Finish service configuration and create *http service* for HTTP/2 protocol. + pub fn h2(self, service: F) -> H2Service + where + B: MessageBody + 'static, + F: IntoNewService, + S::Response: Into>, + { + let cfg = ServiceConfig::new( + self.keep_alive, + self.client_timeout, + self.client_disconnect, + ); + H2Service::with_config(cfg, service.into_new_service()) + } + + /// Finish service configuration and create `HttpService` instance. + pub fn finish(self, service: F) -> HttpService + where + B: MessageBody + 'static, + F: IntoNewService, + S::Response: Into>, + { + let cfg = ServiceConfig::new( + self.keep_alive, + self.client_timeout, + self.client_disconnect, + ); + HttpService::with_config(cfg, service.into_new_service()) + } +} diff --git a/src/config.rs b/src/config.rs index a9e705c95..3c7df2feb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,12 +1,11 @@ use std::cell::UnsafeCell; +use std::fmt; use std::fmt::Write; use std::rc::Rc; use std::time::{Duration, Instant}; -use std::{fmt, net}; use bytes::BytesMut; use futures::{future, Future}; -use log::error; use time; use tokio_timer::{sleep, Delay}; @@ -90,11 +89,6 @@ impl ServiceConfig { })) } - /// Create worker settings builder. - pub fn build() -> ServiceConfigBuilder { - ServiceConfigBuilder::new() - } - #[inline] /// Keep alive duration if configured. pub fn keep_alive(&self) -> Option { @@ -177,116 +171,6 @@ impl ServiceConfig { } } -/// A service config builder -/// -/// This type can be used to construct an instance of `ServiceConfig` through a -/// builder-like pattern. -pub struct ServiceConfigBuilder { - keep_alive: KeepAlive, - client_timeout: u64, - client_disconnect: u64, - host: String, - addr: net::SocketAddr, - secure: bool, -} - -impl ServiceConfigBuilder { - /// Create instance of `ServiceConfigBuilder` - pub fn new() -> ServiceConfigBuilder { - ServiceConfigBuilder { - keep_alive: KeepAlive::Timeout(5), - client_timeout: 5000, - client_disconnect: 0, - secure: false, - host: "localhost".to_owned(), - addr: "127.0.0.1:8080".parse().unwrap(), - } - } - - /// Enable secure flag for current server. - /// This flags also enables `client disconnect timeout`. - /// - /// By default this flag is set to false. - pub fn secure(mut self) -> Self { - self.secure = true; - if self.client_disconnect == 0 { - self.client_disconnect = 3000; - } - self - } - - /// Set server keep-alive setting. - /// - /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(mut self, val: T) -> Self { - self.keep_alive = val.into(); - self - } - - /// Set server client timeout in milliseconds for first request. - /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } - - /// Set server connection disconnect timeout in milliseconds. - /// - /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete - /// within this time, the request get dropped. This timeout affects secure connections. - /// - /// To disable timeout set value to 0. - /// - /// By default disconnect timeout is set to 3000 milliseconds. - pub fn client_disconnect(mut self, val: u64) -> Self { - self.client_disconnect = val; - self - } - - /// Set server host name. - /// - /// Host name is used by application router aa a hostname for url - /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. - /// html#method.host) documentation for more information. - /// - /// By default host name is set to a "localhost" value. - pub fn server_hostname(mut self, val: &str) -> Self { - self.host = val.to_owned(); - self - } - - /// Set server ip address. - /// - /// Host name is used by application router aa a hostname for url - /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. - /// html#method.host) documentation for more information. - /// - /// By default server address is set to a "127.0.0.1:8080" - pub fn server_address(mut self, addr: S) -> Self { - match addr.to_socket_addrs() { - Err(err) => error!("Can not convert to SocketAddr: {}", err), - Ok(mut addrs) => { - if let Some(addr) = addrs.next() { - self.addr = addr; - } - } - } - self - } - - /// Finish service configuration and create `ServiceConfig` object. - pub fn finish(&mut self) -> ServiceConfig { - ServiceConfig::new(self.keep_alive, self.client_timeout, self.client_disconnect) - } -} - struct Date { bytes: [u8; DATE_VALUE_LENGTH], pos: usize, diff --git a/src/h1/service.rs b/src/h1/service.rs index 6a36678b3..e55ff0d99 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -1,6 +1,5 @@ use std::fmt::Debug; use std::marker::PhantomData; -use std::net; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_server_config::ServerConfig as SrvConfig; @@ -8,7 +7,6 @@ use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream}; -use log::error; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; @@ -57,11 +55,6 @@ where _t: PhantomData, } } - - /// Create builder for `HttpService` instance. - pub fn build() -> H1ServiceBuilder { - H1ServiceBuilder::new() - } } impl NewService for H1Service @@ -89,135 +82,6 @@ where } } -/// A http/1 new service builder -/// -/// This type can be used to construct an instance of `ServiceConfig` through a -/// builder-like pattern. -pub struct H1ServiceBuilder { - keep_alive: KeepAlive, - client_timeout: u64, - client_disconnect: u64, - host: String, - addr: net::SocketAddr, - secure: bool, - _t: PhantomData<(T, S)>, -} - -impl H1ServiceBuilder -where - S: NewService, - S::Error: Debug, -{ - /// Create instance of `ServiceConfigBuilder` - pub fn new() -> H1ServiceBuilder { - H1ServiceBuilder { - keep_alive: KeepAlive::Timeout(5), - client_timeout: 5000, - client_disconnect: 0, - secure: false, - host: "localhost".to_owned(), - addr: "127.0.0.1:8080".parse().unwrap(), - _t: PhantomData, - } - } - - /// Enable secure flag for current server. - /// This flags also enables `client disconnect timeout`. - /// - /// By default this flag is set to false. - pub fn secure(mut self) -> Self { - self.secure = true; - if self.client_disconnect == 0 { - self.client_disconnect = 3000; - } - self - } - - /// Set server keep-alive setting. - /// - /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(mut self, val: U) -> Self { - self.keep_alive = val.into(); - self - } - - /// Set server client timeout in milliseconds for first request. - /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } - - /// Set server connection disconnect timeout in milliseconds. - /// - /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete - /// within this time, the request get dropped. This timeout affects secure connections. - /// - /// To disable timeout set value to 0. - /// - /// By default disconnect timeout is set to 3000 milliseconds. - pub fn client_disconnect(mut self, val: u64) -> Self { - self.client_disconnect = val; - self - } - - /// Set server host name. - /// - /// Host name is used by application router aa a hostname for url - /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. - /// html#method.host) documentation for more information. - /// - /// By default host name is set to a "localhost" value. - pub fn server_hostname(mut self, val: &str) -> Self { - self.host = val.to_owned(); - self - } - - /// Set server ip address. - /// - /// Host name is used by application router aa a hostname for url - /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. - /// html#method.host) documentation for more information. - /// - /// By default server address is set to a "127.0.0.1:8080" - pub fn server_address(mut self, addr: U) -> Self { - match addr.to_socket_addrs() { - Err(err) => error!("Can not convert to SocketAddr: {}", err), - Ok(mut addrs) => { - if let Some(addr) = addrs.next() { - self.addr = addr; - } - } - } - self - } - - /// Finish service configuration and create `H1Service` instance. - pub fn finish(self, service: F) -> H1Service - where - B: MessageBody, - F: IntoNewService, - { - let cfg = ServiceConfig::new( - self.keep_alive, - self.client_timeout, - self.client_disconnect, - ); - H1Service { - cfg, - srv: service.into_new_service(), - _t: PhantomData, - } - } -} - #[doc(hidden)] pub struct H1ServiceResponse, B> { fut: ::Future, diff --git a/src/h2/service.rs b/src/h2/service.rs index 57515d4e8..ce7c3b5dd 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -59,11 +59,6 @@ where _t: PhantomData, } } - - /// Create builder for `HttpService` instance. - pub fn build() -> H2ServiceBuilder { - H2ServiceBuilder::new() - } } impl NewService for H2Service @@ -91,136 +86,6 @@ where } } -/// A http/2 new service builder -/// -/// This type can be used to construct an instance of `ServiceConfig` through a -/// builder-like pattern. -pub struct H2ServiceBuilder { - keep_alive: KeepAlive, - client_timeout: u64, - client_disconnect: u64, - host: String, - addr: net::SocketAddr, - secure: bool, - _t: PhantomData<(T, S)>, -} - -impl H2ServiceBuilder -where - S: NewService, - S::Service: 'static, - S::Error: Debug + 'static, -{ - /// Create instance of `H2ServiceBuilder` - pub fn new() -> H2ServiceBuilder { - H2ServiceBuilder { - keep_alive: KeepAlive::Timeout(5), - client_timeout: 5000, - client_disconnect: 0, - secure: false, - host: "localhost".to_owned(), - addr: "127.0.0.1:8080".parse().unwrap(), - _t: PhantomData, - } - } - - /// Enable secure flag for current server. - /// This flags also enables `client disconnect timeout`. - /// - /// By default this flag is set to false. - pub fn secure(mut self) -> Self { - self.secure = true; - if self.client_disconnect == 0 { - self.client_disconnect = 3000; - } - self - } - - /// Set server keep-alive setting. - /// - /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(mut self, val: U) -> Self { - self.keep_alive = val.into(); - self - } - - /// Set server client timeout in milliseconds for first request. - /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } - - /// Set server connection disconnect timeout in milliseconds. - /// - /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete - /// within this time, the request get dropped. This timeout affects secure connections. - /// - /// To disable timeout set value to 0. - /// - /// By default disconnect timeout is set to 3000 milliseconds. - pub fn client_disconnect(mut self, val: u64) -> Self { - self.client_disconnect = val; - self - } - - /// Set server host name. - /// - /// Host name is used by application router aa a hostname for url - /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. - /// html#method.host) documentation for more information. - /// - /// By default host name is set to a "localhost" value. - pub fn server_hostname(mut self, val: &str) -> Self { - self.host = val.to_owned(); - self - } - - /// Set server ip address. - /// - /// Host name is used by application router aa a hostname for url - /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. - /// html#method.host) documentation for more information. - /// - /// By default server address is set to a "127.0.0.1:8080" - pub fn server_address(mut self, addr: U) -> Self { - match addr.to_socket_addrs() { - Err(err) => error!("Can not convert to SocketAddr: {}", err), - Ok(mut addrs) => { - if let Some(addr) = addrs.next() { - self.addr = addr; - } - } - } - self - } - - /// Finish service configuration and create `H1Service` instance. - pub fn finish(self, service: F) -> H2Service - where - B: MessageBody, - F: IntoNewService, - { - let cfg = ServiceConfig::new( - self.keep_alive, - self.client_timeout, - self.client_disconnect, - ); - H2Service { - cfg, - srv: service.into_new_service(), - _t: PhantomData, - } - } -} - #[doc(hidden)] pub struct H2ServiceResponse, B> { fut: ::Future, diff --git a/src/lib.rs b/src/lib.rs index 9d8bc50f1..41ee55fec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,6 +69,7 @@ extern crate log; pub mod body; +mod builder; pub mod client; mod config; mod extensions; @@ -89,7 +90,8 @@ pub mod h2; pub mod test; pub mod ws; -pub use self::config::{KeepAlive, ServiceConfig, ServiceConfigBuilder}; +pub use self::builder::HttpServiceBuilder; +pub use self::config::{KeepAlive, ServiceConfig}; pub use self::error::{Error, ResponseError, Result}; pub use self::extensions::Extensions; pub use self::httpmessage::HttpMessage; diff --git a/src/service/service.rs b/src/service/service.rs index 0fa5d6653..ac28c77a5 100644 --- a/src/service/service.rs +++ b/src/service/service.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; use std::marker::PhantomData; -use std::{fmt, io, net}; +use std::{fmt, io}; use actix_codec::{AsyncRead, AsyncWrite, Framed, FramedParts}; use actix_server_config::ServerConfig as SrvConfig; @@ -12,11 +12,11 @@ use h2::server::{self, Handshake}; use log::error; use crate::body::MessageBody; +use crate::builder::HttpServiceBuilder; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::DispatchError; use crate::request::Request; use crate::response::Response; - use crate::{h1, h2::Dispatcher}; /// `NewService` HTTP1.1/HTTP2 transport implementation @@ -46,7 +46,7 @@ where } /// Create new `HttpService` instance with config. - pub fn with_config>( + pub(crate) fn with_config>( cfg: ServiceConfig, service: F, ) -> Self { @@ -88,155 +88,6 @@ where } } -/// A http service factory builder -/// -/// This type can be used to construct an instance of `ServiceConfig` through a -/// builder-like pattern. -pub struct HttpServiceBuilder { - keep_alive: KeepAlive, - client_timeout: u64, - client_disconnect: u64, - host: String, - addr: net::SocketAddr, - secure: bool, - _t: PhantomData<(T, S)>, -} - -impl HttpServiceBuilder -where - S: NewService, - S::Service: 'static, - S::Error: Debug + 'static, -{ - /// Create instance of `HttpServiceBuilder` type - pub fn new() -> HttpServiceBuilder { - HttpServiceBuilder { - keep_alive: KeepAlive::Timeout(5), - client_timeout: 5000, - client_disconnect: 0, - secure: false, - host: "localhost".to_owned(), - addr: "127.0.0.1:8080".parse().unwrap(), - _t: PhantomData, - } - } - - /// Enable secure flag for current server. - /// This flags also enables `client disconnect timeout`. - /// - /// By default this flag is set to false. - pub fn secure(mut self) -> Self { - self.secure = true; - if self.client_disconnect == 0 { - self.client_disconnect = 3000; - } - self - } - - /// Set server keep-alive setting. - /// - /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(mut self, val: U) -> Self { - self.keep_alive = val.into(); - self - } - - /// Set server client timeout in milliseconds for first request. - /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } - - /// Set server connection disconnect timeout in milliseconds. - /// - /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete - /// within this time, the request get dropped. This timeout affects secure connections. - /// - /// To disable timeout set value to 0. - /// - /// By default disconnect timeout is set to 3000 milliseconds. - pub fn client_disconnect(mut self, val: u64) -> Self { - self.client_disconnect = val; - self - } - - /// Set server host name. - /// - /// Host name is used by application router aa a hostname for url - /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. - /// html#method.host) documentation for more information. - /// - /// By default host name is set to a "localhost" value. - pub fn server_hostname(mut self, val: &str) -> Self { - self.host = val.to_owned(); - self - } - - /// Set server ip address. - /// - /// Host name is used by application router aa a hostname for url - /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. - /// html#method.host) documentation for more information. - /// - /// By default server address is set to a "127.0.0.1:8080" - pub fn server_address(mut self, addr: U) -> Self { - match addr.to_socket_addrs() { - Err(err) => error!("Can not convert to SocketAddr: {}", err), - Ok(mut addrs) => { - if let Some(addr) = addrs.next() { - self.addr = addr; - } - } - } - self - } - - // #[cfg(feature = "ssl")] - // /// Configure alpn protocols for SslAcceptorBuilder. - // pub fn configure_openssl( - // builder: &mut openssl::ssl::SslAcceptorBuilder, - // ) -> io::Result<()> { - // let protos: &[u8] = b"\x02h2"; - // builder.set_alpn_select_callback(|_, protos| { - // const H2: &[u8] = b"\x02h2"; - // if protos.windows(3).any(|window| window == H2) { - // Ok(b"h2") - // } else { - // Err(openssl::ssl::AlpnError::NOACK) - // } - // }); - // builder.set_alpn_protos(&protos)?; - - // Ok(()) - // } - - /// Finish service configuration and create `HttpService` instance. - pub fn finish(self, service: F) -> HttpService - where - B: MessageBody, - F: IntoNewService, - { - let cfg = ServiceConfig::new( - self.keep_alive, - self.client_timeout, - self.client_disconnect, - ); - HttpService { - cfg, - srv: service.into_new_service(), - _t: PhantomData, - } - } -} - #[doc(hidden)] pub struct HttpServiceResponse, B> { fut: ::Future, diff --git a/tests/test_client.rs b/tests/test_client.rs index f44c45cbc..782e487ca 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -3,7 +3,7 @@ use bytes::Bytes; use futures::future::{self, ok}; use actix_http::HttpMessage; -use actix_http::{client, h1, Request, Response}; +use actix_http::{client, HttpService, Request, Response}; use actix_http_test::TestServer; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -32,7 +32,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ fn test_h1_v2() { env_logger::init(); let mut srv = TestServer::new(move || { - h1::H1Service::build() + HttpService::build() .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); @@ -66,7 +66,7 @@ fn test_h1_v2() { #[test] fn test_connection_close() { let mut srv = TestServer::new(move || { - h1::H1Service::build() + HttpService::build() .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); @@ -80,7 +80,7 @@ fn test_connection_close() { #[test] fn test_with_query_parameter() { let mut srv = TestServer::new(move || { - h1::H1Service::build() + HttpService::build() .finish(|req: Request| { if req.uri().query().unwrap().contains("qp=") { ok::<_, ()>(Response::Ok().finish()) diff --git a/tests/test_server.rs b/tests/test_server.rs index b7cd5557b..7a28bca8a 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -10,20 +10,18 @@ use futures::stream::once; use actix_http::body::Body; use actix_http::{ - body, client, h1, h2, http, Error, HttpMessage as HttpMessage2, HttpService, - KeepAlive, Request, Response, + body, client, http, Error, HttpMessage as HttpMessage2, HttpService, KeepAlive, + Request, Response, }; #[test] fn test_h1() { let mut srv = TestServer::new(|| { - h1::H1Service::build() + HttpService::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) .client_disconnect(1000) - .server_hostname("localhost") - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) + .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let req = client::ClientRequest::get(srv.url("/")).finish().unwrap(); @@ -38,7 +36,6 @@ fn test_h1_2() { .keep_alive(KeepAlive::Disabled) .client_timeout(1000) .client_disconnect(1000) - .server_hostname("localhost") .finish(|req: Request| { assert_eq!(req.version(), http::Version::HTTP_11); future::ok::<_, ()>(Response::Ok().finish()) @@ -83,8 +80,8 @@ fn test_h2() -> std::io::Result<()> { .clone() .map_err(|e| println!("Openssl error: {}", e)) .and_then( - h2::H2Service::build() - .finish(|_| future::ok::<_, Error>(Response::Ok().finish())) + HttpService::build() + .h2(|_| future::ok::<_, Error>(Response::Ok().finish())) .map_err(|_| ()), ) }); @@ -129,8 +126,8 @@ fn test_h2_body() -> std::io::Result<()> { .clone() .map_err(|e| println!("Openssl error: {}", e)) .and_then( - h2::H2Service::build() - .finish(|mut req: Request<_>| { + HttpService::build() + .h2(|mut req: Request<_>| { req.body() .limit(1024 * 1024) .and_then(|body| Ok(Response::Ok().body(body))) @@ -153,10 +150,9 @@ fn test_h2_body() -> std::io::Result<()> { #[test] fn test_slow_request() { let srv = TestServer::new(|| { - h1::H1Service::build() + HttpService::build() .client_timeout(100) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) + .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -167,9 +163,9 @@ fn test_slow_request() { } #[test] -fn test_malformed_request() { +fn test_http1_malformed_request() { let srv = TestServer::new(|| { - h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().finish())).map(|_| ()) + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -180,11 +176,9 @@ fn test_malformed_request() { } #[test] -fn test_keepalive() { +fn test_http1_keepalive() { let srv = TestServer::new(|| { - h1::H1Service::build() - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -200,12 +194,11 @@ fn test_keepalive() { } #[test] -fn test_keepalive_timeout() { +fn test_http1_keepalive_timeout() { let srv = TestServer::new(|| { - h1::H1Service::build() + HttpService::build() .keep_alive(1) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) + .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -221,11 +214,9 @@ fn test_keepalive_timeout() { } #[test] -fn test_keepalive_close() { +fn test_http1_keepalive_close() { let srv = TestServer::new(|| { - h1::H1Service::build() - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -241,11 +232,9 @@ fn test_keepalive_close() { } #[test] -fn test_keepalive_http10_default_close() { +fn test_http10_keepalive_default_close() { let srv = TestServer::new(|| { - h1::H1Service::build() - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -260,11 +249,9 @@ fn test_keepalive_http10_default_close() { } #[test] -fn test_keepalive_http10() { +fn test_http10_keepalive() { let srv = TestServer::new(|| { - h1::H1Service::build() - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -286,12 +273,11 @@ fn test_keepalive_http10() { } #[test] -fn test_keepalive_disabled() { +fn test_http1_keepalive_disabled() { let srv = TestServer::new(|| { - h1::H1Service::build() + HttpService::build() .keep_alive(KeepAlive::Disabled) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) + .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -313,7 +299,7 @@ fn test_content_length() { }; let mut srv = TestServer::new(|| { - h1::H1Service::new(|req: Request| { + HttpService::build().h1(|req: Request| { let indx: usize = req.uri().path()[1..].parse().unwrap(); let statuses = [ StatusCode::NO_CONTENT, @@ -325,7 +311,6 @@ fn test_content_length() { ]; future::ok::<_, ()>(Response::new(statuses[indx])) }) - .map(|_| ()) }); let header = HeaderName::from_static("content-length"); @@ -356,6 +341,65 @@ fn test_content_length() { } } +// TODO: fix +// #[test] +// fn test_h2_content_length() { +// use actix_http::http::{ +// header::{HeaderName, HeaderValue}, +// StatusCode, +// }; +// let openssl = ssl_acceptor().unwrap(); + +// let mut srv = TestServer::new(move || { +// openssl +// .clone() +// .map_err(|e| println!("Openssl error: {}", e)) +// .and_then( +// HttpService::build() +// .h2(|req: Request| { +// let indx: usize = req.uri().path()[1..].parse().unwrap(); +// let statuses = [ +// StatusCode::NO_CONTENT, +// StatusCode::CONTINUE, +// StatusCode::SWITCHING_PROTOCOLS, +// StatusCode::PROCESSING, +// StatusCode::OK, +// StatusCode::NOT_FOUND, +// ]; +// future::ok::<_, ()>(Response::new(statuses[indx])) +// }) +// .map_err(|_| ()), +// ) +// }); + +// let header = HeaderName::from_static("content-length"); +// let value = HeaderValue::from_static("0"); + +// { +// for i in 0..4 { +// let req = client::ClientRequest::get(srv.surl(&format!("/{}", i))) +// .finish() +// .unwrap(); +// let response = srv.send_request(req).unwrap(); +// assert_eq!(response.headers().get(&header), None); + +// let req = client::ClientRequest::head(srv.surl(&format!("/{}", i))) +// .finish() +// .unwrap(); +// let response = srv.send_request(req).unwrap(); +// assert_eq!(response.headers().get(&header), None); +// } + +// for i in 4..6 { +// let req = client::ClientRequest::get(srv.surl(&format!("/{}", i))) +// .finish() +// .unwrap(); +// let response = srv.send_request(req).unwrap(); +// assert_eq!(response.headers().get(&header), Some(&value)); +// } +// } +// } + #[test] fn test_headers() { let data = STR.repeat(10); @@ -363,7 +407,7 @@ fn test_headers() { let mut srv = TestServer::new(move || { let data = data.clone(); - h1::H1Service::new(move |_| { + HttpService::build().h1(move |_| { let mut builder = Response::Ok(); for idx in 0..90 { builder.header( @@ -384,9 +428,8 @@ fn test_headers() { ); } future::ok::<_, ()>(builder.body(data.clone())) - }).map(|_| ()) + }) }); - let mut connector = srv.new_connector(); let req = srv.get().finish().unwrap(); @@ -399,6 +442,52 @@ fn test_headers() { assert_eq!(bytes, Bytes::from(data2)); } +#[test] +fn test_h2_headers() { + let data = STR.repeat(10); + let data2 = data.clone(); + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + let data = data.clone(); + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build().h2(move |_| { + let mut builder = Response::Ok(); + for idx in 0..90 { + builder.header( + format!("X-TEST-{}", idx).as_str(), + "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", + ); + } + future::ok::<_, ()>(builder.body(data.clone())) + }).map_err(|_| ())) + }); + let mut connector = srv.new_connector(); + + let req = client::ClientRequest::get(srv.surl("/")).finish().unwrap(); + let mut response = srv.block_on(req.send(&mut connector)).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from(data2)); +} + const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -424,7 +513,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_body() { let mut srv = TestServer::new(|| { - h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().body(STR))) }); let req = srv.get().finish().unwrap(); @@ -439,7 +528,7 @@ fn test_body() { #[test] fn test_head_empty() { let mut srv = TestServer::new(|| { - h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) + HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); @@ -462,10 +551,9 @@ fn test_head_empty() { #[test] fn test_head_binary() { let mut srv = TestServer::new(|| { - h1::H1Service::new(|_| { + HttpService::build().h1(|_| { ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) }) - .map(|_| ()) }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); @@ -488,7 +576,7 @@ fn test_head_binary() { #[test] fn test_head_binary2() { let mut srv = TestServer::new(|| { - h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) + HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); @@ -507,14 +595,13 @@ fn test_head_binary2() { #[test] fn test_body_length() { let mut srv = TestServer::new(|| { - h1::H1Service::new(|_| { + HttpService::build().h1(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( Response::Ok() .body(Body::from_message(body::SizedStream::new(STR.len(), body))), ) }) - .map(|_| ()) }); let req = srv.get().finish().unwrap(); @@ -529,11 +616,10 @@ fn test_body_length() { #[test] fn test_body_chunked_explicit() { let mut srv = TestServer::new(|| { - h1::H1Service::new(|_| { + HttpService::build().h1(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) }) - .map(|_| ()) }); let req = srv.get().finish().unwrap(); @@ -550,7 +636,7 @@ fn test_body_chunked_explicit() { #[test] fn test_body_chunked_implicit() { let mut srv = TestServer::new(|| { - h1::H1Service::new(|_| { + HttpService::build().h1(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) }) @@ -571,7 +657,7 @@ use actix_service::fn_cfg_factory; #[test] fn test_response_http_error_handling() { let mut srv = TestServer::new(|| { - h1::H1Service::new(fn_cfg_factory(|_: &ServerConfig| { + HttpService::build().h1(fn_cfg_factory(|_: &ServerConfig| { Ok::<_, ()>(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( From 9c7056e9b84f63c78587e4ceab32f8c4b6e526aa Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 13:38:56 -0800 Subject: [PATCH 177/208] fix connector --- src/builder.rs | 2 +- src/client/connector.rs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index c55b8d5fc..1df96b0e1 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -69,7 +69,7 @@ where /// /// To disable timeout set value to 0. /// - /// By default disconnect timeout is set to 3000 milliseconds. + /// By default disconnect timeout is set to 0. pub fn client_disconnect(mut self, val: u64) -> Self { self.client_disconnect = val; self diff --git a/src/client/connector.rs b/src/client/connector.rs index 5eb85ba61..ccb5dbce5 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -269,7 +269,11 @@ mod connect_impl { impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + >, { type Request = Connect; type Response = IoConnection; From 6c4be45787ed0a3f684f844a67402eeebb395c77 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 14:33:33 -0800 Subject: [PATCH 178/208] update deps --- Cargo.toml | 14 ++++---------- test-server/Cargo.toml | 8 +++----- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 594ab88db..ae81f1520 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,15 +38,10 @@ ssl = ["openssl", "actix-connector/ssl"] fail = ["failure"] [dependencies] -#actix-service = "0.3.2" +actix-service = "0.3.3" actix-codec = "0.1.1" - -#actix-connector = "0.3.0" -#actix-utils = "0.3.1" - -actix-connector = { git="https://github.com/actix/actix-net.git" } -actix-service = { git="https://github.com/actix/actix-net.git" } -actix-utils = { git="https://github.com/actix/actix-net.git" } +actix-connector = "0.3.0" +actix-utils = "0.3.3" actix-server-config = { git="https://github.com/actix/actix-net.git" } base64 = "0.10" @@ -90,8 +85,7 @@ failure = { version = "0.1.5", optional = true } actix-rt = "0.2.0" #actix-server = { version = "0.3.0", features=["ssl"] } actix-server = { git="https://github.com/actix/actix-net.git", features=["ssl"] } -#actix-connector = { version = "0.3.0", features=["ssl"] } -actix-connector = { git="https://github.com/actix/actix-net.git", features=["ssl"] } +actix-connector = { version = "0.3.0", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 554ab20e3..49f18f04e 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -32,15 +32,13 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] [dependencies] -actix-codec = "0.1" +actix-codec = "0.1.1" actix-rt = "0.2.0" actix-http = { path=".." } -#actix-service = "0.3.2" -actix-service = { git="https://github.com/actix/actix-net.git" } +actix-service = "0.3.3" #actix-server = "0.3.0" actix-server = { git="https://github.com/actix/actix-net.git" } -#actix-utils = "0.3.2" -actix-utils = { git="https://github.com/actix/actix-net.git" } +actix-utils = "0.3.2" base64 = "0.10" bytes = "0.4" From 513ce0b08d51b75d73a98d6087cd329ee95ba09c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 17:42:35 -0800 Subject: [PATCH 179/208] add json and form client request's method --- src/client/request.rs | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/client/request.rs b/src/client/request.rs index 7e971756d..efae5b94e 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -7,6 +7,8 @@ use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; use futures::{Future, Stream}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use serde::Serialize; +use serde_json; use crate::body::{BodyStream, MessageBody}; use crate::error::Error; @@ -558,6 +560,48 @@ impl ClientRequestBuilder { Ok(ClientRequest { head, body }) } + /// Set a JSON body and generate `ClientRequest` + /// + /// `ClientRequestBuilder` can not be used after this call. + pub fn json( + &mut self, + value: T, + ) -> Result, Error> { + let body = serde_json::to_string(&value)?; + + let contains = if let Some(head) = parts(&mut self.head, &self.err) { + head.headers.contains_key(header::CONTENT_TYPE) + } else { + true + }; + if !contains { + self.header(header::CONTENT_TYPE, "application/json"); + } + + Ok(self.body(body)?) + } + + /// Set a urlencoded body and generate `ClientRequest` + /// + /// `ClientRequestBuilder` can not be used after this call. + pub fn form( + &mut self, + value: T, + ) -> Result, Error> { + let body = serde_urlencoded::to_string(&value)?; + + let contains = if let Some(head) = parts(&mut self.head, &self.err) { + head.headers.contains_key(header::CONTENT_TYPE) + } else { + true + }; + if !contains { + self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded"); + } + + Ok(self.body(body)?) + } + /// Set an streaming body and generate `ClientRequest`. /// /// `ClientRequestBuilder` can not be used after this call. From d7557720394a4518d720f54c31f7dd17d11ada3c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 15:30:31 -0700 Subject: [PATCH 180/208] add From impls for ResponseBuilder --- src/response.rs | 80 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/src/response.rs b/src/response.rs index 4e1fe2142..34ac54ea7 100644 --- a/src/response.rs +++ b/src/response.rs @@ -57,28 +57,6 @@ impl Response { resp } - /// Convert `Response` to a `ResponseBuilder` - #[inline] - pub fn into_builder(self) -> ResponseBuilder { - // If this response has cookies, load them into a jar - let mut jar: Option = None; - for c in self.cookies() { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); - } - } - - ResponseBuilder { - head: Some(self.head), - err: None, - cookies: jar, - } - } - /// Convert response to response with body pub fn into_body(self) -> Response { let b = match self.body { @@ -692,6 +670,62 @@ fn parts<'a>( parts.as_mut() } +/// Convert `Response` to a `ResponseBuilder`. Body get dropped. +impl From> for ResponseBuilder { + fn from(res: Response) -> ResponseBuilder { + // If this response has cookies, load them into a jar + let mut jar: Option = None; + for c in res.cookies() { + if let Some(ref mut j) = jar { + j.add_original(c.into_owned()); + } else { + let mut j = CookieJar::new(); + j.add_original(c.into_owned()); + jar = Some(j); + } + } + + ResponseBuilder { + head: Some(res.head), + err: None, + cookies: jar, + } + } +} + +/// Convert `ResponseHead` to a `ResponseBuilder` +impl<'a> From<&'a ResponseHead> for ResponseBuilder { + fn from(head: &'a ResponseHead) -> ResponseBuilder { + // If this response has cookies, load them into a jar + let mut jar: Option = None; + let cookies = CookieIter { + iter: head.headers.get_all(header::SET_COOKIE).iter(), + }; + for c in cookies { + if let Some(ref mut j) = jar { + j.add_original(c.into_owned()); + } else { + let mut j = CookieJar::new(); + j.add_original(c.into_owned()); + jar = Some(j); + } + } + + let mut msg: Message = Message::new(); + msg.version = head.version; + msg.status = head.status; + msg.reason = head.reason; + msg.headers = head.headers.clone(); + msg.no_chunking = head.no_chunking; + + ResponseBuilder { + head: Some(msg), + err: None, + cookies: jar, + } + } +} + impl IntoFuture for ResponseBuilder { type Item = Response; type Error = Error; @@ -989,7 +1023,7 @@ mod tests { resp.add_cookie(&http::Cookie::new("cookie1", "val100")) .unwrap(); - let mut builder = resp.into_builder(); + let mut builder: ResponseBuilder = resp.into(); let resp = builder.status(StatusCode::BAD_REQUEST).finish(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); From 615fbb49bdeb3fd8efa83672a7b71caf4f146465 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 17:00:03 -0700 Subject: [PATCH 181/208] support cookies in TestRequest --- src/test.rs | 117 +++++++++++----------------------------------------- 1 file changed, 24 insertions(+), 93 deletions(-) diff --git a/src/test.rs b/src/test.rs index e2e0fd76e..4ac30be00 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,10 +1,12 @@ //! Test Various helpers for Actix applications to use during testing. +use std::fmt::Write as FmtWrite; use std::str::FromStr; use bytes::Bytes; -use cookie::Cookie; -use http::header::HeaderName; +use cookie::{Cookie, CookieJar}; +use http::header::{self, HeaderName, HeaderValue}; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; +use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use crate::header::{Header, IntoHeaderValue}; use crate::payload::Payload; @@ -44,7 +46,7 @@ struct Inner { method: Method, uri: Uri, headers: HeaderMap, - _cookies: Option>>, + cookies: CookieJar, payload: Option, } @@ -55,7 +57,7 @@ impl Default for TestRequest { uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, headers: HeaderMap::new(), - _cookies: None, + cookies: CookieJar::new(), payload: None, })) } @@ -123,6 +125,12 @@ impl TestRequest { panic!("Can not create header"); } + /// Set cookie for this request + pub fn cookie<'a>(mut self, cookie: Cookie<'a>) -> Self { + parts(&mut self.0).cookies.add(cookie.into_owned()); + self + } + /// Set request payload pub fn set_payload>(&mut self, data: B) -> &mut Self { let mut payload = crate::h1::Payload::empty(); @@ -143,7 +151,7 @@ impl TestRequest { version, headers, payload, - .. + cookies, } = self.0.take().expect("cannot reuse test request builder");; let mut req = if let Some(pl) = payload { @@ -158,96 +166,19 @@ impl TestRequest { head.version = version; head.headers = headers; - // req.set_cookies(cookies); + let mut cookie = String::new(); + for c in cookies.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); + } + head.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); + req } - - // /// This method generates `HttpRequest` instance and runs handler - // /// with generated request. - // pub fn run>(self, h: &H) -> Result { - // let req = self.finish(); - // let resp = h.handle(&req); - - // match resp.respond_to(&req) { - // Ok(resp) => match resp.into().into() { - // AsyncResultItem::Ok(resp) => Ok(resp), - // AsyncResultItem::Err(err) => Err(err), - // AsyncResultItem::Future(fut) => { - // let mut sys = System::new("test"); - // sys.block_on(fut) - // } - // }, - // Err(err) => Err(err.into()), - // } - // } - - // /// This method generates `HttpRequest` instance and runs handler - // /// with generated request. - // /// - // /// This method panics is handler returns actor. - // pub fn run_async(self, h: H) -> Result - // where - // H: Fn(HttpRequest) -> F + 'static, - // F: Future + 'static, - // R: Responder + 'static, - // E: Into + 'static, - // { - // let req = self.finish(); - // let fut = h(req.clone()); - - // let mut sys = System::new("test"); - // match sys.block_on(fut) { - // Ok(r) => match r.respond_to(&req) { - // Ok(reply) => match reply.into().into() { - // AsyncResultItem::Ok(resp) => Ok(resp), - // _ => panic!("Nested async replies are not supported"), - // }, - // Err(e) => Err(e), - // }, - // Err(err) => Err(err), - // } - // } - - // /// This method generates `HttpRequest` instance and executes handler - // pub fn run_async_result(self, f: F) -> Result - // where - // F: FnOnce(&HttpRequest) -> R, - // R: Into>, - // { - // let req = self.finish(); - // let res = f(&req); - - // match res.into().into() { - // AsyncResultItem::Ok(resp) => Ok(resp), - // AsyncResultItem::Err(err) => Err(err), - // AsyncResultItem::Future(fut) => { - // let mut sys = System::new("test"); - // sys.block_on(fut) - // } - // } - // } - - // /// This method generates `HttpRequest` instance and executes handler - // pub fn execute(self, f: F) -> Result - // where - // F: FnOnce(&HttpRequest) -> R, - // R: Responder + 'static, - // { - // let req = self.finish(); - // let resp = f(&req); - - // match resp.respond_to(&req) { - // Ok(resp) => match resp.into().into() { - // AsyncResultItem::Ok(resp) => Ok(resp), - // AsyncResultItem::Err(err) => Err(err), - // AsyncResultItem::Future(fut) => { - // let mut sys = System::new("test"); - // sys.block_on(fut) - // } - // }, - // Err(err) => Err(err.into()), - // } - // } } #[inline] From 50a0cb5653d6d7558caa6a97b0526120486ec8ba Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 17:02:14 -0700 Subject: [PATCH 182/208] do no move self --- src/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test.rs b/src/test.rs index 4ac30be00..63ad14994 100644 --- a/src/test.rs +++ b/src/test.rs @@ -126,7 +126,7 @@ impl TestRequest { } /// Set cookie for this request - pub fn cookie<'a>(mut self, cookie: Cookie<'a>) -> Self { + pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { parts(&mut self.0).cookies.add(cookie.into_owned()); self } From 64360041944638c45f658b9c58b66186127f4344 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 17:06:43 -0700 Subject: [PATCH 183/208] set test cookie if it is not empty --- src/test.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test.rs b/src/test.rs index 63ad14994..c60d2d01c 100644 --- a/src/test.rs +++ b/src/test.rs @@ -172,10 +172,12 @@ impl TestRequest { let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); let _ = write!(&mut cookie, "; {}={}", name, value); } - head.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); + if !cookie.is_empty() { + head.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); + } req } From ad43ca735b748c41bc6ff0da8948544be85e590d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Mar 2019 15:09:42 -0700 Subject: [PATCH 184/208] update server service requirenments --- examples/framed_hello.rs | 7 +-- src/builder.rs | 6 +-- src/h1/service.rs | 67 ++++++++++++++-------------- src/h2/service.rs | 40 ++++++++--------- src/service/service.rs | 94 +++++++++++++++++++++++++++------------- tests/test_server.rs | 2 +- tests/test_ws.rs | 6 ++- 7 files changed, 130 insertions(+), 92 deletions(-) diff --git a/examples/framed_hello.rs b/examples/framed_hello.rs index ff3977854..74b0f7dfd 100644 --- a/examples/framed_hello.rs +++ b/examples/framed_hello.rs @@ -2,8 +2,8 @@ use std::{env, io}; use actix_codec::Framed; use actix_http::{h1, Response, SendResponse, ServiceConfig}; -use actix_server::Server; -use actix_service::NewService; +use actix_server::{Io, Server}; +use actix_service::{fn_service, NewService}; use actix_utils::framed::IntoFramed; use actix_utils::stream::TakeItem; use futures::Future; @@ -14,7 +14,8 @@ fn main() -> io::Result<()> { Server::build() .bind("framed_hello", "127.0.0.1:8080", || { - IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) + fn_service(|io: Io<_>| Ok(io.into_parts().0)) + .and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default()))) .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(_req, _framed): (_, Framed<_, _>)| { SendResponse::send(_framed, Response::Ok().body("Hello world!")) diff --git a/src/builder.rs b/src/builder.rs index 1df96b0e1..2f7466a90 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -95,7 +95,7 @@ where // } /// Finish service configuration and create *http service* for HTTP/1 protocol. - pub fn h1(self, service: F) -> H1Service + pub fn h1(self, service: F) -> H1Service where B: MessageBody + 'static, F: IntoNewService, @@ -110,7 +110,7 @@ where } /// Finish service configuration and create *http service* for HTTP/2 protocol. - pub fn h2(self, service: F) -> H2Service + pub fn h2(self, service: F) -> H2Service where B: MessageBody + 'static, F: IntoNewService, @@ -125,7 +125,7 @@ where } /// Finish service configuration and create `HttpService` instance. - pub fn finish(self, service: F) -> HttpService + pub fn finish(self, service: F) -> HttpService where B: MessageBody + 'static, F: IntoNewService, diff --git a/src/h1/service.rs b/src/h1/service.rs index e55ff0d99..f3301b9b2 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use std::marker::PhantomData; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_server_config::ServerConfig as SrvConfig; +use actix_server_config::{Io, ServerConfig as SrvConfig}; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use futures::future::{ok, FutureResult}; @@ -19,13 +19,13 @@ use super::dispatcher::Dispatcher; use super::Message; /// `NewService` implementation for HTTP1 transport -pub struct H1Service { +pub struct H1Service { srv: S, cfg: ServiceConfig, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl H1Service +impl H1Service where S: NewService, S::Error: Debug, @@ -57,7 +57,7 @@ where } } -impl NewService for H1Service +impl NewService for H1Service where T: AsyncRead + AsyncWrite, S: NewService, @@ -66,12 +66,12 @@ where S::Service: 'static, B: MessageBody, { - type Request = T; + type Request = Io; type Response = (); type Error = DispatchError; type InitError = S::InitError; - type Service = H1ServiceHandler; - type Future = H1ServiceResponse; + type Service = H1ServiceHandler; + type Future = H1ServiceResponse; fn new_service(&self, cfg: &SrvConfig) -> Self::Future { H1ServiceResponse { @@ -83,13 +83,13 @@ where } #[doc(hidden)] -pub struct H1ServiceResponse, B> { +pub struct H1ServiceResponse, B> { fut: ::Future, cfg: Option, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl Future for H1ServiceResponse +impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, S: NewService, @@ -98,7 +98,7 @@ where S::Response: Into>, B: MessageBody, { - type Item = H1ServiceHandler; + type Item = H1ServiceHandler; type Error = S::InitError; fn poll(&mut self) -> Poll { @@ -111,20 +111,20 @@ where } /// `Service` implementation for HTTP1 transport -pub struct H1ServiceHandler { +pub struct H1ServiceHandler { srv: CloneableService, cfg: ServiceConfig, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl H1ServiceHandler +impl H1ServiceHandler where S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, { - fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { + fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { H1ServiceHandler { srv: CloneableService::new(srv), cfg, @@ -133,7 +133,7 @@ where } } -impl Service for H1ServiceHandler +impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, S: Service, @@ -141,7 +141,7 @@ where S::Response: Into>, B: MessageBody, { - type Request = T; + type Request = Io; type Response = (); type Error = DispatchError; type Future = Dispatcher; @@ -153,19 +153,19 @@ where }) } - fn call(&mut self, req: T) -> Self::Future { - Dispatcher::new(req, self.cfg.clone(), self.srv.clone()) + fn call(&mut self, req: Self::Request) -> Self::Future { + Dispatcher::new(req.into_parts().0, self.cfg.clone(), self.srv.clone()) } } /// `NewService` implementation for `OneRequestService` service #[derive(Default)] -pub struct OneRequest { +pub struct OneRequest { config: ServiceConfig, - _t: PhantomData, + _t: PhantomData<(T, P)>, } -impl OneRequest +impl OneRequest where T: AsyncRead + AsyncWrite, { @@ -178,15 +178,15 @@ where } } -impl NewService for OneRequest +impl NewService for OneRequest where T: AsyncRead + AsyncWrite, { - type Request = T; + type Request = Io; type Response = (Request, Framed); type Error = ParseError; type InitError = (); - type Service = OneRequestService; + type Service = OneRequestService; type Future = FutureResult; fn new_service(&self, _: &SrvConfig) -> Self::Future { @@ -199,16 +199,16 @@ where /// `Service` implementation for HTTP1 transport. Reads one request and returns /// request and framed object. -pub struct OneRequestService { +pub struct OneRequestService { config: ServiceConfig, - _t: PhantomData, + _t: PhantomData<(T, P)>, } -impl Service for OneRequestService +impl Service for OneRequestService where T: AsyncRead + AsyncWrite, { - type Request = T; + type Request = Io; type Response = (Request, Framed); type Error = ParseError; type Future = OneRequestServiceResponse; @@ -217,9 +217,12 @@ where Ok(Async::Ready(())) } - fn call(&mut self, req: T) -> Self::Future { + fn call(&mut self, req: Self::Request) -> Self::Future { OneRequestServiceResponse { - framed: Some(Framed::new(req, Codec::new(self.config.clone()))), + framed: Some(Framed::new( + req.into_parts().0, + Codec::new(self.config.clone()), + )), } } } diff --git a/src/h2/service.rs b/src/h2/service.rs index ce7c3b5dd..6ab37919c 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use std::{io, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_server_config::ServerConfig as SrvConfig; +use actix_server_config::{Io, ServerConfig as SrvConfig}; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use bytes::Bytes; @@ -23,13 +23,13 @@ use crate::response::Response; use super::dispatcher::Dispatcher; /// `NewService` implementation for HTTP2 transport -pub struct H2Service { +pub struct H2Service { srv: S, cfg: ServiceConfig, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl H2Service +impl H2Service where S: NewService, S::Service: 'static, @@ -61,7 +61,7 @@ where } } -impl NewService for H2Service +impl NewService for H2Service where T: AsyncRead + AsyncWrite, S: NewService, @@ -70,12 +70,12 @@ where S::Response: Into>, B: MessageBody + 'static, { - type Request = T; + type Request = Io; type Response = (); type Error = DispatchError; type InitError = S::InitError; - type Service = H2ServiceHandler; - type Future = H2ServiceResponse; + type Service = H2ServiceHandler; + type Future = H2ServiceResponse; fn new_service(&self, cfg: &SrvConfig) -> Self::Future { H2ServiceResponse { @@ -87,13 +87,13 @@ where } #[doc(hidden)] -pub struct H2ServiceResponse, B> { +pub struct H2ServiceResponse, B> { fut: ::Future, cfg: Option, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl Future for H2ServiceResponse +impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite, S: NewService, @@ -102,7 +102,7 @@ where S::Error: Debug, B: MessageBody + 'static, { - type Item = H2ServiceHandler; + type Item = H2ServiceHandler; type Error = S::InitError; fn poll(&mut self) -> Poll { @@ -115,20 +115,20 @@ where } /// `Service` implementation for http/2 transport -pub struct H2ServiceHandler { +pub struct H2ServiceHandler { srv: CloneableService, cfg: ServiceConfig, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl H2ServiceHandler +impl H2ServiceHandler where S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { - fn new(cfg: ServiceConfig, srv: S) -> H2ServiceHandler { + fn new(cfg: ServiceConfig, srv: S) -> H2ServiceHandler { H2ServiceHandler { cfg, srv: CloneableService::new(srv), @@ -137,7 +137,7 @@ where } } -impl Service for H2ServiceHandler +impl Service for H2ServiceHandler where T: AsyncRead + AsyncWrite, S: Service + 'static, @@ -145,7 +145,7 @@ where S::Response: Into>, B: MessageBody + 'static, { - type Request = T; + type Request = Io; type Response = (); type Error = DispatchError; type Future = H2ServiceHandlerResponse; @@ -157,12 +157,12 @@ where }) } - fn call(&mut self, req: T) -> Self::Future { + fn call(&mut self, req: Self::Request) -> Self::Future { H2ServiceHandlerResponse { state: State::Handshake( Some(self.srv.clone()), Some(self.cfg.clone()), - server::handshake(req), + server::handshake(req.into_parts().0), ), } } diff --git a/src/service/service.rs b/src/service/service.rs index ac28c77a5..3ddf55739 100644 --- a/src/service/service.rs +++ b/src/service/service.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use std::{fmt, io}; use actix_codec::{AsyncRead, AsyncWrite, Framed, FramedParts}; -use actix_server_config::ServerConfig as SrvConfig; +use actix_server_config::{Io as ServerIo, Protocol, ServerConfig as SrvConfig}; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use bytes::{Buf, BufMut, Bytes, BytesMut}; @@ -20,13 +20,27 @@ use crate::response::Response; use crate::{h1, h2::Dispatcher}; /// `NewService` HTTP1.1/HTTP2 transport implementation -pub struct HttpService { +pub struct HttpService { srv: S, cfg: ServiceConfig, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl HttpService +impl HttpService +where + S: NewService, + S::Service: 'static, + S::Error: Debug + 'static, + S::Response: Into>, + B: MessageBody + 'static, +{ + /// Create builder for `HttpService` instance. + pub fn build() -> HttpServiceBuilder { + HttpServiceBuilder::new() + } +} + +impl HttpService where S: NewService, S::Service: 'static, @@ -56,14 +70,9 @@ where _t: PhantomData, } } - - /// Create builder for `HttpService` instance. - pub fn build() -> HttpServiceBuilder { - HttpServiceBuilder::new() - } } -impl NewService for HttpService +impl NewService for HttpService where T: AsyncRead + AsyncWrite + 'static, S: NewService, @@ -72,12 +81,12 @@ where S::Response: Into>, B: MessageBody + 'static, { - type Request = T; + type Request = ServerIo; type Response = (); type Error = DispatchError; type InitError = S::InitError; - type Service = HttpServiceHandler; - type Future = HttpServiceResponse; + type Service = HttpServiceHandler; + type Future = HttpServiceResponse; fn new_service(&self, cfg: &SrvConfig) -> Self::Future { HttpServiceResponse { @@ -89,13 +98,13 @@ where } #[doc(hidden)] -pub struct HttpServiceResponse, B> { +pub struct HttpServiceResponse, B> { fut: ::Future, cfg: Option, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl Future for HttpServiceResponse +impl Future for HttpServiceResponse where T: AsyncRead + AsyncWrite, S: NewService, @@ -104,7 +113,7 @@ where S::Error: Debug, B: MessageBody + 'static, { - type Item = HttpServiceHandler; + type Item = HttpServiceHandler; type Error = S::InitError; fn poll(&mut self) -> Poll { @@ -117,20 +126,20 @@ where } /// `Service` implementation for http transport -pub struct HttpServiceHandler { +pub struct HttpServiceHandler { srv: CloneableService, cfg: ServiceConfig, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl HttpServiceHandler +impl HttpServiceHandler where S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { - fn new(cfg: ServiceConfig, srv: S) -> HttpServiceHandler { + fn new(cfg: ServiceConfig, srv: S) -> HttpServiceHandler { HttpServiceHandler { cfg, srv: CloneableService::new(srv), @@ -139,7 +148,7 @@ where } } -impl Service for HttpServiceHandler +impl Service for HttpServiceHandler where T: AsyncRead + AsyncWrite + 'static, S: Service + 'static, @@ -147,7 +156,7 @@ where S::Response: Into>, B: MessageBody + 'static, { - type Request = T; + type Request = ServerIo; type Response = (); type Error = DispatchError; type Future = HttpServiceHandlerResponse; @@ -159,14 +168,37 @@ where }) } - fn call(&mut self, req: T) -> Self::Future { - HttpServiceHandlerResponse { - state: State::Unknown(Some(( - req, - BytesMut::with_capacity(14), - self.cfg.clone(), - self.srv.clone(), - ))), + fn call(&mut self, req: Self::Request) -> Self::Future { + let (io, params, proto) = req.into_parts(); + match proto { + Protocol::Http2 => { + let io = Io { + inner: io, + unread: None, + }; + HttpServiceHandlerResponse { + state: State::Handshake(Some(( + server::handshake(io), + self.cfg.clone(), + self.srv.clone(), + ))), + } + } + Protocol::Http10 | Protocol::Http11 => HttpServiceHandlerResponse { + state: State::H1(h1::Dispatcher::new( + io, + self.cfg.clone(), + self.srv.clone(), + )), + }, + _ => HttpServiceHandlerResponse { + state: State::Unknown(Some(( + io, + BytesMut::with_capacity(14), + self.cfg.clone(), + self.srv.clone(), + ))), + }, } } } diff --git a/tests/test_server.rs b/tests/test_server.rs index 7a28bca8a..3771d35c6 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -49,7 +49,7 @@ fn test_h1_2() { } #[cfg(feature = "ssl")] -fn ssl_acceptor() -> std::io::Result> { +fn ssl_acceptor() -> std::io::Result> { use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; // load ssl keys let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 4111ca3db..634b9acdd 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -2,7 +2,8 @@ use std::io; use actix_codec::Framed; use actix_http_test::TestServer; -use actix_service::NewService; +use actix_server::Io; +use actix_service::{fn_service, NewService}; use actix_utils::framed::IntoFramed; use actix_utils::stream::TakeItem; use bytes::{Bytes, BytesMut}; @@ -35,7 +36,8 @@ fn ws_service(req: ws::Frame) -> impl Future| Ok(io.into_parts().0)) + .and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default()))) .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(req, framed): (_, Framed<_, _>)| { // validate request From e15e4f18fd8588c64fbf4096e40553de36278af1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Mar 2019 16:42:33 -0700 Subject: [PATCH 185/208] update tests --- src/client/h2proto.rs | 12 +- src/h1/dispatcher.rs | 36 ++-- src/h2/dispatcher.rs | 10 +- src/h2/mod.rs | 5 +- test-server/src/lib.rs | 5 + tests/test_server.rs | 411 ++++++++++++++++++++++++++++++++++------- 6 files changed, 383 insertions(+), 96 deletions(-) diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index 617c21b60..c05aeddbe 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -6,10 +6,11 @@ use futures::future::{err, Either}; use futures::{Async, Future, Poll}; use h2::{client::SendRequest, SendStream}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; -use http::{request::Request, HttpTryFrom, Version}; +use http::{request::Request, HttpTryFrom, Method, Version}; use crate::body::{BodyLength, MessageBody}; use crate::message::{Message, RequestHead, ResponseHead}; +use crate::payload::Payload; use super::connection::{ConnectionType, IoConnection}; use super::error::SendRequestError; @@ -28,6 +29,7 @@ where B: MessageBody, { trace!("Sending client request: {:?} {:?}", head, body.length()); + let head_req = head.method == Method::HEAD; let length = body.length(); let eof = match length { BodyLength::None | BodyLength::Empty | BodyLength::Sized(0) => true, @@ -99,18 +101,16 @@ where } } }) - .and_then(|resp| { + .and_then(move |resp| { let (parts, body) = resp.into_parts(); + let payload = if head_req { Payload::None } else { body.into() }; let mut head: Message = Message::new(); head.version = parts.version; head.status = parts.status; head.headers = parts.headers; - Ok(ClientResponse { - head, - payload: body.into(), - }) + Ok(ClientResponse { head, payload }) }) .from_err() } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index a7eb96c3f..8543aa213 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -228,18 +228,21 @@ where } None => None, }, - State::ServiceCall(mut fut) => { - match fut.poll().map_err(|_| DispatchError::Service)? { - Async::Ready(res) => { - let (res, body) = res.into().replace_body(()); - Some(self.send_response(res, body)?) - } - Async::NotReady => { - self.state = State::ServiceCall(fut); - None - } + State::ServiceCall(mut fut) => match fut.poll() { + Ok(Async::Ready(res)) => { + let (res, body) = res.into().replace_body(()); + Some(self.send_response(res, body)?) } - } + Ok(Async::NotReady) => { + self.state = State::ServiceCall(fut); + None + } + Err(_e) => { + let res: Response = Response::InternalServerError().finish(); + let (res, body) = res.replace_body(()); + Some(self.send_response(res, body.into_body())?) + } + }, State::SendPayload(mut stream) => { loop { if !self.framed.is_write_buf_full() { @@ -289,12 +292,17 @@ where fn handle_request(&mut self, req: Request) -> Result, DispatchError> { let mut task = self.service.call(req); - match task.poll().map_err(|_| DispatchError::Service)? { - Async::Ready(res) => { + match task.poll() { + Ok(Async::Ready(res)) => { let (res, body) = res.into().replace_body(()); self.send_response(res, body) } - Async::NotReady => Ok(State::ServiceCall(task)), + Ok(Async::NotReady) => Ok(State::ServiceCall(task)), + Err(_e) => { + let res: Response = Response::InternalServerError().finish(); + let (res, body) = res.replace_body(()); + self.send_response(res, body.into_body()) + } } } diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index a3c731ebb..28465b509 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -107,9 +107,7 @@ where fn poll(&mut self) -> Poll { loop { match self.connection.poll()? { - Async::Ready(None) => { - self.flags.insert(Flags::DISCONNECTED); - } + Async::Ready(None) => return Ok(Async::Ready(())), Async::Ready(Some((req, res))) => { // update keep-alive expire if self.ka_timer.is_some() { @@ -255,7 +253,7 @@ where } } Ok(Async::NotReady) => Ok(Async::NotReady), - Err(e) => { + Err(_e) => { let res: Response = Response::InternalServerError().finish(); let (res, body) = res.replace_body(()); @@ -304,7 +302,9 @@ where } } else { match body.poll_next() { - Ok(Async::NotReady) => return Ok(Async::NotReady), + Ok(Async::NotReady) => { + return Ok(Async::NotReady); + } Ok(Async::Ready(None)) => { if let Err(e) = stream.send_data(Bytes::new(), true) { warn!("{:?}", e); diff --git a/src/h2/mod.rs b/src/h2/mod.rs index c5972123f..919317e05 100644 --- a/src/h2/mod.rs +++ b/src/h2/mod.rs @@ -40,7 +40,10 @@ impl Stream for Payload { } Ok(Async::Ready(None)) => Ok(Async::Ready(None)), Ok(Async::NotReady) => Ok(Async::NotReady), - Err(err) => Err(err.into()), + Err(err) => { + println!("======== {:?}", err); + Err(err.into()) + } } } } diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 6b70fbc10..d810d8915 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -167,6 +167,11 @@ impl TestServerRuntime { ClientRequest::get(self.url("/").as_str()) } + /// Create https `GET` request + pub fn sget(&self) -> ClientRequestBuilder { + ClientRequest::get(self.surl("/").as_str()) + } + /// Create `POST` request pub fn post(&self) -> ClientRequestBuilder { ClientRequest::post(self.url("/").as_str()) diff --git a/tests/test_server.rs b/tests/test_server.rs index 3771d35c6..49a943dbb 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -3,15 +3,16 @@ use std::time::Duration; use std::{net, thread}; use actix_http_test::TestServer; -use actix_service::NewService; +use actix_server_config::ServerConfig; +use actix_service::{fn_cfg_factory, NewService}; use bytes::Bytes; use futures::future::{self, ok, Future}; use futures::stream::once; use actix_http::body::Body; use actix_http::{ - body, client, http, Error, HttpMessage as HttpMessage2, HttpService, KeepAlive, - Request, Response, + body, client, error, http, http::header, Error, HttpMessage as HttpMessage2, + HttpService, KeepAlive, Request, Response, }; #[test] @@ -152,7 +153,7 @@ fn test_slow_request() { let srv = TestServer::new(|| { HttpService::build() .client_timeout(100) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -341,67 +342,66 @@ fn test_content_length() { } } -// TODO: fix -// #[test] -// fn test_h2_content_length() { -// use actix_http::http::{ -// header::{HeaderName, HeaderValue}, -// StatusCode, -// }; -// let openssl = ssl_acceptor().unwrap(); +#[test] +fn test_h2_content_length() { + use actix_http::http::{ + header::{HeaderName, HeaderValue}, + StatusCode, + }; + let openssl = ssl_acceptor().unwrap(); -// let mut srv = TestServer::new(move || { -// openssl -// .clone() -// .map_err(|e| println!("Openssl error: {}", e)) -// .and_then( -// HttpService::build() -// .h2(|req: Request| { -// let indx: usize = req.uri().path()[1..].parse().unwrap(); -// let statuses = [ -// StatusCode::NO_CONTENT, -// StatusCode::CONTINUE, -// StatusCode::SWITCHING_PROTOCOLS, -// StatusCode::PROCESSING, -// StatusCode::OK, -// StatusCode::NOT_FOUND, -// ]; -// future::ok::<_, ()>(Response::new(statuses[indx])) -// }) -// .map_err(|_| ()), -// ) -// }); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|req: Request| { + let indx: usize = req.uri().path()[1..].parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + future::ok::<_, ()>(Response::new(statuses[indx])) + }) + .map_err(|_| ()), + ) + }); -// let header = HeaderName::from_static("content-length"); -// let value = HeaderValue::from_static("0"); + let header = HeaderName::from_static("content-length"); + let value = HeaderValue::from_static("0"); -// { -// for i in 0..4 { -// let req = client::ClientRequest::get(srv.surl(&format!("/{}", i))) -// .finish() -// .unwrap(); -// let response = srv.send_request(req).unwrap(); -// assert_eq!(response.headers().get(&header), None); + { + for i in 0..4 { + let req = client::ClientRequest::get(srv.surl(&format!("/{}", i))) + .finish() + .unwrap(); + let response = srv.send_request(req).unwrap(); + assert_eq!(response.headers().get(&header), None); -// let req = client::ClientRequest::head(srv.surl(&format!("/{}", i))) -// .finish() -// .unwrap(); -// let response = srv.send_request(req).unwrap(); -// assert_eq!(response.headers().get(&header), None); -// } + let req = client::ClientRequest::head(srv.surl(&format!("/{}", i))) + .finish() + .unwrap(); + let response = srv.send_request(req).unwrap(); + assert_eq!(response.headers().get(&header), None); + } -// for i in 4..6 { -// let req = client::ClientRequest::get(srv.surl(&format!("/{}", i))) -// .finish() -// .unwrap(); -// let response = srv.send_request(req).unwrap(); -// assert_eq!(response.headers().get(&header), Some(&value)); -// } -// } -// } + for i in 4..6 { + let req = client::ClientRequest::get(srv.surl(&format!("/{}", i))) + .finish() + .unwrap(); + let response = srv.send_request(req).unwrap(); + assert_eq!(response.headers().get(&header), Some(&value)); + } + } +} #[test] -fn test_headers() { +fn test_h1_headers() { let data = STR.repeat(10); let data2 = data.clone(); @@ -511,7 +511,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; #[test] -fn test_body() { +fn test_h1_body() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().body(STR))) }); @@ -526,7 +526,30 @@ fn test_body() { } #[test] -fn test_head_empty() { +fn test_h2_body2() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let req = srv.sget().finish().unwrap(); + let mut response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h1_head_empty() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); @@ -549,7 +572,39 @@ fn test_head_empty() { } #[test] -fn test_head_binary() { +fn test_h2_head_empty() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let req = client::ClientRequest::head(srv.surl("/")).finish().unwrap(); + let mut response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), http::Version::HTTP_2); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_h1_head_binary() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| { ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) @@ -574,7 +629,42 @@ fn test_head_binary() { } #[test] -fn test_head_binary2() { +fn test_h2_head_binary() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + ok::<_, ()>( + Response::Ok().content_length(STR.len() as u64).body(STR), + ) + }) + .map_err(|_| ()), + ) + }); + + let req = client::ClientRequest::head(srv.surl("/")).finish().unwrap(); + let mut response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_h1_head_binary2() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); @@ -593,7 +683,34 @@ fn test_head_binary2() { } #[test] -fn test_body_length() { +fn test_h2_head_binary2() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let req = client::ClientRequest::head(srv.surl("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } +} + +#[test] +fn test_h1_body_length() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); @@ -614,17 +731,58 @@ fn test_body_length() { } #[test] -fn test_body_chunked_explicit() { +fn test_h2_body_length() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + let body = once(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>(Response::Ok().body(Body::from_message( + body::SizedStream::new(STR.len(), body), + ))) + }) + .map_err(|_| ()), + ) + }); + + let req = srv.sget().finish().unwrap(); + let mut response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h1_body_chunked_explicit() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::Ok().streaming(body)) + ok::<_, ()>( + Response::Ok() + .header(header::TRANSFER_ENCODING, "chunked") + .streaming(body), + ) }) }); let req = srv.get().finish().unwrap(); let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); + assert_eq!( + response + .headers() + .get(header::TRANSFER_ENCODING) + .unwrap() + .to_str() + .unwrap(), + "chunked" + ); // read response let bytes = srv.block_on(response.body()).unwrap(); @@ -634,7 +792,41 @@ fn test_body_chunked_explicit() { } #[test] -fn test_body_chunked_implicit() { +fn test_h2_body_chunked_explicit() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + let body = + once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .header(header::TRANSFER_ENCODING, "chunked") + .streaming(body), + ) + }) + .map_err(|_| ()), + ) + }); + + let req = srv.sget().finish().unwrap(); + let mut response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); + assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + + // decode + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h1_body_chunked_implicit() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); @@ -645,17 +837,23 @@ fn test_body_chunked_implicit() { let req = srv.get().finish().unwrap(); let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); + assert_eq!( + response + .headers() + .get(header::TRANSFER_ENCODING) + .unwrap() + .to_str() + .unwrap(), + "chunked" + ); // read response let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -use actix_server_config::ServerConfig; -use actix_service::fn_cfg_factory; - #[test] -fn test_response_http_error_handling() { +fn test_h1_response_http_error_handling() { let mut srv = TestServer::new(|| { HttpService::build().h1(fn_cfg_factory(|_: &ServerConfig| { Ok::<_, ()>(|_| { @@ -677,3 +875,76 @@ fn test_response_http_error_handling() { let bytes = srv.block_on(response.body()).unwrap(); assert!(bytes.is_empty()); } + +#[test] +fn test_h2_response_http_error_handling() { + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(fn_cfg_factory(|_: &ServerConfig| { + Ok::<_, ()>(|_| { + let broken_header = Bytes::from_static(b"\0\0\0"); + ok::<_, ()>( + Response::Ok() + .header(http::header::CONTENT_TYPE, broken_header) + .body(STR), + ) + }) + })) + .map_err(|_| ()), + ) + }); + + let req = srv.sget().finish().unwrap(); + let mut response = srv.send_request(req).unwrap(); + assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_h1_service_error() { + let mut srv = TestServer::new(|| { + HttpService::build() + .h1(|_| Err::(error::ErrorBadRequest("error"))) + }); + + let req = srv.get().finish().unwrap(); + let mut response = srv.send_request(req).unwrap(); + assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_h2_service_error() { + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| Err::(error::ErrorBadRequest("error"))) + .map_err(|_| ()), + ) + }); + + let req = srv.sget().finish().unwrap(); + let mut response = srv.send_request(req).unwrap(); + assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert!(bytes.is_empty()); +} From 409888fcd52a865549e71149bf8c28423cf1b0ea Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Mar 2019 16:47:12 -0700 Subject: [PATCH 186/208] remove debug print, remove unused flags --- src/h2/dispatcher.rs | 9 --------- src/h2/mod.rs | 5 +---- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index 28465b509..cbba34c0d 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -28,20 +28,12 @@ use crate::response::Response; const CHUNK_SIZE: usize = 16_384; -bitflags! { - struct Flags: u8 { - const DISCONNECTED = 0b0000_0001; - const SHUTDOWN = 0b0000_0010; - } -} - /// Dispatcher for HTTP/2 protocol pub struct Dispatcher< T: AsyncRead + AsyncWrite, S: Service + 'static, B: MessageBody, > { - flags: Flags, service: CloneableService, connection: Connection, config: ServiceConfig, @@ -86,7 +78,6 @@ where ka_expire, ka_timer, connection, - flags: Flags::empty(), _t: PhantomData, } } diff --git a/src/h2/mod.rs b/src/h2/mod.rs index 919317e05..c5972123f 100644 --- a/src/h2/mod.rs +++ b/src/h2/mod.rs @@ -40,10 +40,7 @@ impl Stream for Payload { } Ok(Async::Ready(None)) => Ok(Async::Ready(None)), Ok(Async::NotReady) => Ok(Async::NotReady), - Err(err) => { - println!("======== {:?}", err); - Err(err.into()) - } + Err(err) => Err(err.into()), } } } From 00d47acedc9cebdaa73f773a41991bd0a6b3b6af Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Mar 2019 17:56:48 -0700 Subject: [PATCH 187/208] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a5a255672..467e67a9d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Actix http [![Build Status](https://travis-ci.org/actix/actix-http.svg?branch=master)](https://travis-ci.org/actix/actix-http) [![Build status](https://ci.appveyor.com/api/projects/status/i51p65u2bukstgwg/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-http-b1qsn/branch/master) [![codecov](https://codecov.io/gh/actix/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/actix/actix-http.svg?branch=master)](https://travis-ci.org/actix/actix-http) [![codecov](https://codecov.io/gh/actix/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix http From 402a40ab27562bbd0927f25a28753bff5996ba86 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Mar 2019 16:55:16 -0700 Subject: [PATCH 188/208] update actix-server dep --- Cargo.toml | 7 ++++--- examples/framed_hello.rs | 3 ++- test-server/Cargo.toml | 6 +++--- tests/test_server.rs | 4 +++- tests/test_ws.rs | 3 ++- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ae81f1520..9c0f84c52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,11 +38,11 @@ ssl = ["openssl", "actix-connector/ssl"] fail = ["failure"] [dependencies] -actix-service = "0.3.3" +actix-service = "0.3.4" actix-codec = "0.1.1" actix-connector = "0.3.0" -actix-utils = "0.3.3" -actix-server-config = { git="https://github.com/actix/actix-net.git" } +actix-utils = "0.3.4" +actix-server-config = "0.1.0" base64 = "0.10" backtrace = "0.3" @@ -91,3 +91,4 @@ actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" openssl = { version="0.10" } +tokio-tcp = "0.1" \ No newline at end of file diff --git a/examples/framed_hello.rs b/examples/framed_hello.rs index 74b0f7dfd..7d4c13d34 100644 --- a/examples/framed_hello.rs +++ b/examples/framed_hello.rs @@ -7,6 +7,7 @@ use actix_service::{fn_service, NewService}; use actix_utils::framed::IntoFramed; use actix_utils::stream::TakeItem; use futures::Future; +use tokio_tcp::TcpStream; fn main() -> io::Result<()> { env::set_var("RUST_LOG", "framed_hello=info"); @@ -14,7 +15,7 @@ fn main() -> io::Result<()> { Server::build() .bind("framed_hello", "127.0.0.1:8080", || { - fn_service(|io: Io<_>| Ok(io.into_parts().0)) + fn_service(|io: Io| Ok(io.into_parts().0)) .and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default()))) .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(_req, _framed): (_, Framed<_, _>)| { diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 49f18f04e..2b4697736 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -33,12 +33,12 @@ ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] [dependencies] actix-codec = "0.1.1" -actix-rt = "0.2.0" +actix-rt = "0.2.1" actix-http = { path=".." } -actix-service = "0.3.3" +actix-service = "0.3.4" #actix-server = "0.3.0" actix-server = { git="https://github.com/actix/actix-net.git" } -actix-utils = "0.3.2" +actix-utils = "0.3.4" base64 = "0.10" bytes = "0.4" diff --git a/tests/test_server.rs b/tests/test_server.rs index 49a943dbb..8266bb9af 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -2,6 +2,7 @@ use std::io::{Read, Write}; use std::time::Duration; use std::{net, thread}; +use actix_codec::{AsyncRead, AsyncWrite}; use actix_http_test::TestServer; use actix_server_config::ServerConfig; use actix_service::{fn_cfg_factory, NewService}; @@ -50,7 +51,8 @@ fn test_h1_2() { } #[cfg(feature = "ssl")] -fn ssl_acceptor() -> std::io::Result> { +fn ssl_acceptor( +) -> std::io::Result> { use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; // load ssl keys let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 634b9acdd..d8942fb17 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -9,6 +9,7 @@ use actix_utils::stream::TakeItem; use bytes::{Bytes, BytesMut}; use futures::future::{ok, Either}; use futures::{Future, Sink, Stream}; +use tokio_tcp::TcpStream; use actix_http::{h1, ws, ResponseError, SendResponse, ServiceConfig}; @@ -36,7 +37,7 @@ fn ws_service(req: ws::Frame) -> impl Future| Ok(io.into_parts().0)) + fn_service(|io: Io| Ok(io.into_parts().0)) .and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default()))) .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(req, framed): (_, Framed<_, _>)| { From f627d0105552ee44cd75bd799143107f4ae57e54 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Mar 2019 17:04:08 -0700 Subject: [PATCH 189/208] update actix-server --- Cargo.toml | 3 +-- test-server/Cargo.toml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9c0f84c52..8e0f3a68a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,8 +83,7 @@ failure = { version = "0.1.5", optional = true } [dev-dependencies] actix-rt = "0.2.0" -#actix-server = { version = "0.3.0", features=["ssl"] } -actix-server = { git="https://github.com/actix/actix-net.git", features=["ssl"] } +actix-server = { version = "0.4.0", features=["ssl"] } actix-connector = { version = "0.3.0", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 2b4697736..b7535f99c 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -36,8 +36,7 @@ actix-codec = "0.1.1" actix-rt = "0.2.1" actix-http = { path=".." } actix-service = "0.3.4" -#actix-server = "0.3.0" -actix-server = { git="https://github.com/actix/actix-net.git" } +actix-server = "0.4.0" actix-utils = "0.3.4" base64 = "0.10" From 1941aa021798563b516d788bc60fe633b7f47944 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Mar 2019 14:41:40 -0700 Subject: [PATCH 190/208] use actix-connect crate --- Cargo.toml | 6 +- examples/client.rs | 2 +- src/client/connect.rs | 23 ++-- src/client/connector.rs | 255 ++++++++++++++++++--------------------- src/client/error.rs | 53 +++++--- src/client/h1proto.rs | 4 +- src/client/mod.rs | 2 +- src/client/pool.rs | 42 +++---- src/client/request.rs | 27 +++-- src/ws/client/error.rs | 4 +- src/ws/client/mod.rs | 2 +- src/ws/client/service.rs | 40 +++--- src/ws/mod.rs | 2 +- test-server/src/lib.rs | 108 +++++++---------- tests/test_client.rs | 6 +- tests/test_server.rs | 4 +- 16 files changed, 280 insertions(+), 300 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8e0f3a68a..ee9d28ac7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ path = "src/lib.rs" default = ["fail"] # openssl -ssl = ["openssl", "actix-connector/ssl"] +ssl = ["openssl", "actix-connect/ssl"] # failure integration. it is on by default, it will be off in future versions # actix itself does not use failure anymore @@ -40,7 +40,8 @@ fail = ["failure"] [dependencies] actix-service = "0.3.4" actix-codec = "0.1.1" -actix-connector = "0.3.0" +#actix-connector = "0.3.0" +actix-connect = { path="../actix-net/actix-connect" } actix-utils = "0.3.4" actix-server-config = "0.1.0" @@ -71,6 +72,7 @@ sha1 = "0.6" slab = "0.4" serde_urlencoded = "0.5.3" time = "0.1" +tokio-tcp = "0.1.3" tokio-timer = "0.2" tokio-current-thread = "0.1" trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false } diff --git a/examples/client.rs b/examples/client.rs index 06b708e20..7f5f8c91a 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -8,7 +8,7 @@ fn main() -> Result<(), Error> { env_logger::init(); System::new("test").block_on(lazy(|| { - let mut connector = client::Connector::default().service(); + let mut connector = client::Connector::new().service(); client::ClientRequest::get("https://www.rust-lang.org/") // <- Create request builder .header("User-Agent", "Actix-web") diff --git a/src/client/connect.rs b/src/client/connect.rs index f4112cfaa..43be57703 100644 --- a/src/client/connect.rs +++ b/src/client/connect.rs @@ -1,8 +1,7 @@ -use actix_connector::{RequestHost, RequestPort}; use http::uri::Uri; -use http::{Error as HttpError, HttpTryFrom}; +use http::HttpTryFrom; -use super::error::{ConnectorError, InvalidUrlKind}; +use super::error::InvalidUrl; use super::pool::Key; #[derive(Debug)] @@ -19,7 +18,7 @@ impl Connect { } /// Construct `Uri` instance and create `Connect` message. - pub fn try_from(uri: U) -> Result + pub fn try_from(uri: U) -> Result where Uri: HttpTryFrom, { @@ -40,30 +39,26 @@ impl Connect { self.uri.authority_part().unwrap().clone().into() } - pub(crate) fn validate(&self) -> Result<(), ConnectorError> { + pub(crate) fn validate(&self) -> Result<(), InvalidUrl> { if self.uri.host().is_none() { - Err(ConnectorError::InvalidUrl(InvalidUrlKind::MissingHost)) + Err(InvalidUrl::MissingHost) } else if self.uri.scheme_part().is_none() { - Err(ConnectorError::InvalidUrl(InvalidUrlKind::MissingScheme)) + Err(InvalidUrl::MissingScheme) } else if let Some(scheme) = self.uri.scheme_part() { match scheme.as_str() { "http" | "ws" | "https" | "wss" => Ok(()), - _ => Err(ConnectorError::InvalidUrl(InvalidUrlKind::UnknownScheme)), + _ => Err(InvalidUrl::UnknownScheme), } } else { Ok(()) } } -} -impl RequestHost for Connect { - fn host(&self) -> &str { + pub(crate) fn host(&self) -> &str { &self.uri.host().unwrap() } -} -impl RequestPort for Connect { - fn port(&self) -> u16 { + pub(crate) fn port(&self) -> u16 { if let Some(port) = self.uri.port() { port } else if let Some(scheme) = self.uri.scheme_part() { diff --git a/src/client/connector.rs b/src/client/connector.rs index ccb5dbce5..1579cd5ef 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1,14 +1,16 @@ +use std::fmt; +use std::marker::PhantomData; use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_connector::{Resolver, TcpConnector}; -use actix_service::{Service, ServiceExt}; +use actix_connect::{default_connector, Stream}; +use actix_service::{apply_fn, Service, ServiceExt}; use actix_utils::timeout::{TimeoutError, TimeoutService}; -use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; +use tokio_tcp::TcpStream; use super::connect::Connect; use super::connection::Connection; -use super::error::ConnectorError; +use super::error::ConnectError; use super::pool::{ConnectionPool, Protocol}; #[cfg(feature = "ssl")] @@ -19,20 +21,28 @@ type SslConnector = (); /// Http client connector builde instance. /// `Connector` type uses builder-like pattern for connector service construction. -pub struct Connector { - resolver: Resolver, +pub struct Connector { + connector: T, timeout: Duration, conn_lifetime: Duration, conn_keep_alive: Duration, disconnect_timeout: Duration, limit: usize, #[allow(dead_code)] - connector: SslConnector, + ssl: SslConnector, + _t: PhantomData, } -impl Default for Connector { - fn default() -> Connector { - let connector = { +impl Connector<(), ()> { + pub fn new() -> Connector< + impl Service< + Request = actix_connect::Connect, + Response = Stream, + Error = actix_connect::ConnectError, + > + Clone, + TcpStream, + > { + let ssl = { #[cfg(feature = "ssl")] { use log::error; @@ -49,30 +59,51 @@ impl Default for Connector { }; Connector { - connector, - resolver: Resolver::default(), + ssl, + connector: default_connector(), timeout: Duration::from_secs(1), conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), disconnect_timeout: Duration::from_millis(3000), limit: 100, + _t: PhantomData, } } } -impl Connector { - /// Use custom resolver. - pub fn resolver(mut self, resolver: Resolver) -> Self { - self.resolver = resolver;; - self - } - - /// Use custom resolver configuration. - pub fn resolver_config(mut self, cfg: ResolverConfig, opts: ResolverOpts) -> Self { - self.resolver = Resolver::new(cfg, opts); - self +impl Connector { + /// Use custom connector. + pub fn connector(self, connector: T1) -> Connector + where + U1: AsyncRead + AsyncWrite + fmt::Debug, + T1: Service< + Request = actix_connect::Connect, + Response = Stream, + Error = actix_connect::ConnectError, + > + Clone, + { + Connector { + connector, + timeout: self.timeout, + conn_lifetime: self.conn_lifetime, + conn_keep_alive: self.conn_keep_alive, + disconnect_timeout: self.disconnect_timeout, + limit: self.limit, + ssl: self.ssl, + _t: PhantomData, + } } +} +impl Connector +where + U: AsyncRead + AsyncWrite + fmt::Debug + 'static, + T: Service< + Request = actix_connect::Connect, + Response = Stream, + Error = actix_connect::ConnectError, + > + Clone, +{ /// Connection timeout, i.e. max time to connect to remote host including dns name resolution. /// Set to 1 second by default. pub fn timeout(mut self, timeout: Duration) -> Self { @@ -83,7 +114,7 @@ impl Connector { #[cfg(feature = "ssl")] /// Use custom `SslConnector` instance. pub fn ssl(mut self, connector: SslConnector) -> Self { - self.connector = connector; + self.ssl = connector; self } @@ -133,24 +164,18 @@ impl Connector { /// Finish configuration process and create connector service. pub fn service( self, - ) -> impl Service< - Request = Connect, - Response = impl Connection, - Error = ConnectorError, - > + Clone { + ) -> impl Service + + Clone { #[cfg(not(feature = "ssl"))] { let connector = TimeoutService::new( self.timeout, - self.resolver.map_err(ConnectorError::from).and_then( - TcpConnector::default() - .from_err() - .map(|(msg, io)| (msg, io, Protocol::Http1)), - ), + self.connector + .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) .map_err(|e| match e { TimeoutError::Service(e) => e, - TimeoutError::Timeout => ConnectorError::Timeout, + TimeoutError::Timeout => ConnectError::Timeout, }); connect_impl::InnerConnector { @@ -166,48 +191,49 @@ impl Connector { #[cfg(feature = "ssl")] { const H2: &[u8] = b"h2"; - use actix_connector::ssl::OpensslConnector; + use actix_connect::ssl::OpensslConnector; let ssl_service = TimeoutService::new( self.timeout, - self.resolver - .clone() - .map_err(ConnectorError::from) - .and_then(TcpConnector::default().from_err()) - .and_then( - OpensslConnector::service(self.connector) - .map_err(ConnectorError::from) - .map(|(msg, io)| { - let h2 = io - .get_ref() - .ssl() - .selected_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); - if h2 { - (msg, io, Protocol::Http2) - } else { - (msg, io, Protocol::Http1) - } - }), - ), - ) - .map_err(|e| match e { - TimeoutError::Service(e) => e, - TimeoutError::Timeout => ConnectorError::Timeout, - }); - - let tcp_service = TimeoutService::new( - self.timeout, - self.resolver.map_err(ConnectorError::from).and_then( - TcpConnector::default() - .from_err() - .map(|(msg, io)| (msg, io, Protocol::Http1)), + apply_fn(self.connector.clone(), |msg: Connect, srv| { + srv.call(actix_connect::Connect::new(msg.host(), msg.port())) + }) + .map_err(ConnectError::from) + .and_then( + OpensslConnector::service(self.ssl) + .map_err(ConnectError::from) + .map(|stream| { + let sock = stream.into_parts().0; + let h2 = sock + .get_ref() + .ssl() + .selected_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (sock, Protocol::Http2) + } else { + (sock, Protocol::Http1) + } + }), ), ) .map_err(|e| match e { TimeoutError::Service(e) => e, - TimeoutError::Timeout => ConnectorError::Timeout, + TimeoutError::Timeout => ConnectError::Timeout, + }); + + let tcp_service = TimeoutService::new( + self.timeout, + apply_fn(self.connector.clone(), |msg: Connect, srv| { + srv.call(actix_connect::Connect::new(msg.host(), msg.port())) + }) + .map_err(ConnectError::from) + .map(|stream| (stream.into_parts().0, Protocol::Http1)), + ) + .map_err(|e| match e { + TimeoutError::Service(e) => e, + TimeoutError::Timeout => ConnectError::Timeout, }); connect_impl::InnerConnector { @@ -253,11 +279,8 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - > + Clone, + T: Service + + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -269,11 +292,7 @@ mod connect_impl { impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - >, + T: Service, { type Request = Connect; type Response = IoConnection; @@ -289,7 +308,7 @@ mod connect_impl { fn call(&mut self, req: Connect) -> Self::Future { if req.is_secure() { - Either::B(err(ConnectorError::SslIsNotSupported)) + Either::B(err(ConnectError::SslIsNotSupported)) } else if let Err(e) = req.validate() { Either::B(err(e)) } else { @@ -303,7 +322,7 @@ mod connect_impl { mod connect_impl { use std::marker::PhantomData; - use futures::future::{err, Either, FutureResult}; + use futures::future::{Either, FutureResult}; use futures::{Async, Future, Poll}; use super::*; @@ -313,16 +332,8 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service< - Request = Connect, - Response = (Connect, Io1, Protocol), - Error = ConnectorError, - >, - T2: Service< - Request = Connect, - Response = (Connect, Io2, Protocol), - Error = ConnectorError, - >, + T1: Service, + T2: Service, { pub(crate) tcp_pool: ConnectionPool, pub(crate) ssl_pool: ConnectionPool, @@ -332,16 +343,10 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service< - Request = Connect, - Response = (Connect, Io1, Protocol), - Error = ConnectorError, - > + Clone, - T2: Service< - Request = Connect, - Response = (Connect, Io2, Protocol), - Error = ConnectorError, - > + Clone, + T1: Service + + Clone, + T2: Service + + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -355,20 +360,12 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service< - Request = Connect, - Response = (Connect, Io1, Protocol), - Error = ConnectorError, - >, - T2: Service< - Request = Connect, - Response = (Connect, Io2, Protocol), - Error = ConnectorError, - >, + T1: Service, + T2: Service, { type Request = Connect; type Response = EitherConnection; - type Error = ConnectorError; + type Error = ConnectError; type Future = Either< FutureResult, Either< @@ -382,9 +379,7 @@ mod connect_impl { } fn call(&mut self, req: Connect) -> Self::Future { - if let Err(e) = req.validate() { - Either::A(err(e)) - } else if req.is_secure() { + if req.is_secure() { Either::B(Either::B(InnerConnectorResponseB { fut: self.ssl_pool.call(req), _t: PhantomData, @@ -401,11 +396,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io1, Protocol), - Error = ConnectorError, - >, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -413,16 +404,12 @@ mod connect_impl { impl Future for InnerConnectorResponseA where - T: Service< - Request = Connect, - Response = (Connect, Io1, Protocol), - Error = ConnectorError, - >, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { type Item = EitherConnection; - type Error = ConnectorError; + type Error = ConnectError; fn poll(&mut self) -> Poll { match self.fut.poll()? { @@ -435,11 +422,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io2, Protocol), - Error = ConnectorError, - >, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -447,16 +430,12 @@ mod connect_impl { impl Future for InnerConnectorResponseB where - T: Service< - Request = Connect, - Response = (Connect, Io2, Protocol), - Error = ConnectorError, - >, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { type Item = EitherConnection; - type Error = ConnectorError; + type Error = ConnectError; fn poll(&mut self) -> Poll { match self.fut.poll()? { diff --git a/src/client/error.rs b/src/client/error.rs index 6c91ff976..4fce904f1 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -11,11 +11,7 @@ use crate::response::Response; /// A set of errors that can occur while connecting to an HTTP host #[derive(Debug, Display, From)] -pub enum ConnectorError { - /// Invalid URL - #[display(fmt = "Invalid URL")] - InvalidUrl(InvalidUrlKind), - +pub enum ConnectError { /// SSL feature is not enabled #[display(fmt = "SSL is not supported")] SslIsNotSupported, @@ -45,24 +41,30 @@ pub enum ConnectorError { #[display(fmt = "Internal error: connector has been disconnected")] Disconnected, + /// Unresolved host name + #[display(fmt = "Connector received `Connect` method with unresolved host")] + Unresolverd, + /// Connection io error #[display(fmt = "{}", _0)] Io(io::Error), } -#[derive(Debug, Display)] -pub enum InvalidUrlKind { - #[display(fmt = "Missing url scheme")] - MissingScheme, - #[display(fmt = "Unknown url scheme")] - UnknownScheme, - #[display(fmt = "Missing host name")] - MissingHost, +impl From for ConnectError { + fn from(err: actix_connect::ConnectError) -> ConnectError { + match err { + actix_connect::ConnectError::Resolver(e) => ConnectError::Resolver(e), + actix_connect::ConnectError::NoRecords => ConnectError::NoRecords, + actix_connect::ConnectError::InvalidInput => panic!(), + actix_connect::ConnectError::Unresolverd => ConnectError::Unresolverd, + actix_connect::ConnectError::Io(e) => ConnectError::Io(e), + } + } } #[cfg(feature = "ssl")] -impl From> for ConnectorError { - fn from(err: HandshakeError) -> ConnectorError { +impl From> for ConnectError { + fn from(err: HandshakeError) -> ConnectError { match err { HandshakeError::SetupFailure(stack) => SslError::from(stack).into(), HandshakeError::Failure(stream) => stream.into_error().into(), @@ -71,12 +73,27 @@ impl From> for ConnectorError { } } +#[derive(Debug, Display, From)] +pub enum InvalidUrl { + #[display(fmt = "Missing url scheme")] + MissingScheme, + #[display(fmt = "Unknown url scheme")] + UnknownScheme, + #[display(fmt = "Missing host name")] + MissingHost, + #[display(fmt = "Url parse error: {}", _0)] + HttpError(http::Error), +} + /// A set of errors that can occur during request sending and response reading #[derive(Debug, Display, From)] pub enum SendRequestError { + /// Invalid URL + #[display(fmt = "Invalid URL: {}", _0)] + Url(InvalidUrl), /// Failed to connect to host #[display(fmt = "Failed to connect to host: {}", _0)] - Connector(ConnectorError), + Connect(ConnectError), /// Error sending request Send(io::Error), /// Error parsing response @@ -92,10 +109,10 @@ pub enum SendRequestError { impl ResponseError for SendRequestError { fn error_response(&self) -> Response { match *self { - SendRequestError::Connector(ConnectorError::Timeout) => { + SendRequestError::Connect(ConnectError::Timeout) => { Response::GatewayTimeout() } - SendRequestError::Connector(_) => Response::BadGateway(), + SendRequestError::Connect(_) => Response::BadGateway(), _ => Response::InternalServerError(), } .into() diff --git a/src/client/h1proto.rs b/src/client/h1proto.rs index 3329fcfec..34521cc2f 100644 --- a/src/client/h1proto.rs +++ b/src/client/h1proto.rs @@ -6,7 +6,7 @@ use futures::future::{err, ok, Either}; use futures::{Async, Future, Poll, Sink, Stream}; use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; -use super::error::{ConnectorError, SendRequestError}; +use super::error::{ConnectError, SendRequestError}; use super::pool::Acquired; use super::response::ClientResponse; use crate::body::{BodyLength, MessageBody}; @@ -62,7 +62,7 @@ where } ok(res) } else { - err(ConnectorError::Disconnected.into()) + err(ConnectError::Disconnected.into()) } }) }) diff --git a/src/client/mod.rs b/src/client/mod.rs index 8d041827f..0bff97e49 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -12,6 +12,6 @@ mod response; pub use self::connect::Connect; pub use self::connection::Connection; pub use self::connector::Connector; -pub use self::error::{ConnectorError, InvalidUrlKind, SendRequestError}; +pub use self::error::{ConnectError, InvalidUrl, SendRequestError}; pub use self::request::{ClientRequest, ClientRequestBuilder}; pub use self::response::ClientResponse; diff --git a/src/client/pool.rs b/src/client/pool.rs index 188980cb3..214b7a382 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -20,7 +20,7 @@ use tokio_timer::{sleep, Delay}; use super::connect::Connect; use super::connection::{ConnectionType, IoConnection}; -use super::error::ConnectorError; +use super::error::ConnectError; #[derive(Clone, Copy, PartialEq)] pub enum Protocol { @@ -48,11 +48,7 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - >, + T: Service, { pub(crate) fn new( connector: T, @@ -91,17 +87,13 @@ where impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - >, + T: Service, { type Request = Connect; type Response = IoConnection; - type Error = ConnectorError; + type Error = ConnectError; type Future = Either< - FutureResult, ConnectorError>, + FutureResult, Either, OpenConnection>, >; @@ -151,7 +143,7 @@ where { key: Key, token: usize, - rx: oneshot::Receiver, ConnectorError>>, + rx: oneshot::Receiver, ConnectError>>, inner: Option>>>, } @@ -173,7 +165,7 @@ where Io: AsyncRead + AsyncWrite, { type Item = IoConnection; - type Error = ConnectorError; + type Error = ConnectError; fn poll(&mut self) -> Poll { match self.rx.poll() { @@ -187,7 +179,7 @@ where Ok(Async::NotReady) => Ok(Async::NotReady), Err(_) => { let _ = self.inner.take(); - Err(ConnectorError::Disconnected) + Err(ConnectError::Disconnected) } } } @@ -206,7 +198,7 @@ where impl OpenConnection where - F: Future, + F: Future, Io: AsyncRead + AsyncWrite + 'static, { fn new(key: Key, inner: Rc>>, fut: F) -> Self { @@ -234,11 +226,11 @@ where impl Future for OpenConnection where - F: Future, + F: Future, Io: AsyncRead + AsyncWrite, { type Item = IoConnection; - type Error = ConnectorError; + type Error = ConnectError; fn poll(&mut self) -> Poll { if let Some(ref mut h2) = self.h2 { @@ -258,7 +250,7 @@ where match self.fut.poll() { Err(err) => Err(err), - Ok(Async::Ready((_, io, proto))) => { + Ok(Async::Ready((io, proto))) => { let _ = self.inner.take(); if proto == Protocol::Http1 { Ok(Async::Ready(IoConnection::new( @@ -289,7 +281,7 @@ where // impl OpenWaitingConnection // where -// F: Future + 'static, +// F: Future + 'static, // Io: AsyncRead + AsyncWrite + 'static, // { // fn spawn( @@ -323,7 +315,7 @@ where // impl Future for OpenWaitingConnection // where -// F: Future, +// F: Future, // Io: AsyncRead + AsyncWrite, // { // type Item = (); @@ -402,7 +394,7 @@ pub(crate) struct Inner { available: HashMap>>, waiters: Slab<( Connect, - oneshot::Sender, ConnectorError>>, + oneshot::Sender, ConnectError>>, )>, waiters_queue: IndexSet<(Key, usize)>, task: AtomicTask, @@ -444,7 +436,7 @@ where &mut self, connect: Connect, ) -> ( - oneshot::Receiver, ConnectorError>>, + oneshot::Receiver, ConnectError>>, usize, ) { let (tx, rx) = oneshot::channel(); @@ -534,7 +526,7 @@ where // impl Future for ConnectorPoolSupport // where // Io: AsyncRead + AsyncWrite + 'static, -// T: Service, +// T: Service, // T::Future: 'static, // { // type Item = (); diff --git a/src/client/request.rs b/src/client/request.rs index efae5b94e..199e13b93 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -5,6 +5,7 @@ use std::io::Write; use actix_service::Service; use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; +use futures::future::{err, Either}; use futures::{Future, Stream}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use serde::Serialize; @@ -21,7 +22,7 @@ use crate::message::{ConnectionType, Head, RequestHead}; use super::connection::Connection; use super::response::ClientResponse; -use super::{Connect, ConnectorError, SendRequestError}; +use super::{Connect, ConnectError, SendRequestError}; /// An HTTP Client Request /// @@ -32,7 +33,8 @@ use super::{Connect, ConnectorError, SendRequestError}; /// /// fn main() { /// System::new("test").block_on(lazy(|| { -/// let mut connector = client::Connector::default().service(); +/// let mut connector = client::Connector::new().service(); +/// /// client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder /// .header("User-Agent", "Actix-web") /// .finish().unwrap() @@ -178,17 +180,24 @@ where ) -> impl Future where B: 'static, - T: Service, + T: Service, I: Connection, { let Self { head, body } = self; - connector - // connect to the host - .call(Connect::new(head.uri.clone())) - .from_err() - // send request - .and_then(move |connection| connection.send_request(head, body)) + let connect = Connect::new(head.uri.clone()); + if let Err(e) = connect.validate() { + Either::A(err(e.into())) + } else { + Either::B( + connector + // connect to the host + .call(connect) + .from_err() + // send request + .and_then(move |connection| connection.send_request(head, body)), + ) + } } } diff --git a/src/ws/client/error.rs b/src/ws/client/error.rs index 1eccb0b95..ae1e39967 100644 --- a/src/ws/client/error.rs +++ b/src/ws/client/error.rs @@ -1,7 +1,7 @@ //! Http client request use std::io; -use actix_connector::ConnectorError; +use actix_connect::ConnectError; use derive_more::{Display, From}; use http::{header::HeaderValue, Error as HttpError, StatusCode}; @@ -43,7 +43,7 @@ pub enum ClientError { Protocol(ProtocolError), /// Connect error #[display(fmt = "Connector error: {:?}", _0)] - Connect(ConnectorError), + Connect(ConnectError), /// IO Error #[display(fmt = "{}", _0)] Io(io::Error), diff --git a/src/ws/client/mod.rs b/src/ws/client/mod.rs index 12c7229b9..0dbf081c6 100644 --- a/src/ws/client/mod.rs +++ b/src/ws/client/mod.rs @@ -4,7 +4,7 @@ mod service; pub use self::connect::Connect; pub use self::error::ClientError; -pub use self::service::{Client, DefaultClient}; +pub use self::service::Client; #[derive(PartialEq, Hash, Debug, Clone, Copy)] pub(crate) enum Protocol { diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 586873d19..e3781e15f 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -2,8 +2,8 @@ use std::marker::PhantomData; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_connector::{Connect as TcpConnect, ConnectorError, DefaultConnector}; -use actix_service::Service; +use actix_connect::{default_connector, Connect as TcpConnect, ConnectError}; +use actix_service::{apply_fn, Service}; use base64; use futures::future::{err, Either, FutureResult}; use futures::{try_ready, Async, Future, Poll, Sink, Stream}; @@ -20,21 +20,29 @@ use crate::ws::Codec; use super::{ClientError, Connect, Protocol}; -/// Default client, uses default connector. -pub type DefaultClient = Client; - /// WebSocket's client -pub struct Client -where - T: Service, - T::Response: AsyncRead + AsyncWrite, -{ +pub struct Client { connector: T, } +impl Client<()> { + /// Create client with default connector. + pub fn default() -> Client< + impl Service< + Request = TcpConnect, + Response = impl AsyncRead + AsyncWrite, + Error = ConnectError, + > + Clone, + > { + Client::new(apply_fn(default_connector(), |msg: TcpConnect, srv| { + srv.call(msg).map(|stream| stream.into_parts().0) + })) + } +} + impl Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { /// Create new websocket's client factory @@ -43,15 +51,9 @@ where } } -impl Default for Client { - fn default() -> Self { - Client::new(DefaultConnector::default()) - } -} - impl Clone for Client where - T: Service + Clone, + T: Service + Clone, T::Response: AsyncRead + AsyncWrite, { fn clone(&self) -> Self { @@ -63,7 +65,7 @@ where impl Service for Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite + 'static, T::Future: 'static, { diff --git a/src/ws/mod.rs b/src/ws/mod.rs index a8de59dd4..8f9bc83ba 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -21,7 +21,7 @@ mod proto; mod service; mod transport; -pub use self::client::{Client, ClientError, Connect, DefaultClient}; +pub use self::client::{Client, ClientError, Connect}; pub use self::codec::{Codec, Frame, Message}; pub use self::frame::Parser; pub use self::proto::{CloseCode, CloseReason, OpCode}; diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index d810d8915..3bb5feffb 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -1,18 +1,17 @@ //! Various helpers for Actix applications to use during testing. use std::sync::mpsc; -use std::{net, thread}; +use std::{net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::body::MessageBody; use actix_http::client::{ - ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, - ConnectorError, SendRequestError, + ClientRequest, ClientRequestBuilder, ClientResponse, Connect, ConnectError, + Connection, Connector, SendRequestError, }; use actix_http::ws; use actix_rt::{Runtime, System}; use actix_server::{Server, StreamServiceFactory}; use actix_service::Service; - use futures::future::{lazy, Future}; use http::Method; use net2::TcpBuilder; @@ -44,21 +43,15 @@ use net2::TcpBuilder; /// ``` pub struct TestServer; -/// -pub struct TestServerRuntime { +/// Test server controller +pub struct TestServerRuntime { addr: net::SocketAddr, - conn: T, rt: Runtime, } impl TestServer { /// Start new test server with application factory - pub fn new( - factory: F, - ) -> TestServerRuntime< - impl Service - + Clone, - > { + pub fn new(factory: F) -> TestServerRuntime { let (tx, rx) = mpsc::channel(); // run server in separate thread @@ -79,35 +72,9 @@ impl TestServer { let (system, addr) = rx.recv().unwrap(); System::set_current(system); - - let mut rt = Runtime::new().unwrap(); - let conn = rt - .block_on(lazy(|| Ok::<_, ()>(TestServer::new_connector()))) - .unwrap(); - - TestServerRuntime { addr, conn, rt } - } - - fn new_connector( - ) -> impl Service< - Request = Connect, - Response = impl Connection, - Error = ConnectorError, - > + Clone { - #[cfg(feature = "ssl")] - { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder - .set_alpn_protos(b"\x02h2\x08http/1.1") - .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); - Connector::default().ssl(builder.build()).service() - } - #[cfg(not(feature = "ssl"))] - { - Connector::default().service() + TestServerRuntime { + addr, + rt: Runtime::new().unwrap(), } } @@ -122,7 +89,7 @@ impl TestServer { } } -impl TestServerRuntime { +impl TestServerRuntime { /// Execute future on current core pub fn block_on(&mut self, fut: F) -> Result where @@ -131,12 +98,12 @@ impl TestServerRuntime { self.rt.block_on(fut) } - /// Execute future on current core - pub fn execute(&mut self, fut: F) -> Result + /// Execute function on current core + pub fn execute(&mut self, fut: F) -> R where - F: Future, + F: FnOnce() -> R, { - self.rt.block_on(fut) + self.rt.block_on(lazy(|| Ok::<_, ()>(fut()))).unwrap() } /// Construct test server url @@ -190,17 +157,37 @@ impl TestServerRuntime { .take() } - /// Http connector - pub fn connector(&mut self) -> &mut T { - &mut self.conn + fn new_connector( + ) -> impl Service + + Clone { + #[cfg(feature = "ssl")] + { + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); + Connector::new() + .timeout(time::Duration::from_millis(500)) + .ssl(builder.build()) + .service() + } + #[cfg(not(feature = "ssl"))] + { + Connector::new() + .timeout(time::Duration::from_millis(500)) + .service() + } } /// Http connector - pub fn new_connector(&mut self) -> T - where - T: Clone, - { - self.conn.clone() + pub fn connector( + &mut self, + ) -> impl Service + + Clone { + self.execute(|| TestServerRuntime::new_connector()) } /// Stop http server @@ -209,11 +196,7 @@ impl TestServerRuntime { } } -impl TestServerRuntime -where - T: Service + Clone, - T::Response: Connection, -{ +impl TestServerRuntime { /// Connect to websocket server at a given path pub fn ws_at( &mut self, @@ -236,11 +219,12 @@ where &mut self, req: ClientRequest, ) -> Result { - self.rt.block_on(req.send(&mut self.conn)) + let mut conn = self.connector(); + self.rt.block_on(req.send(&mut conn)) } } -impl Drop for TestServerRuntime { +impl Drop for TestServerRuntime { fn drop(&mut self) { self.stop() } diff --git a/tests/test_client.rs b/tests/test_client.rs index 782e487ca..90e1a4f4a 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -36,7 +36,7 @@ fn test_h1_v2() { .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); - let mut connector = srv.new_connector(); + let mut connector = srv.connector(); let request = srv.get().finish().unwrap(); let response = srv.block_on(request.send(&mut connector)).unwrap(); @@ -70,7 +70,7 @@ fn test_connection_close() { .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); - let mut connector = srv.new_connector(); + let mut connector = srv.connector(); let request = srv.get().close().finish().unwrap(); let response = srv.block_on(request.send(&mut connector)).unwrap(); @@ -90,7 +90,7 @@ fn test_with_query_parameter() { }) .map(|_| ()) }); - let mut connector = srv.new_connector(); + let mut connector = srv.connector(); let request = client::ClientRequest::get(srv.url("/?qp=5")) .finish() diff --git a/tests/test_server.rs b/tests/test_server.rs index 8266bb9af..98f740941 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -432,7 +432,7 @@ fn test_h1_headers() { future::ok::<_, ()>(builder.body(data.clone())) }) }); - let mut connector = srv.new_connector(); + let mut connector = srv.connector(); let req = srv.get().finish().unwrap(); @@ -479,7 +479,7 @@ fn test_h2_headers() { future::ok::<_, ()>(builder.body(data.clone())) }).map_err(|_| ())) }); - let mut connector = srv.new_connector(); + let mut connector = srv.connector(); let req = client::ClientRequest::get(srv.surl("/")).finish().unwrap(); let mut response = srv.block_on(req.send(&mut connector)).unwrap(); From 033a8d890cc276ee4ebdbbbd614aa2c20d085c49 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Mar 2019 15:57:33 -0700 Subject: [PATCH 191/208] update actix connect --- Cargo.toml | 3 +-- src/client/connect.rs | 7 +++++-- src/client/connector.rs | 32 ++++++++++++++++---------------- src/ws/client/service.rs | 16 ++++++++-------- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ee9d28ac7..11f532c89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,8 +40,7 @@ fail = ["failure"] [dependencies] actix-service = "0.3.4" actix-codec = "0.1.1" -#actix-connector = "0.3.0" -actix-connect = { path="../actix-net/actix-connect" } +actix-connect = { git = "https://github.com/actix/actix-net.git" } actix-utils = "0.3.4" actix-server-config = "0.1.0" diff --git a/src/client/connect.rs b/src/client/connect.rs index 43be57703..93626b0a7 100644 --- a/src/client/connect.rs +++ b/src/client/connect.rs @@ -1,3 +1,4 @@ +use actix_connect::Address; use http::uri::Uri; use http::HttpTryFrom; @@ -53,12 +54,14 @@ impl Connect { Ok(()) } } +} - pub(crate) fn host(&self) -> &str { +impl Address for Connect { + fn host(&self) -> &str { &self.uri.host().unwrap() } - pub(crate) fn port(&self) -> u16 { + fn port(&self) -> u16 { if let Some(port) = self.uri.port() { port } else if let Some(scheme) = self.uri.scheme_part() { diff --git a/src/client/connector.rs b/src/client/connector.rs index 1579cd5ef..aaa88abc0 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -3,7 +3,9 @@ use std::marker::PhantomData; use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_connect::{default_connector, Stream}; +use actix_connect::{ + default_connector, Connect as TcpConnect, Connection as TcpConnection, +}; use actix_service::{apply_fn, Service, ServiceExt}; use actix_utils::timeout::{TimeoutError, TimeoutService}; use tokio_tcp::TcpStream; @@ -36,8 +38,8 @@ pub struct Connector { impl Connector<(), ()> { pub fn new() -> Connector< impl Service< - Request = actix_connect::Connect, - Response = Stream, + Request = TcpConnect, + Response = TcpConnection, Error = actix_connect::ConnectError, > + Clone, TcpStream, @@ -77,8 +79,8 @@ impl Connector { where U1: AsyncRead + AsyncWrite + fmt::Debug, T1: Service< - Request = actix_connect::Connect, - Response = Stream, + Request = TcpConnect, + Response = TcpConnection, Error = actix_connect::ConnectError, > + Clone, { @@ -99,8 +101,8 @@ impl Connector where U: AsyncRead + AsyncWrite + fmt::Debug + 'static, T: Service< - Request = actix_connect::Connect, - Response = Stream, + Request = TcpConnect, + Response = TcpConnection, Error = actix_connect::ConnectError, > + Clone, { @@ -170,8 +172,10 @@ where { let connector = TimeoutService::new( self.timeout, - self.connector - .map(|stream| (stream.into_parts().0, Protocol::Http1)), + apply_fn(self.connector, |msg: Connect, srv| { + srv.call(actix_connect::Connect::with_request(msg)) + }) + .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -196,7 +200,7 @@ where let ssl_service = TimeoutService::new( self.timeout, apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(actix_connect::Connect::new(msg.host(), msg.port())) + srv.call(actix_connect::Connect::with_request(msg)) }) .map_err(ConnectError::from) .and_then( @@ -226,7 +230,7 @@ where let tcp_service = TimeoutService::new( self.timeout, apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(actix_connect::Connect::new(msg.host(), msg.port())) + srv.call(actix_connect::Connect::with_request(msg)) }) .map_err(ConnectError::from) .map(|stream| (stream.into_parts().0, Protocol::Http1)), @@ -267,11 +271,7 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - >, + T: Service, { pub(crate) tcp_pool: ConnectionPool, } diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index e3781e15f..7be30993b 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_connect::{default_connector, Connect as TcpConnect, ConnectError}; +use actix_connect::{default_connector, Address, Connect as TcpConnect, ConnectError}; use actix_service::{apply_fn, Service}; use base64; use futures::future::{err, Either, FutureResult}; @@ -29,12 +29,12 @@ impl Client<()> { /// Create client with default connector. pub fn default() -> Client< impl Service< - Request = TcpConnect, + Request = TcpConnect<(String, u16)>, Response = impl AsyncRead + AsyncWrite, Error = ConnectError, > + Clone, > { - Client::new(apply_fn(default_connector(), |msg: TcpConnect, srv| { + Client::new(apply_fn(default_connector(), |msg: TcpConnect<_>, srv| { srv.call(msg).map(|stream| stream.into_parts().0) })) } @@ -42,7 +42,7 @@ impl Client<()> { impl Client where - T: Service, + T: Service, Error = ConnectError>, T::Response: AsyncRead + AsyncWrite, { /// Create new websocket's client factory @@ -53,7 +53,7 @@ where impl Clone for Client where - T: Service + Clone, + T: Service, Error = ConnectError> + Clone, T::Response: AsyncRead + AsyncWrite, { fn clone(&self) -> Self { @@ -65,7 +65,7 @@ where impl Service for Client where - T: Service, + T: Service, Error = ConnectError>, T::Response: AsyncRead + AsyncWrite + 'static, T::Future: 'static, { @@ -130,8 +130,8 @@ where ); // prep connection - let connect = TcpConnect::new( - request.uri().host().unwrap(), + let connect = TcpConnect::from_string( + request.uri().host().unwrap().to_string(), request.uri().port().unwrap_or_else(|| proto.port()), ); From 3a24a75d137fa41ba9db8b5b099b1b76c92b40d8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Mar 2019 16:56:11 -0700 Subject: [PATCH 192/208] update dep --- src/client/connector.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index aaa88abc0..b8b583a9d 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -173,7 +173,7 @@ where let connector = TimeoutService::new( self.timeout, apply_fn(self.connector, |msg: Connect, srv| { - srv.call(actix_connect::Connect::with_request(msg)) + srv.call(actix_connect::Connect::with(msg)) }) .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) @@ -200,7 +200,7 @@ where let ssl_service = TimeoutService::new( self.timeout, apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(actix_connect::Connect::with_request(msg)) + srv.call(actix_connect::Connect::with(msg)) }) .map_err(ConnectError::from) .and_then( @@ -230,7 +230,7 @@ where let tcp_service = TimeoutService::new( self.timeout, apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(actix_connect::Connect::with_request(msg)) + srv.call(actix_connect::Connect::with(msg)) }) .map_err(ConnectError::from) .map(|stream| (stream.into_parts().0, Protocol::Http1)), From d2c755bb47907f35e5883507b0a56cb23e4f4bf5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Mar 2019 22:57:28 -0700 Subject: [PATCH 193/208] update client connector --- src/client/connect.rs | 7 ++++--- src/client/connector.rs | 6 +++--- src/ws/client/service.rs | 14 ++++++-------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/client/connect.rs b/src/client/connect.rs index 93626b0a7..82e5e45c0 100644 --- a/src/client/connect.rs +++ b/src/client/connect.rs @@ -61,8 +61,8 @@ impl Address for Connect { &self.uri.host().unwrap() } - fn port(&self) -> u16 { - if let Some(port) = self.uri.port() { + fn port(&self) -> Option { + let port = if let Some(port) = self.uri.port() { port } else if let Some(scheme) = self.uri.scheme_part() { match scheme.as_str() { @@ -72,6 +72,7 @@ impl Address for Connect { } } else { 80 - } + }; + Some(port) } } diff --git a/src/client/connector.rs b/src/client/connector.rs index b8b583a9d..c764b93c4 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -173,7 +173,7 @@ where let connector = TimeoutService::new( self.timeout, apply_fn(self.connector, |msg: Connect, srv| { - srv.call(actix_connect::Connect::with(msg)) + srv.call(actix_connect::Connect::new(msg)) }) .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) @@ -200,7 +200,7 @@ where let ssl_service = TimeoutService::new( self.timeout, apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(actix_connect::Connect::with(msg)) + srv.call(actix_connect::Connect::new(msg)) }) .map_err(ConnectError::from) .and_then( @@ -230,7 +230,7 @@ where let tcp_service = TimeoutService::new( self.timeout, apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(actix_connect::Connect::with(msg)) + srv.call(actix_connect::Connect::new(msg)) }) .map_err(ConnectError::from) .map(|stream| (stream.into_parts().0, Protocol::Http1)), diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 7be30993b..1aa391249 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -29,7 +29,7 @@ impl Client<()> { /// Create client with default connector. pub fn default() -> Client< impl Service< - Request = TcpConnect<(String, u16)>, + Request = TcpConnect, Response = impl AsyncRead + AsyncWrite, Error = ConnectError, > + Clone, @@ -42,7 +42,7 @@ impl Client<()> { impl Client where - T: Service, Error = ConnectError>, + T: Service, Error = ConnectError>, T::Response: AsyncRead + AsyncWrite, { /// Create new websocket's client factory @@ -53,7 +53,7 @@ where impl Clone for Client where - T: Service, Error = ConnectError> + Clone, + T: Service, Error = ConnectError> + Clone, T::Response: AsyncRead + AsyncWrite, { fn clone(&self) -> Self { @@ -65,7 +65,7 @@ where impl Service for Client where - T: Service, Error = ConnectError>, + T: Service, Error = ConnectError>, T::Response: AsyncRead + AsyncWrite + 'static, T::Future: 'static, { @@ -130,10 +130,8 @@ where ); // prep connection - let connect = TcpConnect::from_string( - request.uri().host().unwrap().to_string(), - request.uri().port().unwrap_or_else(|| proto.port()), - ); + let connect = TcpConnect::new(request.uri().host().unwrap().to_string()) + .set_port(request.uri().port().unwrap_or_else(|| proto.port())); let fut = Box::new( self.connector From b8bfd29d2c5e9b5a9ce10e27a1e1898d54e40444 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Mar 2019 11:52:52 -0700 Subject: [PATCH 194/208] use Uri as client connect message --- Cargo.toml | 2 +- src/client/connect.rs | 78 ---------------------- src/client/connector.rs | 136 +++++++++++++++++++-------------------- src/client/mod.rs | 2 - src/client/pool.rs | 132 ++++--------------------------------- src/client/request.rs | 37 +++++++---- src/ws/client/service.rs | 4 +- test-server/src/lib.rs | 14 ++-- 8 files changed, 112 insertions(+), 293 deletions(-) delete mode 100644 src/client/connect.rs diff --git a/Cargo.toml b/Cargo.toml index 11f532c89..3b9a84984 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ encoding = "0.2" futures = "0.1" hashbrown = "0.1.8" h2 = "0.1.16" -http = "0.1.8" +http = "0.1.16" httparse = "1.3" indexmap = "1.0" lazy_static = "1.0" diff --git a/src/client/connect.rs b/src/client/connect.rs deleted file mode 100644 index 82e5e45c0..000000000 --- a/src/client/connect.rs +++ /dev/null @@ -1,78 +0,0 @@ -use actix_connect::Address; -use http::uri::Uri; -use http::HttpTryFrom; - -use super::error::InvalidUrl; -use super::pool::Key; - -#[derive(Debug)] -/// `Connect` type represents a message that can be sent to -/// `Connector` with a connection request. -pub struct Connect { - pub(crate) uri: Uri, -} - -impl Connect { - /// Create `Connect` message for specified `Uri` - pub fn new(uri: Uri) -> Connect { - Connect { uri } - } - - /// Construct `Uri` instance and create `Connect` message. - pub fn try_from(uri: U) -> Result - where - Uri: HttpTryFrom, - { - Ok(Connect { - uri: Uri::try_from(uri).map_err(|e| e.into())?, - }) - } - - pub(crate) fn is_secure(&self) -> bool { - if let Some(scheme) = self.uri.scheme_part() { - scheme.as_str() == "https" - } else { - false - } - } - - pub(crate) fn key(&self) -> Key { - self.uri.authority_part().unwrap().clone().into() - } - - pub(crate) fn validate(&self) -> Result<(), InvalidUrl> { - if self.uri.host().is_none() { - Err(InvalidUrl::MissingHost) - } else if self.uri.scheme_part().is_none() { - Err(InvalidUrl::MissingScheme) - } else if let Some(scheme) = self.uri.scheme_part() { - match scheme.as_str() { - "http" | "ws" | "https" | "wss" => Ok(()), - _ => Err(InvalidUrl::UnknownScheme), - } - } else { - Ok(()) - } - } -} - -impl Address for Connect { - fn host(&self) -> &str { - &self.uri.host().unwrap() - } - - fn port(&self) -> Option { - let port = if let Some(port) = self.uri.port() { - port - } else if let Some(scheme) = self.uri.scheme_part() { - match scheme.as_str() { - "http" | "ws" => 80, - "https" | "wss" => 443, - _ => 80, - } - } else { - 80 - }; - Some(port) - } -} diff --git a/src/client/connector.rs b/src/client/connector.rs index c764b93c4..b8054151b 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -8,9 +8,9 @@ use actix_connect::{ }; use actix_service::{apply_fn, Service, ServiceExt}; use actix_utils::timeout::{TimeoutError, TimeoutService}; +use http::Uri; use tokio_tcp::TcpStream; -use super::connect::Connect; use super::connection::Connection; use super::error::ConnectError; use super::pool::{ConnectionPool, Protocol}; @@ -38,8 +38,8 @@ pub struct Connector { impl Connector<(), ()> { pub fn new() -> Connector< impl Service< - Request = TcpConnect, - Response = TcpConnection, + Request = TcpConnect, + Response = TcpConnection, Error = actix_connect::ConnectError, > + Clone, TcpStream, @@ -79,8 +79,8 @@ impl Connector { where U1: AsyncRead + AsyncWrite + fmt::Debug, T1: Service< - Request = TcpConnect, - Response = TcpConnection, + Request = TcpConnect, + Response = TcpConnection, Error = actix_connect::ConnectError, > + Clone, { @@ -101,8 +101,8 @@ impl Connector where U: AsyncRead + AsyncWrite + fmt::Debug + 'static, T: Service< - Request = TcpConnect, - Response = TcpConnection, + Request = TcpConnect, + Response = TcpConnection, Error = actix_connect::ConnectError, > + Clone, { @@ -166,16 +166,14 @@ where /// Finish configuration process and create connector service. pub fn service( self, - ) -> impl Service - + Clone { + ) -> impl Service + Clone + { #[cfg(not(feature = "ssl"))] { let connector = TimeoutService::new( self.timeout, - apply_fn(self.connector, |msg: Connect, srv| { - srv.call(actix_connect::Connect::new(msg)) - }) - .map(|stream| (stream.into_parts().0, Protocol::Http1)), + apply_fn(self.connector, |msg: Uri, srv| srv.call(msg.into())) + .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -199,28 +197,26 @@ where let ssl_service = TimeoutService::new( self.timeout, - apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(actix_connect::Connect::new(msg)) - }) - .map_err(ConnectError::from) - .and_then( - OpensslConnector::service(self.ssl) - .map_err(ConnectError::from) - .map(|stream| { - let sock = stream.into_parts().0; - let h2 = sock - .get_ref() - .ssl() - .selected_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); - if h2 { - (sock, Protocol::Http2) - } else { - (sock, Protocol::Http1) - } - }), - ), + apply_fn(self.connector.clone(), |msg: Uri, srv| srv.call(msg.into())) + .map_err(ConnectError::from) + .and_then( + OpensslConnector::service(self.ssl) + .map_err(ConnectError::from) + .map(|stream| { + let sock = stream.into_parts().0; + let h2 = sock + .get_ref() + .ssl() + .selected_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (sock, Protocol::Http2) + } else { + (sock, Protocol::Http1) + } + }), + ), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -229,11 +225,9 @@ where let tcp_service = TimeoutService::new( self.timeout, - apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(actix_connect::Connect::new(msg)) - }) - .map_err(ConnectError::from) - .map(|stream| (stream.into_parts().0, Protocol::Http1)), + apply_fn(self.connector.clone(), |msg: Uri, srv| srv.call(msg.into())) + .map_err(ConnectError::from) + .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -271,7 +265,7 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) tcp_pool: ConnectionPool, } @@ -279,7 +273,7 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service + T: Service + Clone, { fn clone(&self) -> Self { @@ -292,9 +286,9 @@ mod connect_impl { impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { - type Request = Connect; + type Request = Uri; type Response = IoConnection; type Error = ConnectorError; type Future = Either< @@ -306,13 +300,12 @@ mod connect_impl { self.tcp_pool.poll_ready() } - fn call(&mut self, req: Connect) -> Self::Future { - if req.is_secure() { - Either::B(err(ConnectError::SslIsNotSupported)) - } else if let Err(e) = req.validate() { - Either::B(err(e)) - } else { - Either::A(self.tcp_pool.call(req)) + fn call(&mut self, req: Uri) -> Self::Future { + match req.scheme_str() { + Some("https") | Some("wss") => { + Either::B(err(ConnectError::SslIsNotSupported)) + } + _ => Either::A(self.tcp_pool.call(req)), } } } @@ -332,8 +325,8 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service, - T2: Service, + T1: Service, + T2: Service, { pub(crate) tcp_pool: ConnectionPool, pub(crate) ssl_pool: ConnectionPool, @@ -343,9 +336,9 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service + T1: Service + Clone, - T2: Service + T2: Service + Clone, { fn clone(&self) -> Self { @@ -360,10 +353,10 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service, - T2: Service, + T1: Service, + T2: Service, { - type Request = Connect; + type Request = Uri; type Response = EitherConnection; type Error = ConnectError; type Future = Either< @@ -378,17 +371,18 @@ mod connect_impl { self.tcp_pool.poll_ready() } - fn call(&mut self, req: Connect) -> Self::Future { - if req.is_secure() { - Either::B(Either::B(InnerConnectorResponseB { - fut: self.ssl_pool.call(req), - _t: PhantomData, - })) - } else { - Either::B(Either::A(InnerConnectorResponseA { + fn call(&mut self, req: Uri) -> Self::Future { + match req.scheme_str() { + Some("https") | Some("wss") => { + Either::B(Either::B(InnerConnectorResponseB { + fut: self.ssl_pool.call(req), + _t: PhantomData, + })) + } + _ => Either::B(Either::A(InnerConnectorResponseA { fut: self.tcp_pool.call(req), _t: PhantomData, - })) + })), } } } @@ -396,7 +390,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -404,7 +398,7 @@ mod connect_impl { impl Future for InnerConnectorResponseA where - T: Service, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { @@ -422,7 +416,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -430,7 +424,7 @@ mod connect_impl { impl Future for InnerConnectorResponseB where - T: Service, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { diff --git a/src/client/mod.rs b/src/client/mod.rs index 0bff97e49..86b1a0cc0 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,5 +1,4 @@ //! Http client api -mod connect; mod connection; mod connector; mod error; @@ -9,7 +8,6 @@ mod pool; mod request; mod response; -pub use self::connect::Connect; pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectError, InvalidUrl, SendRequestError}; diff --git a/src/client/pool.rs b/src/client/pool.rs index 214b7a382..a94b1e52a 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -7,18 +7,17 @@ use std::time::{Duration, Instant}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::Service; use bytes::Bytes; -use futures::future::{ok, Either, FutureResult}; +use futures::future::{err, ok, Either, FutureResult}; use futures::task::AtomicTask; use futures::unsync::oneshot; use futures::{Async, Future, Poll}; use h2::client::{handshake, Handshake}; use hashbrown::HashMap; -use http::uri::Authority; +use http::uri::{Authority, Uri}; use indexmap::IndexSet; use slab::Slab; use tokio_timer::{sleep, Delay}; -use super::connect::Connect; use super::connection::{ConnectionType, IoConnection}; use super::error::ConnectError; @@ -48,7 +47,7 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) fn new( connector: T, @@ -87,9 +86,9 @@ where impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { - type Request = Connect; + type Request = Uri; type Response = IoConnection; type Error = ConnectError; type Future = Either< @@ -101,8 +100,12 @@ where self.0.poll_ready() } - fn call(&mut self, req: Connect) -> Self::Future { - let key = req.key(); + fn call(&mut self, req: Uri) -> Self::Future { + let key = if let Some(authority) = req.authority_part() { + authority.clone().into() + } else { + return Either::A(err(ConnectError::Unresolverd)); + }; // acquire connection match self.1.as_ref().borrow_mut().acquire(&key) { @@ -268,110 +271,6 @@ where } } -// struct OpenWaitingConnection -// where -// Io: AsyncRead + AsyncWrite + 'static, -// { -// fut: F, -// key: Key, -// h2: Option>, -// rx: Option, ConnectorError>>>, -// inner: Option>>>, -// } - -// impl OpenWaitingConnection -// where -// F: Future + 'static, -// Io: AsyncRead + AsyncWrite + 'static, -// { -// fn spawn( -// key: Key, -// rx: oneshot::Sender, ConnectorError>>, -// inner: Rc>>, -// fut: F, -// ) { -// tokio_current_thread::spawn(OpenWaitingConnection { -// key, -// fut, -// h2: None, -// rx: Some(rx), -// inner: Some(inner), -// }) -// } -// } - -// impl Drop for OpenWaitingConnection -// where -// Io: AsyncRead + AsyncWrite + 'static, -// { -// fn drop(&mut self) { -// if let Some(inner) = self.inner.take() { -// let mut inner = inner.as_ref().borrow_mut(); -// inner.release(); -// inner.check_availibility(); -// } -// } -// } - -// impl Future for OpenWaitingConnection -// where -// F: Future, -// Io: AsyncRead + AsyncWrite, -// { -// type Item = (); -// type Error = (); - -// fn poll(&mut self) -> Poll { -// if let Some(ref mut h2) = self.h2 { -// return match h2.poll() { -// Ok(Async::Ready((snd, connection))) => { -// tokio_current_thread::spawn(connection.map_err(|_| ())); -// let _ = self.rx.take().unwrap().send(Ok(IoConnection::new( -// ConnectionType::H2(snd), -// Instant::now(), -// Some(Acquired(self.key.clone(), self.inner.clone())), -// ))); -// Ok(Async::Ready(())) -// } -// Ok(Async::NotReady) => Ok(Async::NotReady), -// Err(e) => { -// let _ = self.inner.take(); -// if let Some(rx) = self.rx.take() { -// let _ = rx.send(Err(e.into())); -// } - -// Err(()) -// } -// }; -// } - -// match self.fut.poll() { -// Err(err) => { -// let _ = self.inner.take(); -// if let Some(rx) = self.rx.take() { -// let _ = rx.send(Err(err)); -// } -// Err(()) -// } -// Ok(Async::Ready((_, io, proto))) => { -// let _ = self.inner.take(); -// if proto == Protocol::Http1 { -// let _ = self.rx.take().unwrap().send(Ok(IoConnection::new( -// ConnectionType::H1(io), -// Instant::now(), -// Some(Acquired(self.key.clone(), self.inner.clone())), -// ))); -// } else { -// self.h2 = Some(handshake(io)); -// return self.poll(); -// } -// Ok(Async::Ready(())) -// } -// Ok(Async::NotReady) => Ok(Async::NotReady), -// } -// } -// } - enum Acquire { Acquired(ConnectionType, Instant), Available, @@ -392,10 +291,7 @@ pub(crate) struct Inner { limit: usize, acquired: usize, available: HashMap>>, - waiters: Slab<( - Connect, - oneshot::Sender, ConnectError>>, - )>, + waiters: Slab<(Uri, oneshot::Sender, ConnectError>>)>, waiters_queue: IndexSet<(Key, usize)>, task: AtomicTask, } @@ -434,14 +330,14 @@ where /// connection is not available, wait fn wait_for( &mut self, - connect: Connect, + connect: Uri, ) -> ( oneshot::Receiver, ConnectError>>, usize, ) { let (tx, rx) = oneshot::channel(); - let key = connect.key(); + let key: Key = connect.authority_part().unwrap().clone().into(); let entry = self.waiters.vacant_entry(); let token = entry.key(); entry.insert((connect, tx)); diff --git a/src/client/request.rs b/src/client/request.rs index 199e13b93..7c7079fb7 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -21,8 +21,8 @@ use crate::http::{ use crate::message::{ConnectionType, Head, RequestHead}; use super::connection::Connection; +use super::error::{ConnectError, InvalidUrl, SendRequestError}; use super::response::ClientResponse; -use super::{Connect, ConnectError, SendRequestError}; /// An HTTP Client Request /// @@ -180,23 +180,32 @@ where ) -> impl Future where B: 'static, - T: Service, + T: Service, I: Connection, { let Self { head, body } = self; - let connect = Connect::new(head.uri.clone()); - if let Err(e) = connect.validate() { - Either::A(err(e.into())) + let uri = head.uri.clone(); + + // validate uri + if uri.host().is_none() { + Either::A(err(InvalidUrl::MissingHost.into())) + } else if uri.scheme_part().is_none() { + Either::A(err(InvalidUrl::MissingScheme.into())) + } else if let Some(scheme) = uri.scheme_part() { + match scheme.as_str() { + "http" | "ws" | "https" | "wss" => Either::B( + connector + // connect to the host + .call(uri) + .from_err() + // send request + .and_then(move |connection| connection.send_request(head, body)), + ), + _ => Either::A(err(InvalidUrl::UnknownScheme.into())), + } } else { - Either::B( - connector - // connect to the host - .call(connect) - .from_err() - // send request - .and_then(move |connection| connection.send_request(head, body)), - ) + Either::A(err(InvalidUrl::UnknownScheme.into())) } } } @@ -529,7 +538,7 @@ impl ClientRequestBuilder { if !parts.headers.contains_key(header::HOST) { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - let _ = match parts.uri.port() { + let _ = match parts.uri.port_u16() { None | Some(80) | Some(443) => write!(wrt, "{}", host), Some(port) => write!(wrt, "{}:{}", host, port), }; diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 1aa391249..a0a9b2030 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_connect::{default_connector, Address, Connect as TcpConnect, ConnectError}; +use actix_connect::{default_connector, Connect as TcpConnect, ConnectError}; use actix_service::{apply_fn, Service}; use base64; use futures::future::{err, Either, FutureResult}; @@ -131,7 +131,7 @@ where // prep connection let connect = TcpConnect::new(request.uri().host().unwrap().to_string()) - .set_port(request.uri().port().unwrap_or_else(|| proto.port())); + .set_port(request.uri().port_u16().unwrap_or_else(|| proto.port())); let fut = Box::new( self.connector diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 3bb5feffb..26bca787e 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -5,10 +5,10 @@ use std::{net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::body::MessageBody; use actix_http::client::{ - ClientRequest, ClientRequestBuilder, ClientResponse, Connect, ConnectError, - Connection, Connector, SendRequestError, + ClientRequest, ClientRequestBuilder, ClientResponse, ConnectError, Connection, + Connector, SendRequestError, }; -use actix_http::ws; +use actix_http::{http::Uri, ws}; use actix_rt::{Runtime, System}; use actix_server::{Server, StreamServiceFactory}; use actix_service::Service; @@ -158,8 +158,8 @@ impl TestServerRuntime { } fn new_connector( - ) -> impl Service - + Clone { + ) -> impl Service + Clone + { #[cfg(feature = "ssl")] { use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; @@ -185,8 +185,8 @@ impl TestServerRuntime { /// Http connector pub fn connector( &mut self, - ) -> impl Service - + Clone { + ) -> impl Service + Clone + { self.execute(|| TestServerRuntime::new_connector()) } From 76bb30dc3ae263791b10f055826696f8d605e987 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Mar 2019 13:06:29 -0700 Subject: [PATCH 195/208] fix names --- src/client/connector.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index b8054151b..de1615066 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -265,7 +265,7 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) tcp_pool: ConnectionPool, } @@ -286,14 +286,14 @@ mod connect_impl { impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { type Request = Uri; type Response = IoConnection; - type Error = ConnectorError; + type Error = ConnectError; type Future = Either< as Service>::Future, - FutureResult, ConnectorError>, + FutureResult, ConnectError>, >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { From 15ba40d3ab1c4335d0b887ab8d55939ad20761a3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Mar 2019 13:08:05 -0700 Subject: [PATCH 196/208] fix non ssl connector --- src/client/connector.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/connector.rs b/src/client/connector.rs index de1615066..804756ced 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -173,6 +173,7 @@ where let connector = TimeoutService::new( self.timeout, apply_fn(self.connector, |msg: Uri, srv| srv.call(msg.into())) + .map_err(ConnectError::from) .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) .map_err(|e| match e { From ce4a2629f35c28ce80a09423e9386764f35df14e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Mar 2019 22:56:06 -0700 Subject: [PATCH 197/208] update actix-connect --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3b9a84984..a68489f51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ fail = ["failure"] [dependencies] actix-service = "0.3.4" actix-codec = "0.1.1" -actix-connect = { git = "https://github.com/actix/actix-net.git" } +actix-connect = "0.1.0" actix-utils = "0.3.4" actix-server-config = "0.1.0" @@ -85,7 +85,7 @@ failure = { version = "0.1.5", optional = true } [dev-dependencies] actix-rt = "0.2.0" actix-server = { version = "0.4.0", features=["ssl"] } -actix-connector = { version = "0.3.0", features=["ssl"] } +actix-connect = { version = "0.1.0", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" From fd141ef9b109c11ba6fb7007b261364037608ed8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 16 Mar 2019 22:10:15 -0700 Subject: [PATCH 198/208] move json to actix-web --- src/httpmessage.rs | 40 -------- src/json.rs | 222 --------------------------------------------- src/lib.rs | 2 - 3 files changed, 264 deletions(-) delete mode 100644 src/json.rs diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 3c049c09b..8573e917d 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -19,7 +19,6 @@ use crate::error::{ }; use crate::extensions::Extensions; use crate::header::Header; -use crate::json::JsonBody; use crate::payload::Payload; struct Cookies(Vec>); @@ -219,45 +218,6 @@ pub trait HttpMessage: Sized { UrlEncoded::new(self) } - /// Parse `application/json` encoded body. - /// Return `JsonBody` future. It resolves to a `T` value. - /// - /// Returns error: - /// - /// * content type is not `application/json` - /// * content length is greater than 256k - /// - /// ## Server example - /// - /// ```rust,ignore - /// # extern crate actix_web; - /// # extern crate futures; - /// # #[macro_use] extern crate serde_derive; - /// use actix_web::*; - /// use futures::future::{ok, Future}; - /// - /// #[derive(Deserialize, Debug)] - /// struct MyObj { - /// name: String, - /// } - /// - /// fn index(mut req: HttpRequest) -> Box> { - /// req.json() // <- get JsonBody future - /// .from_err() - /// .and_then(|val: MyObj| { // <- deserialized value - /// println!("==== BODY ==== {:?}", val); - /// Ok(Response::Ok().into()) - /// }).responder() - /// } - /// # fn main() {} - /// ``` - fn json(&mut self) -> JsonBody - where - Self::Stream: Stream + 'static, - { - JsonBody::new(self) - } - /// Return stream of lines. fn readlines(&mut self) -> Readlines where diff --git a/src/json.rs b/src/json.rs deleted file mode 100644 index 026ecb871..000000000 --- a/src/json.rs +++ /dev/null @@ -1,222 +0,0 @@ -use bytes::BytesMut; -use futures::{Future, Poll, Stream}; -use http::header::CONTENT_LENGTH; - -use bytes::Bytes; -use mime; -use serde::de::DeserializeOwned; -use serde_json; - -use crate::error::{JsonPayloadError, PayloadError}; -use crate::httpmessage::HttpMessage; -use crate::payload::Payload; - -/// Request payload json parser that resolves to a deserialized `T` value. -/// -/// Returns error: -/// -/// * content type is not `application/json` -/// * content length is greater than 256k -/// -/// # Server example -/// -/// ```rust,ignore -/// # extern crate actix_web; -/// # extern crate futures; -/// # #[macro_use] extern crate serde_derive; -/// use actix_web::{AsyncResponder, Error, HttpMessage, HttpRequest, Response}; -/// use futures::future::Future; -/// -/// #[derive(Deserialize, Debug)] -/// struct MyObj { -/// name: String, -/// } -/// -/// fn index(mut req: HttpRequest) -> Box> { -/// req.json() // <- get JsonBody future -/// .from_err() -/// .and_then(|val: MyObj| { // <- deserialized value -/// println!("==== BODY ==== {:?}", val); -/// Ok(Response::Ok().into()) -/// }).responder() -/// } -/// # fn main() {} -/// ``` -pub struct JsonBody { - limit: usize, - length: Option, - stream: Payload, - err: Option, - fut: Option>>, -} - -impl JsonBody -where - T: HttpMessage, - T::Stream: Stream + 'static, - U: DeserializeOwned + 'static, -{ - /// Create `JsonBody` for request. - pub fn new(req: &mut T) -> Self { - // check content-type - let json = if let Ok(Some(mime)) = req.mime_type() { - mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) - } else { - false - }; - if !json { - return JsonBody { - limit: 262_144, - length: None, - stream: Payload::None, - fut: None, - err: Some(JsonPayloadError::ContentType), - }; - } - - let mut len = None; - if let Some(l) = req.headers().get(CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } - } - } - - JsonBody { - limit: 262_144, - length: len, - stream: req.take_payload(), - fut: None, - err: None, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } -} - -impl Future for JsonBody -where - T: HttpMessage, - T::Stream: Stream + 'static, - U: DeserializeOwned + 'static, -{ - type Item = U; - type Error = JsonPayloadError; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut { - return fut.poll(); - } - - if let Some(err) = self.err.take() { - return Err(err); - } - - let limit = self.limit; - if let Some(len) = self.length.take() { - if len > limit { - return Err(JsonPayloadError::Overflow); - } - } - - let fut = std::mem::replace(&mut self.stream, Payload::None) - .from_err() - .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(JsonPayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .and_then(|body| Ok(serde_json::from_slice::(&body)?)); - self.fut = Some(Box::new(fut)); - self.poll() - } -} - -#[cfg(test)] -mod tests { - use bytes::Bytes; - use futures::Async; - use http::header; - use serde_derive::{Deserialize, Serialize}; - - use super::*; - use crate::test::TestRequest; - - impl PartialEq for JsonPayloadError { - fn eq(&self, other: &JsonPayloadError) -> bool { - match *self { - JsonPayloadError::Overflow => match *other { - JsonPayloadError::Overflow => true, - _ => false, - }, - JsonPayloadError::ContentType => match *other { - JsonPayloadError::ContentType => true, - _ => false, - }, - _ => false, - } - } - } - - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct MyObject { - name: String, - } - - #[test] - fn test_json_body() { - let mut req = TestRequest::default().finish(); - let mut json = req.json::(); - assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - - let mut req = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/text"), - ) - .finish(); - let mut json = req.json::(); - assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - - let mut req = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("10000"), - ) - .finish(); - let mut json = req.json::().limit(100); - assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); - - let mut req = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .finish(); - - let mut json = req.json::(); - assert_eq!( - json.poll().ok().unwrap(), - Async::Ready(MyObject { - name: "test".to_owned() - }) - ); - } -} diff --git a/src/lib.rs b/src/lib.rs index 41ee55fec..443266a9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,7 +77,6 @@ mod header; mod helpers; mod httpcodes; pub mod httpmessage; -mod json; mod message; mod payload; mod request; @@ -113,7 +112,6 @@ pub mod dev { //! ``` pub use crate::httpmessage::{MessageBody, Readlines, UrlEncoded}; - pub use crate::json::JsonBody; pub use crate::response::ResponseBuilder; } From fa66a07ec5070621292b411ab48b501cad18002e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 01:02:51 -0700 Subject: [PATCH 199/208] move httpmessage futures to actix-web --- examples/echo.rs | 27 +- examples/echo2.rs | 25 +- src/error.rs | 74 ----- src/httpmessage.rs | 627 +------------------------------------------ src/lib.rs | 17 +- tests/test_client.rs | 22 +- tests/test_server.rs | 54 ++-- 7 files changed, 84 insertions(+), 762 deletions(-) diff --git a/examples/echo.rs b/examples/echo.rs index 8ec0e6a97..c36292c44 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -1,10 +1,9 @@ use std::{env, io}; -use actix_http::HttpMessage; -use actix_http::{HttpService, Request, Response}; +use actix_http::{error::PayloadError, HttpService, Request, Response}; use actix_server::Server; -use bytes::Bytes; -use futures::Future; +use bytes::BytesMut; +use futures::{Future, Stream}; use http::header::HeaderValue; use log::info; @@ -18,12 +17,20 @@ fn main() -> io::Result<()> { .client_timeout(1000) .client_disconnect(1000) .finish(|mut req: Request| { - req.body().limit(512).and_then(|bytes: Bytes| { - info!("request body: {:?}", bytes); - let mut res = Response::Ok(); - res.header("x-head", HeaderValue::from_static("dummy value!")); - Ok(res.body(bytes)) - }) + req.take_payload() + .fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, PayloadError>(body) + }) + .and_then(|bytes| { + info!("request body: {:?}", bytes); + let mut res = Response::Ok(); + res.header( + "x-head", + HeaderValue::from_static("dummy value!"), + ); + Ok(res.body(bytes)) + }) }) })? .run() diff --git a/examples/echo2.rs b/examples/echo2.rs index 101adc1cf..b239796b4 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -1,20 +1,25 @@ use std::{env, io}; use actix_http::http::HeaderValue; -use actix_http::HttpMessage; -use actix_http::{Error, HttpService, Request, Response}; +use actix_http::{error::PayloadError, Error, HttpService, Request, Response}; use actix_server::Server; -use bytes::Bytes; -use futures::Future; +use bytes::BytesMut; +use futures::{Future, Stream}; use log::info; fn handle_request(mut req: Request) -> impl Future { - req.body().limit(512).from_err().and_then(|bytes: Bytes| { - info!("request body: {:?}", bytes); - let mut res = Response::Ok(); - res.header("x-head", HeaderValue::from_static("dummy value!")); - Ok(res.body(bytes)) - }) + req.take_payload() + .fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, PayloadError>(body) + }) + .from_err() + .and_then(|bytes| { + info!("request body: {:?}", bytes); + let mut res = Response::Ok(); + res.header("x-head", HeaderValue::from_static("dummy value!")); + Ok(res.body(bytes)) + }) } fn main() -> io::Result<()> { diff --git a/src/error.rs b/src/error.rs index 696162f86..e0a416ef8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -390,80 +390,6 @@ impl ResponseError for ContentTypeError { } } -/// A set of errors that can occur during parsing urlencoded payloads -#[derive(Debug, Display, From)] -pub enum UrlencodedError { - /// Can not decode chunked transfer encoding - #[display(fmt = "Can not decode chunked transfer encoding")] - Chunked, - /// Payload size is bigger than allowed. (default: 256kB) - #[display(fmt = "Urlencoded payload size is bigger than allowed. (default: 256kB)")] - Overflow, - /// Payload size is now known - #[display(fmt = "Payload size is now known")] - UnknownLength, - /// Content type error - #[display(fmt = "Content type error")] - ContentType, - /// Parse error - #[display(fmt = "Parse error")] - Parse, - /// Payload error - #[display(fmt = "Error that occur during reading payload: {}", _0)] - Payload(PayloadError), -} - -/// Return `BadRequest` for `UrlencodedError` -impl ResponseError for UrlencodedError { - fn error_response(&self) -> Response { - match *self { - UrlencodedError::Overflow => Response::new(StatusCode::PAYLOAD_TOO_LARGE), - UrlencodedError::UnknownLength => Response::new(StatusCode::LENGTH_REQUIRED), - _ => Response::new(StatusCode::BAD_REQUEST), - } - } -} - -/// A set of errors that can occur during parsing json payloads -#[derive(Debug, Display, From)] -pub enum JsonPayloadError { - /// Payload size is bigger than allowed. (default: 256kB) - #[display(fmt = "Json payload size is bigger than allowed. (default: 256kB)")] - Overflow, - /// Content type error - #[display(fmt = "Content type error")] - ContentType, - /// Deserialize error - #[display(fmt = "Json deserialize error: {}", _0)] - Deserialize(JsonError), - /// Payload error - #[display(fmt = "Error that occur during reading payload: {}", _0)] - Payload(PayloadError), -} - -/// Return `BadRequest` for `UrlencodedError` -impl ResponseError for JsonPayloadError { - fn error_response(&self) -> Response { - match *self { - JsonPayloadError::Overflow => Response::new(StatusCode::PAYLOAD_TOO_LARGE), - _ => Response::new(StatusCode::BAD_REQUEST), - } - } -} - -/// Error type returned when reading body as lines. -#[derive(From)] -pub enum ReadlinesError { - /// Error when decoding a line. - EncodingError, - /// Payload error. - PayloadError(PayloadError), - /// Line limit exceeded. - LimitOverflow, - /// ContentType error. - ContentTypeError(ContentTypeError), -} - /// Helper type that can wrap any error and generate custom response. /// /// In following example any `io::Error` will be converted into "BAD REQUEST" diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 8573e917d..117e10a81 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,22 +1,14 @@ use std::cell::{Ref, RefMut}; use std::str; -use bytes::{Bytes, BytesMut}; use cookie::Cookie; use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; -use encoding::types::{DecoderTrap, Encoding}; use encoding::EncodingRef; -use futures::{Async, Future, Poll, Stream}; use http::{header, HeaderMap}; use mime::Mime; -use serde::de::DeserializeOwned; -use serde_urlencoded; -use crate::error::{ - ContentTypeError, CookieParseError, ParseError, PayloadError, ReadlinesError, - UrlencodedError, -}; +use crate::error::{ContentTypeError, CookieParseError, ParseError}; use crate::extensions::Extensions; use crate::header::Header; use crate::payload::Payload; @@ -143,88 +135,6 @@ pub trait HttpMessage: Sized { } None } - - /// Load http message body. - /// - /// By default only 256Kb payload reads to a memory, then - /// `PayloadError::Overflow` get returned. Use `MessageBody::limit()` - /// method to change upper limit. - /// - /// ## Server example - /// - /// ```rust,ignore - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// # #[macro_use] extern crate serde_derive; - /// use actix_web::{ - /// AsyncResponder, FutureResponse, HttpMessage, HttpRequest, Response, - /// }; - /// use bytes::Bytes; - /// use futures::future::Future; - /// - /// fn index(mut req: HttpRequest) -> FutureResponse { - /// req.body() // <- get Body future - /// .limit(1024) // <- change max size of the body to a 1kb - /// .from_err() - /// .and_then(|bytes: Bytes| { // <- complete body - /// println!("==== BODY ==== {:?}", bytes); - /// Ok(Response::Ok().into()) - /// }).responder() - /// } - /// # fn main() {} - /// ``` - fn body(&mut self) -> MessageBody - where - Self::Stream: Stream + Sized, - { - MessageBody::new(self) - } - - /// Parse `application/x-www-form-urlencoded` encoded request's body. - /// Return `UrlEncoded` future. Form can be deserialized to any type that - /// implements `Deserialize` trait from *serde*. - /// - /// Returns error: - /// - /// * content type is not `application/x-www-form-urlencoded` - /// * content-length is greater than 256k - /// - /// ## Server example - /// - /// ```rust,ignore - /// # extern crate actix_web; - /// # extern crate futures; - /// # use futures::Future; - /// # use std::collections::HashMap; - /// use actix_web::{FutureResponse, HttpMessage, HttpRequest, Response}; - /// - /// fn index(mut req: HttpRequest) -> FutureResponse { - /// Box::new( - /// req.urlencoded::>() // <- get UrlEncoded future - /// .from_err() - /// .and_then(|params| { // <- url encoded parameters - /// println!("==== BODY ==== {:?}", params); - /// Ok(Response::Ok().into()) - /// }), - /// ) - /// } - /// # fn main() {} - /// ``` - fn urlencoded(&mut self) -> UrlEncoded - where - Self::Stream: Stream, - { - UrlEncoded::new(self) - } - - /// Return stream of lines. - fn readlines(&mut self) -> Readlines - where - Self::Stream: Stream + 'static, - { - Readlines::new(self) - } } impl<'a, T> HttpMessage for &'a mut T @@ -253,383 +163,12 @@ where } } -/// Stream to read request line by line. -pub struct Readlines { - stream: Payload, - buff: BytesMut, - limit: usize, - checked_buff: bool, - encoding: EncodingRef, - err: Option, -} - -impl Readlines -where - T: HttpMessage, - T::Stream: Stream, -{ - /// Create a new stream to read request line by line. - fn new(req: &mut T) -> Self { - let encoding = match req.encoding() { - Ok(enc) => enc, - Err(err) => return Self::err(err.into()), - }; - - Readlines { - stream: req.take_payload(), - buff: BytesMut::with_capacity(262_144), - limit: 262_144, - checked_buff: true, - err: None, - encoding, - } - } - - /// Change max line size. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - fn err(err: ReadlinesError) -> Self { - Readlines { - stream: Payload::None, - buff: BytesMut::new(), - limit: 262_144, - checked_buff: true, - encoding: UTF_8, - err: Some(err), - } - } -} - -impl Stream for Readlines -where - T: HttpMessage, - T::Stream: Stream, -{ - type Item = String; - type Error = ReadlinesError; - - fn poll(&mut self) -> Poll, Self::Error> { - if let Some(err) = self.err.take() { - return Err(err); - } - - // check if there is a newline in the buffer - if !self.checked_buff { - let mut found: Option = None; - for (ind, b) in self.buff.iter().enumerate() { - if *b == b'\n' { - found = Some(ind); - break; - } - } - if let Some(ind) = found { - // check if line is longer than limit - if ind + 1 > self.limit { - return Err(ReadlinesError::LimitOverflow); - } - let enc: *const Encoding = self.encoding as *const Encoding; - let line = if enc == UTF_8 { - str::from_utf8(&self.buff.split_to(ind + 1)) - .map_err(|_| ReadlinesError::EncodingError)? - .to_owned() - } else { - self.encoding - .decode(&self.buff.split_to(ind + 1), DecoderTrap::Strict) - .map_err(|_| ReadlinesError::EncodingError)? - }; - return Ok(Async::Ready(Some(line))); - } - self.checked_buff = true; - } - // poll req for more bytes - match self.stream.poll() { - Ok(Async::Ready(Some(mut bytes))) => { - // check if there is a newline in bytes - let mut found: Option = None; - for (ind, b) in bytes.iter().enumerate() { - if *b == b'\n' { - found = Some(ind); - break; - } - } - if let Some(ind) = found { - // check if line is longer than limit - if ind + 1 > self.limit { - return Err(ReadlinesError::LimitOverflow); - } - let enc: *const Encoding = self.encoding as *const Encoding; - let line = if enc == UTF_8 { - str::from_utf8(&bytes.split_to(ind + 1)) - .map_err(|_| ReadlinesError::EncodingError)? - .to_owned() - } else { - self.encoding - .decode(&bytes.split_to(ind + 1), DecoderTrap::Strict) - .map_err(|_| ReadlinesError::EncodingError)? - }; - // extend buffer with rest of the bytes; - self.buff.extend_from_slice(&bytes); - self.checked_buff = false; - return Ok(Async::Ready(Some(line))); - } - self.buff.extend_from_slice(&bytes); - Ok(Async::NotReady) - } - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(None)) => { - if self.buff.is_empty() { - return Ok(Async::Ready(None)); - } - if self.buff.len() > self.limit { - return Err(ReadlinesError::LimitOverflow); - } - let enc: *const Encoding = self.encoding as *const Encoding; - let line = if enc == UTF_8 { - str::from_utf8(&self.buff) - .map_err(|_| ReadlinesError::EncodingError)? - .to_owned() - } else { - self.encoding - .decode(&self.buff, DecoderTrap::Strict) - .map_err(|_| ReadlinesError::EncodingError)? - }; - self.buff.clear(); - Ok(Async::Ready(Some(line))) - } - Err(e) => Err(ReadlinesError::from(e)), - } - } -} - -/// Future that resolves to a complete http message body. -pub struct MessageBody { - limit: usize, - length: Option, - stream: Payload, - err: Option, - fut: Option>>, -} - -impl MessageBody -where - T: HttpMessage, - T::Stream: Stream, -{ - /// Create `MessageBody` for request. - pub fn new(req: &mut T) -> MessageBody { - let mut len = None; - if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } else { - return Self::err(PayloadError::UnknownLength); - } - } else { - return Self::err(PayloadError::UnknownLength); - } - } - - MessageBody { - stream: req.take_payload(), - limit: 262_144, - length: len, - fut: None, - err: None, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - fn err(e: PayloadError) -> Self { - MessageBody { - stream: Payload::None, - limit: 262_144, - fut: None, - err: Some(e), - length: None, - } - } -} - -impl Future for MessageBody -where - T: HttpMessage, - T::Stream: Stream + 'static, -{ - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut { - return fut.poll(); - } - - if let Some(err) = self.err.take() { - return Err(err); - } - - if let Some(len) = self.length.take() { - if len > self.limit { - return Err(PayloadError::Overflow); - } - } - - // future - let limit = self.limit; - self.fut = Some(Box::new( - std::mem::replace(&mut self.stream, Payload::None) - .from_err() - .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(PayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .map(|body| body.freeze()), - )); - self.poll() - } -} - -/// Future that resolves to a parsed urlencoded values. -pub struct UrlEncoded { - stream: Payload, - limit: usize, - length: Option, - encoding: EncodingRef, - err: Option, - fut: Option>>, -} - -impl UrlEncoded -where - T: HttpMessage, - T::Stream: Stream, -{ - /// Create a new future to URL encode a request - pub fn new(req: &mut T) -> UrlEncoded { - // check content type - if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { - return Self::err(UrlencodedError::ContentType); - } - let encoding = match req.encoding() { - Ok(enc) => enc, - Err(_) => return Self::err(UrlencodedError::ContentType), - }; - - let mut len = None; - if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } else { - return Self::err(UrlencodedError::UnknownLength); - } - } else { - return Self::err(UrlencodedError::UnknownLength); - } - }; - - UrlEncoded { - encoding, - stream: req.take_payload(), - limit: 262_144, - length: len, - fut: None, - err: None, - } - } - - fn err(e: UrlencodedError) -> Self { - UrlEncoded { - stream: Payload::None, - limit: 262_144, - fut: None, - err: Some(e), - length: None, - encoding: UTF_8, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } -} - -impl Future for UrlEncoded -where - T: HttpMessage, - T::Stream: Stream + 'static, - U: DeserializeOwned + 'static, -{ - type Item = U; - type Error = UrlencodedError; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut { - return fut.poll(); - } - - if let Some(err) = self.err.take() { - return Err(err); - } - - // payload size - let limit = self.limit; - if let Some(len) = self.length.take() { - if len > limit { - return Err(UrlencodedError::Overflow); - } - } - - // future - let encoding = self.encoding; - let fut = std::mem::replace(&mut self.stream, Payload::None) - .from_err() - .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(UrlencodedError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .and_then(move |body| { - if (encoding as *const Encoding) == UTF_8 { - serde_urlencoded::from_bytes::(&body) - .map_err(|_| UrlencodedError::Parse) - } else { - let body = encoding - .decode(&body, DecoderTrap::Strict) - .map_err(|_| UrlencodedError::Parse)?; - serde_urlencoded::from_str::(&body) - .map_err(|_| UrlencodedError::Parse) - } - }); - self.fut = Some(Box::new(fut)); - self.poll() - } -} - #[cfg(test)] mod tests { + use bytes::Bytes; use encoding::all::ISO_8859_2; use encoding::Encoding; - use futures::Async; use mime; - use serde_derive::Deserialize; use super::*; use crate::test::TestRequest; @@ -720,166 +259,4 @@ mod tests { .finish(); assert!(req.chunked().is_err()); } - - impl PartialEq for UrlencodedError { - fn eq(&self, other: &UrlencodedError) -> bool { - match *self { - UrlencodedError::Chunked => match *other { - UrlencodedError::Chunked => true, - _ => false, - }, - UrlencodedError::Overflow => match *other { - UrlencodedError::Overflow => true, - _ => false, - }, - UrlencodedError::UnknownLength => match *other { - UrlencodedError::UnknownLength => true, - _ => false, - }, - UrlencodedError::ContentType => match *other { - UrlencodedError::ContentType => true, - _ => false, - }, - _ => false, - } - } - } - - #[derive(Deserialize, Debug, PartialEq)] - struct Info { - hello: String, - } - - #[test] - fn test_urlencoded_error() { - let mut req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "xxxx") - .finish(); - assert_eq!( - req.urlencoded::().poll().err().unwrap(), - UrlencodedError::UnknownLength - ); - - let mut req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "1000000") - .finish(); - assert_eq!( - req.urlencoded::().poll().err().unwrap(), - UrlencodedError::Overflow - ); - - let mut req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain") - .header(header::CONTENT_LENGTH, "10") - .finish(); - assert_eq!( - req.urlencoded::().poll().err().unwrap(), - UrlencodedError::ContentType - ); - } - - #[test] - fn test_urlencoded() { - let mut req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - let result = req.urlencoded::().poll().ok().unwrap(); - assert_eq!( - result, - Async::Ready(Info { - hello: "world".to_owned() - }) - ); - - let mut req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded; charset=utf-8", - ) - .header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - let result = req.urlencoded().poll().ok().unwrap(); - assert_eq!( - result, - Async::Ready(Info { - hello: "world".to_owned() - }) - ); - } - - #[test] - fn test_message_body() { - let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); - match req.body().poll().err().unwrap() { - PayloadError::UnknownLength => (), - _ => unreachable!("error"), - } - - let mut req = - TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); - match req.body().poll().err().unwrap() { - PayloadError::Overflow => (), - _ => unreachable!("error"), - } - - let mut req = TestRequest::default() - .set_payload(Bytes::from_static(b"test")) - .finish(); - match req.body().poll().ok().unwrap() { - Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")), - _ => unreachable!("error"), - } - - let mut req = TestRequest::default() - .set_payload(Bytes::from_static(b"11111111111111")) - .finish(); - match req.body().limit(5).poll().err().unwrap() { - PayloadError::Overflow => (), - _ => unreachable!("error"), - } - } - - #[test] - fn test_readlines() { - let mut req = TestRequest::default() - .set_payload(Bytes::from_static( - b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ - industry. Lorem Ipsum has been the industry's standard dummy\n\ - Contrary to popular belief, Lorem Ipsum is not simply random text.", - )) - .finish(); - let mut r = Readlines::new(&mut req); - match r.poll().ok().unwrap() { - Async::Ready(Some(s)) => assert_eq!( - s, - "Lorem Ipsum is simply dummy text of the printing and typesetting\n" - ), - _ => unreachable!("error"), - } - match r.poll().ok().unwrap() { - Async::Ready(Some(s)) => assert_eq!( - s, - "industry. Lorem Ipsum has been the industry's standard dummy\n" - ), - _ => unreachable!("error"), - } - match r.poll().ok().unwrap() { - Async::Ready(Some(s)) => assert_eq!( - s, - "Contrary to popular belief, Lorem Ipsum is not simply random text." - ), - _ => unreachable!("error"), - } - } } diff --git a/src/lib.rs b/src/lib.rs index 443266a9b..9a87b77f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,24 +97,9 @@ pub use self::httpmessage::HttpMessage; pub use self::message::{Head, Message, RequestHead, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; -pub use self::response::Response; +pub use self::response::{Response, ResponseBuilder}; pub use self::service::{HttpService, SendError, SendResponse}; -pub mod dev { - //! The `actix-web` prelude for library developers - //! - //! The purpose of this module is to alleviate imports of many common actix - //! traits by adding a glob import to the top of actix heavy modules: - //! - //! ``` - //! # #![allow(unused_imports)] - //! use actix_http::dev::*; - //! ``` - - pub use crate::httpmessage::{MessageBody, Readlines, UrlEncoded}; - pub use crate::response::ResponseBuilder; -} - pub mod http { //! Various HTTP related types diff --git a/tests/test_client.rs b/tests/test_client.rs index 90e1a4f4a..2832b1b70 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -1,9 +1,11 @@ use actix_service::NewService; -use bytes::Bytes; +use bytes::{Bytes, BytesMut}; use futures::future::{self, ok}; +use futures::{Future, Stream}; -use actix_http::HttpMessage; -use actix_http::{client, HttpService, Request, Response}; +use actix_http::{ + client, error::PayloadError, HttpMessage, HttpService, Request, Response, +}; use actix_http_test::TestServer; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -28,6 +30,16 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; +fn load_body(stream: S) -> impl Future +where + S: Stream, +{ + stream.fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, PayloadError>(body) + }) +} + #[test] fn test_h1_v2() { env_logger::init(); @@ -51,7 +63,7 @@ fn test_h1_v2() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); let request = srv.post().finish().unwrap(); @@ -59,7 +71,7 @@ fn test_h1_v2() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } diff --git a/tests/test_server.rs b/tests/test_server.rs index 98f740941..8a7316cdf 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -6,16 +6,27 @@ use actix_codec::{AsyncRead, AsyncWrite}; use actix_http_test::TestServer; use actix_server_config::ServerConfig; use actix_service::{fn_cfg_factory, NewService}; -use bytes::Bytes; +use bytes::{Bytes, BytesMut}; use futures::future::{self, ok, Future}; -use futures::stream::once; +use futures::stream::{once, Stream}; use actix_http::body::Body; +use actix_http::error::PayloadError; use actix_http::{ body, client, error, http, http::header, Error, HttpMessage as HttpMessage2, HttpService, KeepAlive, Request, Response, }; +fn load_body(stream: S) -> impl Future +where + S: Stream, +{ + stream.fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, PayloadError>(body) + }) +} + #[test] fn test_h1() { let mut srv = TestServer::new(|| { @@ -131,8 +142,7 @@ fn test_h2_body() -> std::io::Result<()> { .and_then( HttpService::build() .h2(|mut req: Request<_>| { - req.body() - .limit(1024 * 1024) + load_body(req.take_payload()) .and_then(|body| Ok(Response::Ok().body(body))) }) .map_err(|_| ()), @@ -145,7 +155,7 @@ fn test_h2_body() -> std::io::Result<()> { let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); - let body = srv.block_on(response.body().limit(1024 * 1024)).unwrap(); + let body = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(&body, data.as_bytes()); Ok(()) } @@ -440,7 +450,7 @@ fn test_h1_headers() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from(data2)); } @@ -486,7 +496,7 @@ fn test_h2_headers() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from(data2)); } @@ -523,7 +533,7 @@ fn test_h1_body() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -546,7 +556,7 @@ fn test_h2_body2() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -569,7 +579,7 @@ fn test_h1_head_empty() { } // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert!(bytes.is_empty()); } @@ -601,7 +611,7 @@ fn test_h2_head_empty() { } // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert!(bytes.is_empty()); } @@ -626,7 +636,7 @@ fn test_h1_head_binary() { } // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert!(bytes.is_empty()); } @@ -661,7 +671,7 @@ fn test_h2_head_binary() { } // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert!(bytes.is_empty()); } @@ -728,7 +738,7 @@ fn test_h1_body_length() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -756,7 +766,7 @@ fn test_h2_body_length() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -787,7 +797,7 @@ fn test_h1_body_chunked_explicit() { ); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); // decode assert_eq!(bytes, Bytes::from_static(STR.as_ref())); @@ -821,7 +831,7 @@ fn test_h2_body_chunked_explicit() { assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); // decode assert_eq!(bytes, Bytes::from_static(STR.as_ref())); @@ -850,7 +860,7 @@ fn test_h1_body_chunked_implicit() { ); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -874,7 +884,7 @@ fn test_h1_response_http_error_handling() { assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert!(bytes.is_empty()); } @@ -907,7 +917,7 @@ fn test_h2_response_http_error_handling() { assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert!(bytes.is_empty()); } @@ -923,7 +933,7 @@ fn test_h1_service_error() { assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert!(bytes.is_empty()); } @@ -947,6 +957,6 @@ fn test_h2_service_error() { assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert!(bytes.is_empty()); } From 2b5f9f05118efda14a08febf386f665519596a04 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 08:50:25 -0700 Subject: [PATCH 200/208] temp fix for tarpaulin --- .travis.yml | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index 283437ce6..ff8815059 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,9 @@ matrix: include: - rust: stable - rust: beta - - rust: nightly + - rust: nightly-2019-03-02 allow_failures: - - rust: nightly + - rust: nightly-2019-03-02 env: global: @@ -25,8 +25,8 @@ before_install: - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev before_cache: | - if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f --version 0.6.7 + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin fi script: @@ -34,20 +34,19 @@ script: - cargo build --features="ssl" - cargo test --features="ssl" -after_success: | - if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then - cargo tarpaulin --features="ssl" --out Xml - bash <(curl -s https://codecov.io/bash) - echo "Uploaded code coverage" - fi - # Upload docs -#after_success: -# - | -# if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then -# cargo doc --features "session" --no-deps && -# echo "" > target/doc/index.html && -# git clone https://github.com/davisp/ghp-import.git && -# ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && -# echo "Uploaded documentation" -# fi +after_success: + - | + if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then + cargo doc --no-deps && + echo "" > target/doc/index.html && + git clone https://github.com/davisp/ghp-import.git && + ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && + echo "Uploaded documentation" + fi + - | + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then + taskset -c 0 cargo tarpaulin --features="ssl" --out Xml + bash <(curl -s https://codecov.io/bash) + echo "Uploaded code coverage" + fi From 85c2887b3086c2f207f6be4c42ec83522052fb72 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 21:09:50 -0700 Subject: [PATCH 201/208] export ws::hash_key --- src/error.rs | 43 ------------------------------------------- src/ws/mod.rs | 2 +- src/ws/proto.rs | 2 +- 3 files changed, 2 insertions(+), 45 deletions(-) diff --git a/src/error.rs b/src/error.rs index e0a416ef8..6bc401332 100644 --- a/src/error.rs +++ b/src/error.rs @@ -981,23 +981,6 @@ mod tests { from!(httparse::Error::Version => ParseError::Version); } - // #[test] - // fn failure_error() { - // const NAME: &str = "RUST_BACKTRACE"; - // let old_tb = env::var(NAME); - // env::set_var(NAME, "0"); - // let error = failure::err_msg("Hello!"); - // let resp: Error = error.into(); - // assert_eq!( - // format!("{:?}", resp), - // "Compat { error: ErrorMessage { msg: \"Hello!\" } }\n\n" - // ); - // match old_tb { - // Ok(x) => env::set_var(NAME, x), - // _ => env::remove_var(NAME), - // } - // } - #[test] fn test_internal_error() { let err = @@ -1006,32 +989,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } - // #[test] - // fn test_error_downcasting_direct() { - // #[derive(Debug, Display)] - // #[display(fmt = "demo error")] - // struct DemoError; - - // impl ResponseError for DemoError {} - - // let err: Error = DemoError.into(); - // let err_ref: &DemoError = err.downcast_ref().unwrap(); - // assert_eq!(err_ref.to_string(), "demo error"); - // } - - // #[test] - // fn test_error_downcasting_compat() { - // #[derive(Debug, Display)] - // #[display(fmt = "demo error")] - // struct DemoError; - - // impl ResponseError for DemoError {} - - // let err: Error = failure::Error::from(DemoError).into(); - // let err_ref: &DemoError = err.downcast_ref().unwrap(); - // assert_eq!(err_ref.to_string(), "demo error"); - // } - #[test] fn test_error_helpers() { let r: Response = ErrorBadRequest("err").into(); diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 8f9bc83ba..3d3f5b925 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -24,7 +24,7 @@ mod transport; pub use self::client::{Client, ClientError, Connect}; pub use self::codec::{Codec, Frame, Message}; pub use self::frame::Parser; -pub use self::proto::{CloseCode, CloseReason, OpCode}; +pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode}; pub use self::service::VerifyWebSockets; pub use self::transport::Transport; diff --git a/src/ws/proto.rs b/src/ws/proto.rs index 35fbbe3ee..eef874741 100644 --- a/src/ws/proto.rs +++ b/src/ws/proto.rs @@ -209,7 +209,7 @@ impl> From<(CloseCode, T)> for CloseReason { static WS_GUID: &'static str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // TODO: hash is always same size, we dont need String -pub(crate) fn hash_key(key: &[u8]) -> String { +pub fn hash_key(key: &[u8]) -> String { let mut hasher = sha1::Sha1::new(); hasher.update(key); From f26d4b6a23de57c7fec54476f711599f63906f27 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 21:57:53 -0700 Subject: [PATCH 202/208] do not chunk websocket stream --- src/h1/client.rs | 1 + src/h1/codec.rs | 1 + src/h1/dispatcher.rs | 10 +++++----- src/h1/encoder.rs | 3 ++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/h1/client.rs b/src/h1/client.rs index de4d10e1b..851851266 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -211,6 +211,7 @@ impl Encoder for ClientCodec { dst, &mut msg, false, + false, inner.version, length, inner.ctype, diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 23feda505..c66364c02 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -170,6 +170,7 @@ impl Encoder for Codec { dst, &mut res, self.flags.contains(Flags::HEAD), + self.flags.contains(Flags::STREAM), self.version, length, self.ctype, diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 8543aa213..82813a526 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -323,17 +323,17 @@ where match msg { Message::Item(mut req) => { match self.framed.get_codec().message_type() { - MessageType::Payload => { + MessageType::Payload | MessageType::Stream => { let (ps, pl) = Payload::create(false); let (req1, _) = req.replace_payload(crate::Payload::H1(pl)); req = req1; self.payload = Some(ps); } - MessageType::Stream => { - self.unhandled = Some(req); - return Ok(updated); - } + //MessageType::Stream => { + // self.unhandled = Some(req); + // return Ok(updated); + //} _ => (), } diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 9fe5ba69a..712d123eb 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -241,6 +241,7 @@ impl MessageEncoder { dst: &mut BytesMut, message: &mut T, head: bool, + stream: bool, version: Version, length: BodyLength, ctype: ConnectionType, @@ -253,7 +254,7 @@ impl MessageEncoder { BodyLength::Sized(len) => TransferEncoding::length(len as u64), BodyLength::Sized64(len) => TransferEncoding::length(len), BodyLength::Stream => { - if message.chunked() { + if message.chunked() && !stream { TransferEncoding::chunked() } else { TransferEncoding::eof() From 3301a46264fb5c49456b38d0b275845ec77d2784 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 22:56:13 -0700 Subject: [PATCH 203/208] proper connection upgrade check --- src/message.rs | 12 ++++++++++-- src/ws/mod.rs | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/message.rs b/src/message.rs index f9dfe9736..bf465ee6d 100644 --- a/src/message.rs +++ b/src/message.rs @@ -3,7 +3,7 @@ use std::collections::VecDeque; use std::rc::Rc; use crate::extensions::Extensions; -use crate::http::{HeaderMap, Method, StatusCode, Uri, Version}; +use crate::http::{header, HeaderMap, Method, StatusCode, Uri, Version}; /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] @@ -33,7 +33,15 @@ pub trait Head: Default + 'static { fn set_connection_type(&mut self, ctype: ConnectionType); fn upgrade(&self) -> bool { - self.connection_type() == ConnectionType::Upgrade + if let Some(hdr) = self.headers().get(header::CONNECTION) { + if let Ok(s) = hdr.to_str() { + s.to_ascii_lowercase().contains("upgrade") + } else { + false + } + } else { + false + } } /// Check if keep-alive is enabled diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 3d3f5b925..88fabde94 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -132,7 +132,7 @@ pub fn verify_handshake(req: &Request) -> Result<(), HandshakeError> { // Check for "UPGRADE" to websocket header let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) { if let Ok(s) = hdr.to_str() { - s.to_lowercase().contains("websocket") + s.to_ascii_lowercase().contains("websocket") } else { false } From fd86d73a0391a08d707aebc63074e2d88d83f330 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Mar 2019 05:26:12 -0700 Subject: [PATCH 204/208] fix response upgrade type --- src/message.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/message.rs b/src/message.rs index bf465ee6d..4e46093f6 100644 --- a/src/message.rs +++ b/src/message.rs @@ -106,6 +106,18 @@ impl Head for RequestHead { } } + fn upgrade(&self) -> bool { + if let Some(hdr) = self.headers().get(header::CONNECTION) { + if let Ok(s) = hdr.to_str() { + s.to_ascii_lowercase().contains("upgrade") + } else { + false + } + } else { + false + } + } + fn pool() -> &'static MessagePool { REQUEST_POOL.with(|p| *p) } @@ -194,6 +206,10 @@ impl Head for ResponseHead { } } + fn upgrade(&self) -> bool { + self.connection_type() == ConnectionType::Upgrade + } + fn pool() -> &'static MessagePool { RESPONSE_POOL.with(|p| *p) } From 00b7dc7887540249560f0f195f2e59df51e10f61 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Mar 2019 09:44:48 -0700 Subject: [PATCH 205/208] handle socket shutdown for h1 connections --- src/client/h2proto.rs | 6 +-- src/config.rs | 10 ++-- src/h1/dispatcher.rs | 112 ++++++++++++++++++++++------------------- src/h2/dispatcher.rs | 2 +- src/payload.rs | 2 +- src/service/service.rs | 2 +- src/ws/client/mod.rs | 24 ++++----- 7 files changed, 81 insertions(+), 77 deletions(-) diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index c05aeddbe..bf2d3e1b2 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -5,7 +5,7 @@ use bytes::Bytes; use futures::future::{err, Either}; use futures::{Async, Future, Poll}; use h2::{client::SendRequest, SendStream}; -use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; +use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING}; use http::{request::Request, HttpTryFrom, Method, Version}; use crate::body::{BodyLength, MessageBody}; @@ -45,7 +45,7 @@ where *req.version_mut() = Version::HTTP_2; let mut skip_len = true; - let mut has_date = false; + // let mut has_date = false; // Content length let _ = match length { @@ -72,7 +72,7 @@ where match *key { CONNECTION | TRANSFER_ENCODING => continue, // http2 specific CONTENT_LENGTH if skip_len => continue, - DATE => has_date = true, + // DATE => has_date = true, _ => (), } req.headers_mut().append(key, value.clone()); diff --git a/src/config.rs b/src/config.rs index 3c7df2feb..f7d7f5f94 100644 --- a/src/config.rs +++ b/src/config.rs @@ -85,7 +85,7 @@ impl ServiceConfig { ka_enabled, client_timeout, client_disconnect, - timer: DateService::with(Duration::from_millis(500)), + timer: DateService::new(), })) } @@ -204,14 +204,12 @@ impl fmt::Write for Date { struct DateService(Rc); struct DateServiceInner { - interval: Duration, current: UnsafeCell>, } impl DateServiceInner { - fn new(interval: Duration) -> Self { + fn new() -> Self { DateServiceInner { - interval, current: UnsafeCell::new(None), } } @@ -232,8 +230,8 @@ impl DateServiceInner { } impl DateService { - fn with(resolution: Duration) -> Self { - DateService(Rc::new(DateServiceInner::new(resolution))) + fn new() -> Self { + DateService(Rc::new(DateServiceInner::new())) } fn check_date(&self) { diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 82813a526..8e21e9b09 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -7,7 +7,7 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::Service; use actix_utils::cloneable::CloneableService; use bitflags::bitflags; -use futures::{try_ready, Async, Future, Poll, Sink, Stream}; +use futures::{Async, Future, Poll, Sink, Stream}; use log::{debug, error, trace}; use tokio_timer::Delay; @@ -32,6 +32,7 @@ bitflags! { const POLLED = 0b0000_1000; const SHUTDOWN = 0b0010_0000; const DISCONNECTED = 0b0100_0000; + const DROPPING = 0b1000_0000; } } @@ -56,7 +57,6 @@ where state: State, payload: Option, messages: VecDeque, - unhandled: Option, ka_expire: Instant, ka_timer: Option, @@ -131,7 +131,6 @@ where state: State::None, error: None, messages: VecDeque::new(), - unhandled: None, service, flags, config, @@ -411,8 +410,19 @@ where /// keep-alive timer fn poll_keepalive(&mut self) -> Result<(), DispatchError> { if self.ka_timer.is_none() { - return Ok(()); + // shutdown timeout + if self.flags.contains(Flags::SHUTDOWN) { + if let Some(interval) = self.config.client_disconnect_timer() { + self.ka_timer = Some(Delay::new(interval)); + } else { + self.flags.insert(Flags::DISCONNECTED); + return Ok(()); + } + } else { + return Ok(()); + } } + match self.ka_timer.as_mut().unwrap().poll().map_err(|e| { error!("Timer error {:?}", e); DispatchError::Unknown @@ -436,6 +446,8 @@ where let _ = timer.poll(); } } else { + // no shutdown timeout, drop socket + self.flags.insert(Flags::DISCONNECTED); return Ok(()); } } else { @@ -483,61 +495,55 @@ where #[inline] fn poll(&mut self) -> Poll { - let shutdown = if let Some(ref mut inner) = self.inner { - if inner.flags.contains(Flags::SHUTDOWN) { - inner.poll_keepalive()?; - try_ready!(inner.poll_flush()); - true + let inner = self.inner.as_mut().unwrap(); + + if inner.flags.contains(Flags::SHUTDOWN) { + inner.poll_keepalive()?; + if inner.flags.contains(Flags::DISCONNECTED) { + Ok(Async::Ready(())) } else { - inner.poll_keepalive()?; - inner.poll_request()?; - loop { - inner.poll_response()?; - if let Async::Ready(false) = inner.poll_flush()? { - break; - } - } - - if inner.flags.contains(Flags::DISCONNECTED) { - return Ok(Async::Ready(())); - } - - // keep-alive and stream errors - if inner.state.is_empty() && inner.framed.is_write_buf_empty() { - if let Some(err) = inner.error.take() { - return Err(err); - } - // unhandled request (upgrade or connect) - else if inner.unhandled.is_some() { - false - } - // disconnect if keep-alive is not enabled - else if inner.flags.contains(Flags::STARTED) - && !inner.flags.intersects(Flags::KEEPALIVE) - { - true - } - // disconnect if shutdown - else if inner.flags.contains(Flags::SHUTDOWN) { - true - } else { - return Ok(Async::NotReady); - } - } else { - return Ok(Async::NotReady); + // try_ready!(inner.poll_flush()); + match inner.framed.get_mut().shutdown()? { + Async::Ready(_) => Ok(Async::Ready(())), + Async::NotReady => Ok(Async::NotReady), } } } else { - unreachable!() - }; + inner.poll_keepalive()?; + inner.poll_request()?; + loop { + inner.poll_response()?; + if let Async::Ready(false) = inner.poll_flush()? { + break; + } + } - let mut inner = self.inner.take().unwrap(); + if inner.flags.contains(Flags::DISCONNECTED) { + return Ok(Async::Ready(())); + } - // TODO: shutdown - Ok(Async::Ready(())) - //Ok(Async::Ready(HttpServiceResult::Shutdown( - // inner.framed.into_inner(), - //))) + // keep-alive and stream errors + if inner.state.is_empty() && inner.framed.is_write_buf_empty() { + if let Some(err) = inner.error.take() { + return Err(err); + } + // disconnect if keep-alive is not enabled + else if inner.flags.contains(Flags::STARTED) + && !inner.flags.intersects(Flags::KEEPALIVE) + { + inner.flags.insert(Flags::SHUTDOWN); + self.poll() + } + // disconnect if shutdown + else if inner.flags.contains(Flags::SHUTDOWN) { + self.poll() + } else { + return Ok(Async::NotReady); + } + } else { + return Ok(Async::NotReady); + } + } } } diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index cbba34c0d..ea63dc2bc 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -56,7 +56,7 @@ where config: ServiceConfig, timeout: Option, ) -> Self { - let keepalive = config.keep_alive_enabled(); + // let keepalive = config.keep_alive_enabled(); // let flags = if keepalive { // Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED // } else { diff --git a/src/payload.rs b/src/payload.rs index 8f96bab95..91e6b5c95 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -41,7 +41,7 @@ impl From for Payload { impl Payload { /// Takes current payload and replaces it with `None` value - fn take(&mut self) -> Payload { + pub fn take(&mut self) -> Payload { std::mem::replace(self, Payload::None) } } diff --git a/src/service/service.rs b/src/service/service.rs index 3ddf55739..0bc1634d8 100644 --- a/src/service/service.rs +++ b/src/service/service.rs @@ -169,7 +169,7 @@ where } fn call(&mut self, req: Self::Request) -> Self::Future { - let (io, params, proto) = req.into_parts(); + let (io, _, proto) = req.into_parts(); match proto { Protocol::Http2 => { let io = Io { diff --git a/src/ws/client/mod.rs b/src/ws/client/mod.rs index 0dbf081c6..a5c221967 100644 --- a/src/ws/client/mod.rs +++ b/src/ws/client/mod.rs @@ -25,19 +25,19 @@ impl Protocol { } } - fn is_http(self) -> bool { - match self { - Protocol::Https | Protocol::Http => true, - _ => false, - } - } + // fn is_http(self) -> bool { + // match self { + // Protocol::Https | Protocol::Http => true, + // _ => false, + // } + // } - fn is_secure(self) -> bool { - match self { - Protocol::Https | Protocol::Wss => true, - _ => false, - } - } + // fn is_secure(self) -> bool { + // match self { + // Protocol::Https | Protocol::Wss => true, + // _ => false, + // } + // } fn port(self) -> u16 { match self { From c5c7b244be264154ad9b60335a88722b0139206b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 23 Mar 2019 09:40:20 -0700 Subject: [PATCH 206/208] cookie is optional --- .travis.yml | 4 +- Cargo.toml | 24 +++++------ src/client/request.rs | 37 +++++++++++------ src/error.rs | 17 +++++--- src/h1/dispatcher.rs | 3 +- src/httpmessage.rs | 11 ++++- src/lib.rs | 1 + src/response.rs | 89 ++++++++++++++++++++++++++-------------- src/test.rs | 57 +++++++++++++------------ src/ws/client/connect.rs | 2 + 10 files changed, 151 insertions(+), 94 deletions(-) diff --git a/.travis.yml b/.travis.yml index ff8815059..02fbd42c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,8 +31,8 @@ before_cache: | script: - cargo clean -- cargo build --features="ssl" -- cargo test --features="ssl" +- cargo build --all-features +- cargo test --all-features # Upload docs after_success: diff --git a/Cargo.toml b/Cargo.toml index a68489f51..84b974be5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,20 +7,20 @@ readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-http.git" -documentation = "https://actix.rs/api/actix-http/stable/actix_http/" +documentation = "https://docs.rs/actix-http/" categories = ["network-programming", "asynchronous", "web-programming::http-server", "web-programming::websocket"] -license = "Apache-2.0" +license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" [package.metadata.docs.rs] -features = ["session"] +features = ["ssl", "fail", "cookie"] [badges] travis-ci = { repository = "actix/actix-http", branch = "master" } -appveyor = { repository = "fafhrd91/actix-http-b1qsn" } +# appveyor = { repository = "fafhrd91/actix-http-b1qsn" } codecov = { repository = "actix/actix-http", branch = "master", service = "github" } [lib] @@ -28,13 +28,15 @@ name = "actix_http" path = "src/lib.rs" [features] -default = ["fail"] +default = [] # openssl ssl = ["openssl", "actix-connect/ssl"] -# failure integration. it is on by default, it will be off in future versions -# actix itself does not use failure anymore +# cookies integration +cookies = ["cookie"] + +# failure integration. actix does not use failure anymore fail = ["failure"] [dependencies] @@ -49,7 +51,6 @@ backtrace = "0.3" bitflags = "1.0" bytes = "0.4" byteorder = "1.2" -cookie = { version="0.11", features=["percent-encode"] } derive_more = "0.14" encoding = "0.2" futures = "0.1" @@ -76,11 +77,10 @@ tokio-timer = "0.2" tokio-current-thread = "0.1" trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false } -# openssl -openssl = { version="0.10", optional = true } - -# failure is optional +# optional deps +cookie = { version="0.11", features=["percent-encode"], optional = true } failure = { version = "0.1.5", optional = true } +openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.0" diff --git a/src/client/request.rs b/src/client/request.rs index 7c7079fb7..26713aa46 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -1,13 +1,12 @@ use std::fmt; -use std::fmt::Write as FmtWrite; use std::io::Write; use actix_service::Service; use bytes::{BufMut, Bytes, BytesMut}; +#[cfg(feature = "cookies")] use cookie::{Cookie, CookieJar}; use futures::future::{err, Either}; use futures::{Future, Stream}; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use serde::Serialize; use serde_json; @@ -58,6 +57,7 @@ impl ClientRequest<()> { ClientRequestBuilder { head: Some(RequestHead::default()), err: None, + #[cfg(feature = "cookies")] cookies: None, default_headers: true, } @@ -235,6 +235,7 @@ where pub struct ClientRequestBuilder { head: Option, err: Option, + #[cfg(feature = "cookies")] cookies: Option, default_headers: bool, } @@ -441,6 +442,7 @@ impl ClientRequestBuilder { self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) } + #[cfg(feature = "cookies")] /// Set a cookie /// /// ```rust @@ -560,20 +562,28 @@ impl ClientRequestBuilder { ); } + #[allow(unused_mut)] let mut head = self.head.take().expect("cannot reuse request builder"); - // set cookies - if let Some(ref mut jar) = self.cookies { - let mut cookie = String::new(); - for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); - let _ = write!(&mut cookie, "; {}={}", name, value); + #[cfg(feature = "cookies")] + { + use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; + use std::fmt::Write; + + // set cookies + if let Some(ref mut jar) = self.cookies { + let mut cookie = String::new(); + for c in jar.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = + percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); + } + head.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); } - head.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); } Ok(ClientRequest { head, body }) } @@ -646,6 +656,7 @@ impl ClientRequestBuilder { ClientRequestBuilder { head: self.head.take(), err: self.err.take(), + #[cfg(feature = "cookies")] cookies: self.cookies.take(), default_headers: self.default_headers, } diff --git a/src/error.rs b/src/error.rs index 6bc401332..820071b1e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,6 +8,7 @@ use std::{fmt, io, result}; // use actix::MailboxError; use actix_utils::timeout::TimeoutError; use backtrace::Backtrace; +#[cfg(feature = "cookies")] use cookie; use derive_more::{Display, From}; use futures::Canceled; @@ -19,7 +20,8 @@ use serde_json::error::Error as JsonError; use serde_urlencoded::ser::Error as FormError; use tokio_timer::Error as TimerError; -// re-exports +// re-export for convinience +#[cfg(feature = "cookies")] pub use cookie::ParseError as CookieParseError; use crate::body::Body; @@ -322,6 +324,7 @@ impl ResponseError for PayloadError { } /// Return `BadRequest` for `cookie::ParseError` +#[cfg(feature = "cookies")] impl ResponseError for cookie::ParseError { fn error_response(&self) -> Response { Response::new(StatusCode::BAD_REQUEST) @@ -889,7 +892,6 @@ mod failure_integration { #[cfg(test)] mod tests { use super::*; - use cookie::ParseError as CookieParseError; use http::{Error as HttpError, StatusCode}; use httparse; use std::error::Error as StdError; @@ -900,14 +902,19 @@ mod tests { let resp: Response = ParseError::Incomplete.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = CookieParseError::EmptyName.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into(); let resp: Response = err.error_response(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } + #[cfg(feature = "cookies")] + #[test] + fn test_cookie_parse() { + use cookie::ParseError as CookieParseError; + let resp: Response = CookieParseError::EmptyName.error_response(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + #[test] fn test_as_response() { let orig = io::Error::new(io::ErrorKind::Other, "other"); diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 8e21e9b09..afeabc825 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -613,13 +613,12 @@ mod tests { let mut sys = actix_rt::System::new("test"); let _ = sys.block_on(lazy(|| { let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); - let readbuf = BytesMut::new(); let mut h1 = Dispatcher::new( buf, ServiceConfig::default(), CloneableService::new( - (|req| ok::<_, Error>(Response::Ok().finish())).into_service(), + (|_| ok::<_, Error>(Response::Ok().finish())).into_service(), ), ); assert!(h1.poll().is_ok()); diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 117e10a81..60821d300 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,18 +1,23 @@ use std::cell::{Ref, RefMut}; use std::str; -use cookie::Cookie; use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; use encoding::EncodingRef; use http::{header, HeaderMap}; use mime::Mime; -use crate::error::{ContentTypeError, CookieParseError, ParseError}; +use crate::error::{ContentTypeError, ParseError}; use crate::extensions::Extensions; use crate::header::Header; use crate::payload::Payload; +#[cfg(feature = "cookies")] +use crate::error::CookieParseError; +#[cfg(feature = "cookies")] +use cookie::Cookie; + +#[cfg(feature = "cookies")] struct Cookies(Vec>); /// Trait that implements general purpose operations on http messages @@ -105,6 +110,7 @@ pub trait HttpMessage: Sized { /// Load request cookies. #[inline] + #[cfg(feature = "cookies")] fn cookies(&self) -> Result>>, CookieParseError> { if self.extensions().get::().is_none() { let mut cookies = Vec::new(); @@ -125,6 +131,7 @@ pub trait HttpMessage: Sized { } /// Return request cookie. + #[cfg(feature = "cookies")] fn cookie(&self, name: &str) -> Option> { if let Ok(cookies) = self.cookies() { for cookie in cookies.iter() { diff --git a/src/lib.rs b/src/lib.rs index 9a87b77f1..85efe5e44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -113,6 +113,7 @@ pub mod http { #[doc(hidden)] pub use http::uri::PathAndQuery; + #[cfg(feature = "cookies")] pub use cookie::{Cookie, CookieBuilder}; /// Various http headers diff --git a/src/response.rs b/src/response.rs index 34ac54ea7..5281c2d95 100644 --- a/src/response.rs +++ b/src/response.rs @@ -3,6 +3,7 @@ use std::io::Write; use std::{fmt, str}; use bytes::{BufMut, Bytes, BytesMut}; +#[cfg(feature = "cookies")] use cookie::{Cookie, CookieJar}; use futures::future::{ok, FutureResult, IntoFuture}; use futures::Stream; @@ -128,6 +129,7 @@ impl Response { /// Get an iterator for the cookies set by this response #[inline] + #[cfg(feature = "cookies")] pub fn cookies(&self) -> CookieIter { CookieIter { iter: self.head.headers.get_all(header::SET_COOKIE).iter(), @@ -136,6 +138,7 @@ impl Response { /// Add a cookie to this response #[inline] + #[cfg(feature = "cookies")] pub fn add_cookie(&mut self, cookie: &Cookie) -> Result<(), HttpError> { let h = &mut self.head.headers; HeaderValue::from_str(&cookie.to_string()) @@ -148,6 +151,7 @@ impl Response { /// Remove all cookies with the given name from this response. Returns /// the number of cookies removed. #[inline] + #[cfg(feature = "cookies")] pub fn del_cookie(&mut self, name: &str) -> usize { let h = &mut self.head.headers; let vals: Vec = h @@ -267,10 +271,12 @@ impl IntoFuture for Response { } } +#[cfg(feature = "cookies")] pub struct CookieIter<'a> { iter: header::ValueIter<'a, HeaderValue>, } +#[cfg(feature = "cookies")] impl<'a> Iterator for CookieIter<'a> { type Item = Cookie<'a>; @@ -292,6 +298,7 @@ impl<'a> Iterator for CookieIter<'a> { pub struct ResponseBuilder { head: Option>, err: Option, + #[cfg(feature = "cookies")] cookies: Option, } @@ -304,6 +311,7 @@ impl ResponseBuilder { ResponseBuilder { head: Some(head), err: None, + #[cfg(feature = "cookies")] cookies: None, } } @@ -503,6 +511,7 @@ impl ResponseBuilder { /// .finish() /// } /// ``` + #[cfg(feature = "cookies")] pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); @@ -530,6 +539,7 @@ impl ResponseBuilder { /// builder.finish() /// } /// ``` + #[cfg(feature = "cookies")] pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self { { if self.cookies.is_none() { @@ -582,15 +592,20 @@ impl ResponseBuilder { return Response::from(Error::from(e)).into_body(); } + #[allow(unused_mut)] let mut response = self.head.take().expect("cannot reuse response builder"); - if let Some(ref jar) = self.cookies { - for cookie in jar.delta() { - match HeaderValue::from_str(&cookie.to_string()) { - Ok(val) => { - let _ = response.headers.append(header::SET_COOKIE, val); - } - Err(e) => return Response::from(Error::from(e)).into_body(), - }; + + #[cfg(feature = "cookies")] + { + if let Some(ref jar) = self.cookies { + for cookie in jar.delta() { + match HeaderValue::from_str(&cookie.to_string()) { + Ok(val) => { + let _ = response.headers.append(header::SET_COOKIE, val); + } + Err(e) => return Response::from(Error::from(e)).into_body(), + }; + } } } @@ -654,6 +669,7 @@ impl ResponseBuilder { ResponseBuilder { head: self.head.take(), err: self.err.take(), + #[cfg(feature = "cookies")] cookies: self.cookies.take(), } } @@ -674,7 +690,9 @@ fn parts<'a>( impl From> for ResponseBuilder { fn from(res: Response) -> ResponseBuilder { // If this response has cookies, load them into a jar + #[cfg(feature = "cookies")] let mut jar: Option = None; + #[cfg(feature = "cookies")] for c in res.cookies() { if let Some(ref mut j) = jar { j.add_original(c.into_owned()); @@ -688,6 +706,7 @@ impl From> for ResponseBuilder { ResponseBuilder { head: Some(res.head), err: None, + #[cfg(feature = "cookies")] cookies: jar, } } @@ -697,17 +716,22 @@ impl From> for ResponseBuilder { impl<'a> From<&'a ResponseHead> for ResponseBuilder { fn from(head: &'a ResponseHead) -> ResponseBuilder { // If this response has cookies, load them into a jar + #[cfg(feature = "cookies")] let mut jar: Option = None; - let cookies = CookieIter { - iter: head.headers.get_all(header::SET_COOKIE).iter(), - }; - for c in cookies { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); + + #[cfg(feature = "cookies")] + { + let cookies = CookieIter { + iter: head.headers.get_all(header::SET_COOKIE).iter(), + }; + for c in cookies { + if let Some(ref mut j) = jar { + j.add_original(c.into_owned()); + } else { + let mut j = CookieJar::new(); + j.add_original(c.into_owned()); + jar = Some(j); + } } } @@ -721,6 +745,7 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { ResponseBuilder { head: Some(msg), err: None, + #[cfg(feature = "cookies")] cookies: jar, } } @@ -802,14 +827,9 @@ impl From for Response { #[cfg(test)] mod tests { - use time::Duration; - use super::*; use crate::body::Body; - use crate::http; use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; - use crate::httpmessage::HttpMessage; - use crate::test::TestRequest; #[test] fn test_debug() { @@ -822,8 +842,11 @@ mod tests { } #[test] + #[cfg(feature = "cookies")] fn test_response_cookies() { - let req = TestRequest::default() + use crate::httpmessage::HttpMessage; + + let req = crate::test::TestRequest::default() .header(COOKIE, "cookie1=value1") .header(COOKIE, "cookie2=value2") .finish(); @@ -831,11 +854,11 @@ mod tests { let resp = Response::Ok() .cookie( - http::Cookie::build("name", "value") + crate::http::Cookie::build("name", "value") .domain("www.rust-lang.org") .path("/test") .http_only(true) - .max_age(Duration::days(1)) + .max_age(time::Duration::days(1)) .finish(), ) .del_cookie(&cookies[0]) @@ -856,16 +879,17 @@ mod tests { } #[test] + #[cfg(feature = "cookies")] fn test_update_response_cookies() { let mut r = Response::Ok() - .cookie(http::Cookie::new("original", "val100")) + .cookie(crate::http::Cookie::new("original", "val100")) .finish(); - r.add_cookie(&http::Cookie::new("cookie2", "val200")) + r.add_cookie(&crate::http::Cookie::new("cookie2", "val200")) .unwrap(); - r.add_cookie(&http::Cookie::new("cookie2", "val250")) + r.add_cookie(&crate::http::Cookie::new("cookie2", "val250")) .unwrap(); - r.add_cookie(&http::Cookie::new("cookie3", "val300")) + r.add_cookie(&crate::http::Cookie::new("cookie3", "val300")) .unwrap(); assert_eq!(r.cookies().count(), 4); @@ -1016,11 +1040,14 @@ mod tests { } #[test] + #[cfg(feature = "cookies")] fn test_into_builder() { + use crate::httpmessage::HttpMessage; + let mut resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); - resp.add_cookie(&http::Cookie::new("cookie1", "val100")) + resp.add_cookie(&crate::http::Cookie::new("cookie1", "val100")) .unwrap(); let mut builder: ResponseBuilder = resp.into(); diff --git a/src/test.rs b/src/test.rs index c60d2d01c..2d4b3d0f9 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,12 +1,11 @@ //! Test Various helpers for Actix applications to use during testing. -use std::fmt::Write as FmtWrite; use std::str::FromStr; use bytes::Bytes; +#[cfg(feature = "cookies")] use cookie::{Cookie, CookieJar}; -use http::header::{self, HeaderName, HeaderValue}; +use http::header::HeaderName; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use crate::header::{Header, IntoHeaderValue}; use crate::payload::Payload; @@ -46,6 +45,7 @@ struct Inner { method: Method, uri: Uri, headers: HeaderMap, + #[cfg(feature = "cookies")] cookies: CookieJar, payload: Option, } @@ -57,6 +57,7 @@ impl Default for TestRequest { uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, headers: HeaderMap::new(), + #[cfg(feature = "cookies")] cookies: CookieJar::new(), payload: None, })) @@ -126,6 +127,7 @@ impl TestRequest { } /// Set cookie for this request + #[cfg(feature = "cookies")] pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { parts(&mut self.0).cookies.add(cookie.into_owned()); self @@ -145,38 +147,39 @@ impl TestRequest { /// Complete request creation and generate `Request` instance pub fn finish(&mut self) -> Request { - let Inner { - method, - uri, - version, - headers, - payload, - cookies, - } = self.0.take().expect("cannot reuse test request builder");; + let inner = self.0.take().expect("cannot reuse test request builder");; - let mut req = if let Some(pl) = payload { + let mut req = if let Some(pl) = inner.payload { Request::with_payload(pl) } else { Request::with_payload(crate::h1::Payload::empty().into()) }; let head = req.head_mut(); - head.uri = uri; - head.method = method; - head.version = version; - head.headers = headers; + head.uri = inner.uri; + head.method = inner.method; + head.version = inner.version; + head.headers = inner.headers; - let mut cookie = String::new(); - for c in cookies.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - if !cookie.is_empty() { - head.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); + #[cfg(feature = "cookies")] + { + use std::fmt::Write as FmtWrite; + + use http::header::{self, HeaderValue}; + use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; + + let mut cookie = String::new(); + for c in inner.cookies.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); + } + if !cookie.is_empty() { + head.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); + } } req diff --git a/src/ws/client/connect.rs b/src/ws/client/connect.rs index 09d025631..5e877a645 100644 --- a/src/ws/client/connect.rs +++ b/src/ws/client/connect.rs @@ -1,6 +1,7 @@ //! Http client request use std::str; +#[cfg(feature = "cookies")] use cookie::Cookie; use http::header::{HeaderName, HeaderValue}; use http::{Error as HttpError, HttpTryFrom}; @@ -50,6 +51,7 @@ impl Connect { self } + #[cfg(feature = "cookies")] /// Set cookie for handshake request pub fn cookie(mut self, cookie: Cookie) -> Self { self.request.cookie(cookie); From 9037473e0fc60b668cfb2b68beeed43d1a8891a0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Mar 2019 21:52:45 -0700 Subject: [PATCH 207/208] update client error --- Cargo.toml | 2 +- src/body.rs | 10 ++++++++++ src/client/connection.rs | 2 +- src/client/error.rs | 4 ++++ src/client/request.rs | 11 ++++++++--- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 84b974be5..da11a5853 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ failure = { version = "0.1.5", optional = true } openssl = { version="0.10", optional = true } [dev-dependencies] -actix-rt = "0.2.0" +actix-rt = "0.2.1" actix-server = { version = "0.4.0", features=["ssl"] } actix-connect = { version = "0.1.0", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } diff --git a/src/body.rs b/src/body.rs index b7e8ec98a..e1399e6b4 100644 --- a/src/body.rs +++ b/src/body.rs @@ -45,6 +45,16 @@ impl MessageBody for () { } } +impl MessageBody for Box { + fn length(&self) -> BodyLength { + self.as_ref().length() + } + + fn poll_next(&mut self) -> Poll, Error> { + self.as_mut().poll_next() + } +} + pub enum ResponseBody { Body(B), Other(Body), diff --git a/src/client/connection.rs b/src/client/connection.rs index 683738e28..8de23bd24 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -21,7 +21,7 @@ pub(crate) enum ConnectionType { pub trait Connection { type Future: Future; - /// Close connection + /// Send request and body fn send_request( self, head: RequestHead, diff --git a/src/client/error.rs b/src/client/error.rs index 4fce904f1..69ec49585 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -7,6 +7,7 @@ use trust_dns_resolver::error::ResolveError; use openssl::ssl::{Error as SslError, HandshakeError}; use crate::error::{Error, ParseError, ResponseError}; +use crate::http::Error as HttpError; use crate::response::Response; /// A set of errors that can occur while connecting to an HTTP host @@ -98,6 +99,9 @@ pub enum SendRequestError { Send(io::Error), /// Error parsing response Response(ParseError), + /// Http error + #[display(fmt = "{}", _0)] + Http(HttpError), /// Http2 error #[display(fmt = "{}", _0)] H2(h2::Error), diff --git a/src/client/request.rs b/src/client/request.rs index 26713aa46..134a42640 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -118,6 +118,11 @@ impl ClientRequest where B: MessageBody, { + /// Create new client request + pub fn new(head: RequestHead, body: B) -> Self { + ClientRequest { head, body } + } + /// Get the request URI #[inline] pub fn uri(&self) -> &Uri { @@ -174,14 +179,14 @@ where // Send request /// /// This method returns a future that resolves to a ClientResponse - pub fn send( + pub fn send( self, connector: &mut T, ) -> impl Future where B: 'static, - T: Service, - I: Connection, + T: Service, + T::Response: Connection, { let Self { head, body } = self; From 2c7da28ef9fab2265c8149daedfdfc60b7ba3641 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Mar 2019 11:43:22 -0700 Subject: [PATCH 208/208] move high level client code to awc crate --- src/client/connection.rs | 10 +- src/client/h1proto.rs | 38 +-- src/client/h2proto.rs | 9 +- src/client/mod.rs | 4 - src/client/request.rs | 699 --------------------------------------- src/client/response.rs | 116 ------- src/h1/client.rs | 9 +- src/h1/decoder.rs | 15 +- src/h1/dispatcher.rs | 2 +- src/lib.rs | 62 +--- src/response.rs | 2 - src/ws/client/connect.rs | 37 ++- src/ws/client/service.rs | 50 +-- test-server/Cargo.toml | 3 +- test-server/src/lib.rs | 121 ++++--- tests/test_client.rs | 31 +- tests/test_server.rs | 129 +++----- 17 files changed, 211 insertions(+), 1126 deletions(-) delete mode 100644 src/client/request.rs delete mode 100644 src/client/response.rs diff --git a/src/client/connection.rs b/src/client/connection.rs index 8de23bd24..e8c1201aa 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -6,11 +6,11 @@ use futures::Future; use h2::client::SendRequest; use crate::body::MessageBody; -use crate::message::RequestHead; +use crate::message::{RequestHead, ResponseHead}; +use crate::payload::Payload; use super::error::SendRequestError; use super::pool::Acquired; -use super::response::ClientResponse; use super::{h1proto, h2proto}; pub(crate) enum ConnectionType { @@ -19,7 +19,7 @@ pub(crate) enum ConnectionType { } pub trait Connection { - type Future: Future; + type Future: Future; /// Send request and body fn send_request( @@ -80,7 +80,7 @@ impl Connection for IoConnection where T: AsyncRead + AsyncWrite + 'static, { - type Future = Box>; + type Future = Box>; fn send_request( mut self, @@ -117,7 +117,7 @@ where A: AsyncRead + AsyncWrite + 'static, B: AsyncRead + AsyncWrite + 'static, { - type Future = Box>; + type Future = Box>; fn send_request( self, diff --git a/src/client/h1proto.rs b/src/client/h1proto.rs index 34521cc2f..2e29484ff 100644 --- a/src/client/h1proto.rs +++ b/src/client/h1proto.rs @@ -2,17 +2,18 @@ use std::{io, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use bytes::Bytes; -use futures::future::{err, ok, Either}; +use futures::future::{ok, Either}; use futures::{Async, Future, Poll, Sink, Stream}; +use crate::error::PayloadError; +use crate::h1; +use crate::message::{RequestHead, ResponseHead}; +use crate::payload::{Payload, PayloadStream}; + use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; use super::error::{ConnectError, SendRequestError}; use super::pool::Acquired; -use super::response::ClientResponse; use crate::body::{BodyLength, MessageBody}; -use crate::error::PayloadError; -use crate::h1; -use crate::message::RequestHead; pub(crate) fn send_request( io: T, @@ -20,7 +21,7 @@ pub(crate) fn send_request( body: B, created: time::Instant, pool: Option>, -) -> impl Future +) -> impl Future where T: AsyncRead + AsyncWrite + 'static, B: MessageBody, @@ -50,19 +51,20 @@ where .into_future() .map_err(|(e, _)| SendRequestError::from(e)) .and_then(|(item, framed)| { - if let Some(mut res) = item { + if let Some(res) = item { match framed.get_codec().message_type() { h1::MessageType::None => { let force_close = !framed.get_codec().keepalive(); - release_connection(framed, force_close) + release_connection(framed, force_close); + Ok((res, Payload::None)) } _ => { - res.set_payload(Payload::stream(framed).into()); + let pl: PayloadStream = Box::new(PlStream::new(framed)); + Ok((res, pl.into())) } } - ok(res) } else { - err(ConnectError::Disconnected.into()) + Err(ConnectError::Disconnected.into()) } }) }) @@ -199,21 +201,19 @@ where } } -pub(crate) struct Payload { +pub(crate) struct PlStream { framed: Option>, } -impl Payload { - pub fn stream( - framed: Framed, - ) -> Box> { - Box::new(Payload { +impl PlStream { + fn new(framed: Framed) -> Self { + PlStream { framed: Some(framed.map_codec(|codec| codec.into_payload_codec())), - }) + } } } -impl Stream for Payload { +impl Stream for PlStream { type Item = Bytes; type Error = PayloadError; diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index bf2d3e1b2..9ad722627 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -9,13 +9,12 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING}; use http::{request::Request, HttpTryFrom, Method, Version}; use crate::body::{BodyLength, MessageBody}; -use crate::message::{Message, RequestHead, ResponseHead}; +use crate::message::{RequestHead, ResponseHead}; use crate::payload::Payload; use super::connection::{ConnectionType, IoConnection}; use super::error::SendRequestError; use super::pool::Acquired; -use super::response::ClientResponse; pub(crate) fn send_request( io: SendRequest, @@ -23,7 +22,7 @@ pub(crate) fn send_request( body: B, created: time::Instant, pool: Option>, -) -> impl Future +) -> impl Future where T: AsyncRead + AsyncWrite + 'static, B: MessageBody, @@ -105,12 +104,12 @@ where let (parts, body) = resp.into_parts(); let payload = if head_req { Payload::None } else { body.into() }; - let mut head: Message = Message::new(); + let mut head = ResponseHead::default(); head.version = parts.version; head.status = parts.status; head.headers = parts.headers; - Ok(ClientResponse { head, payload }) + Ok((head, payload)) }) .from_err() } diff --git a/src/client/mod.rs b/src/client/mod.rs index 86b1a0cc0..87c374742 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -5,11 +5,7 @@ mod error; mod h1proto; mod h2proto; mod pool; -mod request; -mod response; pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectError, InvalidUrl, SendRequestError}; -pub use self::request::{ClientRequest, ClientRequestBuilder}; -pub use self::response::ClientResponse; diff --git a/src/client/request.rs b/src/client/request.rs deleted file mode 100644 index 134a42640..000000000 --- a/src/client/request.rs +++ /dev/null @@ -1,699 +0,0 @@ -use std::fmt; -use std::io::Write; - -use actix_service::Service; -use bytes::{BufMut, Bytes, BytesMut}; -#[cfg(feature = "cookies")] -use cookie::{Cookie, CookieJar}; -use futures::future::{err, Either}; -use futures::{Future, Stream}; -use serde::Serialize; -use serde_json; - -use crate::body::{BodyStream, MessageBody}; -use crate::error::Error; -use crate::header::{self, Header, IntoHeaderValue}; -use crate::http::{ - uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, - Uri, Version, -}; -use crate::message::{ConnectionType, Head, RequestHead}; - -use super::connection::Connection; -use super::error::{ConnectError, InvalidUrl, SendRequestError}; -use super::response::ClientResponse; - -/// An HTTP Client Request -/// -/// ```rust -/// use futures::future::{Future, lazy}; -/// use actix_rt::System; -/// use actix_http::client; -/// -/// fn main() { -/// System::new("test").block_on(lazy(|| { -/// let mut connector = client::Connector::new().service(); -/// -/// client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") -/// .finish().unwrap() -/// .send(&mut connector) // <- Send http request -/// .map_err(|_| ()) -/// .and_then(|response| { // <- server http response -/// println!("Response: {:?}", response); -/// Ok(()) -/// }) -/// })); -/// } -/// ``` -pub struct ClientRequest { - head: RequestHead, - body: B, -} - -impl ClientRequest<()> { - /// Create client request builder - pub fn build() -> ClientRequestBuilder { - ClientRequestBuilder { - head: Some(RequestHead::default()), - err: None, - #[cfg(feature = "cookies")] - cookies: None, - default_headers: true, - } - } - - /// Create request builder for `GET` request - pub fn get(uri: U) -> ClientRequestBuilder - where - Uri: HttpTryFrom, - { - let mut builder = ClientRequest::build(); - builder.method(Method::GET).uri(uri); - builder - } - - /// Create request builder for `HEAD` request - pub fn head(uri: U) -> ClientRequestBuilder - where - Uri: HttpTryFrom, - { - let mut builder = ClientRequest::build(); - builder.method(Method::HEAD).uri(uri); - builder - } - - /// Create request builder for `POST` request - pub fn post(uri: U) -> ClientRequestBuilder - where - Uri: HttpTryFrom, - { - let mut builder = ClientRequest::build(); - builder.method(Method::POST).uri(uri); - builder - } - - /// Create request builder for `PUT` request - pub fn put(uri: U) -> ClientRequestBuilder - where - Uri: HttpTryFrom, - { - let mut builder = ClientRequest::build(); - builder.method(Method::PUT).uri(uri); - builder - } - - /// Create request builder for `DELETE` request - pub fn delete(uri: U) -> ClientRequestBuilder - where - Uri: HttpTryFrom, - { - let mut builder = ClientRequest::build(); - builder.method(Method::DELETE).uri(uri); - builder - } -} - -impl ClientRequest -where - B: MessageBody, -{ - /// Create new client request - pub fn new(head: RequestHead, body: B) -> Self { - ClientRequest { head, body } - } - - /// Get the request URI - #[inline] - pub fn uri(&self) -> &Uri { - &self.head.uri - } - - /// Set client request URI - #[inline] - pub fn set_uri(&mut self, uri: Uri) { - self.head.uri = uri - } - - /// Get the request method - #[inline] - pub fn method(&self) -> &Method { - &self.head.method - } - - /// Set HTTP `Method` for the request - #[inline] - pub fn set_method(&mut self, method: Method) { - self.head.method = method - } - - /// Get HTTP version for the request - #[inline] - pub fn version(&self) -> Version { - self.head.version - } - - /// Set http `Version` for the request - #[inline] - pub fn set_version(&mut self, version: Version) { - self.head.version = version - } - - /// Get the headers from the request - #[inline] - pub fn headers(&self) -> &HeaderMap { - &self.head.headers - } - - /// Get a mutable reference to the headers - #[inline] - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head.headers - } - - /// Deconstruct ClientRequest to a RequestHead and body tuple - pub fn into_parts(self) -> (RequestHead, B) { - (self.head, self.body) - } - - // Send request - /// - /// This method returns a future that resolves to a ClientResponse - pub fn send( - self, - connector: &mut T, - ) -> impl Future - where - B: 'static, - T: Service, - T::Response: Connection, - { - let Self { head, body } = self; - - let uri = head.uri.clone(); - - // validate uri - if uri.host().is_none() { - Either::A(err(InvalidUrl::MissingHost.into())) - } else if uri.scheme_part().is_none() { - Either::A(err(InvalidUrl::MissingScheme.into())) - } else if let Some(scheme) = uri.scheme_part() { - match scheme.as_str() { - "http" | "ws" | "https" | "wss" => Either::B( - connector - // connect to the host - .call(uri) - .from_err() - // send request - .and_then(move |connection| connection.send_request(head, body)), - ), - _ => Either::A(err(InvalidUrl::UnknownScheme.into())), - } - } else { - Either::A(err(InvalidUrl::UnknownScheme.into())) - } - } -} - -impl fmt::Debug for ClientRequest -where - B: MessageBody, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!( - f, - "\nClientRequest {:?} {}:{}", - self.head.version, self.head.method, self.head.uri - )?; - writeln!(f, " headers:")?; - for (key, val) in self.head.headers.iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -/// An HTTP Client request builder -/// -/// This type can be used to construct an instance of `ClientRequest` through a -/// builder-like pattern. -pub struct ClientRequestBuilder { - head: Option, - err: Option, - #[cfg(feature = "cookies")] - cookies: Option, - default_headers: bool, -} - -impl ClientRequestBuilder { - /// Set HTTP URI of request. - #[inline] - pub fn uri(&mut self, uri: U) -> &mut Self - where - Uri: HttpTryFrom, - { - match Uri::try_from(uri) { - Ok(uri) => { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.uri = uri; - } - } - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Set HTTP method of this request. - #[inline] - pub fn method(&mut self, method: Method) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.method = method; - } - self - } - - /// Set HTTP method of this request. - #[inline] - pub fn get_method(&mut self) -> &Method { - let parts = self.head.as_ref().expect("cannot reuse request builder"); - &parts.method - } - - /// Set HTTP version of this request. - /// - /// By default requests's HTTP version depends on network stream - #[inline] - pub fn version(&mut self, version: Version) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.version = version; - } - self - } - - /// Set a header. - /// - /// ```rust - /// # extern crate mime; - /// # extern crate actix_http; - /// # - /// use actix_http::{client, http}; - /// - /// fn main() { - /// let req = client::ClientRequest::build() - /// .set(http::header::Date::now()) - /// .set(http::header::ContentType(mime::TEXT_HTML)) - /// .finish() - /// .unwrap(); - /// } - /// ``` - pub fn set(&mut self, hdr: H) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - match hdr.try_into() { - Ok(value) => { - parts.headers.insert(H::name(), value); - } - Err(e) => self.err = Some(e.into()), - } - } - self - } - - /// Append a header. - /// - /// Header gets appended to existing header. - /// To override header use `set_header()` method. - /// - /// ```rust - /// # extern crate actix_http; - /// # - /// use actix_http::{client, http}; - /// - /// fn main() { - /// let req = client::ClientRequest::build() - /// .header("X-TEST", "value") - /// .header(http::header::CONTENT_TYPE, "application/json") - /// .finish() - /// .unwrap(); - /// } - /// ``` - pub fn header(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.append(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set a header. - pub fn set_header(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set a header only if it is not yet set. - pub fn set_header_if_none(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => { - if !parts.headers.contains_key(&key) { - match value.try_into() { - Ok(value) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - } - } - } - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Enable connection upgrade - #[inline] - pub fn upgrade(&mut self, value: V) -> &mut Self - where - V: IntoHeaderValue, - { - { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_connection_type(ConnectionType::Upgrade); - } - } - self.set_header(header::UPGRADE, value) - } - - /// Close connection - #[inline] - pub fn close(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_connection_type(ConnectionType::Close); - } - self - } - - /// Set request's content type - #[inline] - pub fn content_type(&mut self, value: V) -> &mut Self - where - HeaderValue: HttpTryFrom, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match HeaderValue::try_from(value) { - Ok(value) => { - parts.headers.insert(header::CONTENT_TYPE, value); - } - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set content length - #[inline] - pub fn content_length(&mut self, len: u64) -> &mut Self { - let mut wrt = BytesMut::new().writer(); - let _ = write!(wrt, "{}", len); - self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) - } - - #[cfg(feature = "cookies")] - /// Set a cookie - /// - /// ```rust - /// # extern crate actix_http; - /// use actix_http::{client, http}; - /// - /// fn main() { - /// let req = client::ClientRequest::build() - /// .cookie( - /// http::Cookie::build("name", "value") - /// .domain("www.rust-lang.org") - /// .path("/") - /// .secure(true) - /// .http_only(true) - /// .finish(), - /// ) - /// .finish() - /// .unwrap(); - /// } - /// ``` - pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { - if self.cookies.is_none() { - let mut jar = CookieJar::new(); - jar.add(cookie.into_owned()); - self.cookies = Some(jar) - } else { - self.cookies.as_mut().unwrap().add(cookie.into_owned()); - } - self - } - - /// Do not add default request headers. - /// By default `Accept-Encoding` and `User-Agent` headers are set. - pub fn no_default_headers(&mut self) -> &mut Self { - self.default_headers = false; - self - } - - /// This method calls provided closure with builder reference if - /// value is `true`. - pub fn if_true(&mut self, value: bool, f: F) -> &mut Self - where - F: FnOnce(&mut ClientRequestBuilder), - { - if value { - f(self); - } - self - } - - /// This method calls provided closure with builder reference if - /// value is `Some`. - pub fn if_some(&mut self, value: Option, f: F) -> &mut Self - where - F: FnOnce(T, &mut ClientRequestBuilder), - { - if let Some(val) = value { - f(val, self); - } - self - } - - /// Set a body and generate `ClientRequest`. - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn body( - &mut self, - body: B, - ) -> Result, HttpError> { - if let Some(e) = self.err.take() { - return Err(e); - } - - if self.default_headers { - // enable br only for https - let https = if let Some(parts) = parts(&mut self.head, &self.err) { - parts - .uri - .scheme_part() - .map(|s| s == &uri::Scheme::HTTPS) - .unwrap_or(true) - } else { - true - }; - - if https { - self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate"); - } else { - self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate"); - } - - // set request host header - if let Some(parts) = parts(&mut self.head, &self.err) { - if let Some(host) = parts.uri.host() { - if !parts.headers.contains_key(header::HOST) { - let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - - let _ = match parts.uri.port_u16() { - None | Some(80) | Some(443) => write!(wrt, "{}", host), - Some(port) => write!(wrt, "{}:{}", host, port), - }; - - match wrt.get_mut().take().freeze().try_into() { - Ok(value) => { - parts.headers.insert(header::HOST, value); - } - Err(e) => self.err = Some(e.into()), - } - } - } - } - - // user agent - self.set_header_if_none( - header::USER_AGENT, - concat!("actix-http/", env!("CARGO_PKG_VERSION")), - ); - } - - #[allow(unused_mut)] - let mut head = self.head.take().expect("cannot reuse request builder"); - - #[cfg(feature = "cookies")] - { - use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; - use std::fmt::Write; - - // set cookies - if let Some(ref mut jar) = self.cookies { - let mut cookie = String::new(); - for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = - percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - head.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); - } - } - Ok(ClientRequest { head, body }) - } - - /// Set a JSON body and generate `ClientRequest` - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn json( - &mut self, - value: T, - ) -> Result, Error> { - let body = serde_json::to_string(&value)?; - - let contains = if let Some(head) = parts(&mut self.head, &self.err) { - head.headers.contains_key(header::CONTENT_TYPE) - } else { - true - }; - if !contains { - self.header(header::CONTENT_TYPE, "application/json"); - } - - Ok(self.body(body)?) - } - - /// Set a urlencoded body and generate `ClientRequest` - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn form( - &mut self, - value: T, - ) -> Result, Error> { - let body = serde_urlencoded::to_string(&value)?; - - let contains = if let Some(head) = parts(&mut self.head, &self.err) { - head.headers.contains_key(header::CONTENT_TYPE) - } else { - true - }; - if !contains { - self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded"); - } - - Ok(self.body(body)?) - } - - /// Set an streaming body and generate `ClientRequest`. - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn stream( - &mut self, - stream: S, - ) -> Result, HttpError> - where - S: Stream, - E: Into + 'static, - { - self.body(BodyStream::new(stream)) - } - - /// Set an empty body and generate `ClientRequest`. - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn finish(&mut self) -> Result, HttpError> { - self.body(()) - } - - /// This method construct new `ClientRequestBuilder` - pub fn take(&mut self) -> ClientRequestBuilder { - ClientRequestBuilder { - head: self.head.take(), - err: self.err.take(), - #[cfg(feature = "cookies")] - cookies: self.cookies.take(), - default_headers: self.default_headers, - } - } -} - -#[inline] -fn parts<'a>( - parts: &'a mut Option, - err: &Option, -) -> Option<&'a mut RequestHead> { - if err.is_some() { - return None; - } - parts.as_mut() -} - -impl fmt::Debug for ClientRequestBuilder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(ref parts) = self.head { - writeln!( - f, - "\nClientRequestBuilder {:?} {}:{}", - parts.version, parts.method, parts.uri - )?; - writeln!(f, " headers:")?; - for (key, val) in parts.headers.iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } else { - write!(f, "ClientRequestBuilder(Consumed)") - } - } -} diff --git a/src/client/response.rs b/src/client/response.rs deleted file mode 100644 index 7c6cdf643..000000000 --- a/src/client/response.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::cell::{Ref, RefMut}; -use std::fmt; - -use bytes::Bytes; -use futures::{Poll, Stream}; -use http::{HeaderMap, StatusCode, Version}; - -use crate::error::PayloadError; -use crate::extensions::Extensions; -use crate::httpmessage::HttpMessage; -use crate::message::{Head, Message, ResponseHead}; -use crate::payload::{Payload, PayloadStream}; - -/// Client Response -pub struct ClientResponse { - pub(crate) head: Message, - pub(crate) payload: Payload, -} - -impl HttpMessage for ClientResponse { - type Stream = PayloadStream; - - fn headers(&self) -> &HeaderMap { - &self.head.headers - } - - fn extensions(&self) -> Ref { - self.head.extensions() - } - - fn extensions_mut(&self) -> RefMut { - self.head.extensions_mut() - } - - fn take_payload(&mut self) -> Payload { - std::mem::replace(&mut self.payload, Payload::None) - } -} - -impl ClientResponse { - /// Create new Request instance - pub fn new() -> ClientResponse { - let head: Message = Message::new(); - head.extensions_mut().clear(); - - ClientResponse { - head, - payload: Payload::None, - } - } - - #[inline] - pub(crate) fn head(&self) -> &ResponseHead { - &self.head - } - - #[inline] - pub(crate) fn head_mut(&mut self) -> &mut ResponseHead { - &mut self.head - } - - /// Read the Request Version. - #[inline] - pub fn version(&self) -> Version { - self.head().version - } - - /// Get the status from the server. - #[inline] - pub fn status(&self) -> StatusCode { - self.head().status - } - - #[inline] - /// Returns Request's headers. - pub fn headers(&self) -> &HeaderMap { - &self.head().headers - } - - #[inline] - /// Returns mutable Request's headers. - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head_mut().headers - } - - /// Checks if a connection should be kept alive. - #[inline] - pub fn keep_alive(&self) -> bool { - self.head().keep_alive() - } - - /// Set response payload - pub fn set_payload(&mut self, payload: Payload) { - self.payload = payload; - } -} - -impl Stream for ClientResponse { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll, Self::Error> { - self.payload.poll() - } -} - -impl fmt::Debug for ClientResponse { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status(),)?; - writeln!(f, " headers:")?; - for (key, val) in self.headers().iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} diff --git a/src/h1/client.rs b/src/h1/client.rs index 851851266..b3a5a5d9f 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -13,11 +13,10 @@ use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; use super::{decoder, encoder}; use super::{Message, MessageType}; use crate::body::BodyLength; -use crate::client::ClientResponse; use crate::config::ServiceConfig; use crate::error::{ParseError, PayloadError}; use crate::helpers; -use crate::message::{ConnectionType, Head, MessagePool, RequestHead}; +use crate::message::{ConnectionType, Head, MessagePool, RequestHead, ResponseHead}; bitflags! { struct Flags: u8 { @@ -41,7 +40,7 @@ pub struct ClientPayloadCodec { struct ClientCodecInner { config: ServiceConfig, - decoder: decoder::MessageDecoder, + decoder: decoder::MessageDecoder, payload: Option, version: Version, ctype: ConnectionType, @@ -123,14 +122,14 @@ impl ClientPayloadCodec { } impl Decoder for ClientCodec { - type Item = ClientResponse; + type Item = ResponseHead; type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set"); if let Some((req, payload)) = self.inner.decoder.decode(src)? { - if let Some(ctype) = req.head().ctype { + if let Some(ctype) = req.ctype { // do not use peer's keep-alive self.inner.ctype = if ctype == ConnectionType::KeepAlive { self.inner.ctype diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 77b76c242..a1b221c06 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -9,9 +9,8 @@ use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version}; use httparse; use log::{debug, error, trace}; -use crate::client::ClientResponse; use crate::error::ParseError; -use crate::message::ConnectionType; +use crate::message::{ConnectionType, ResponseHead}; use crate::request::Request; const MAX_BUFFER_SIZE: usize = 131_072; @@ -227,13 +226,13 @@ impl MessageType for Request { } } -impl MessageType for ClientResponse { +impl MessageType for ResponseHead { fn set_connection_type(&mut self, ctype: Option) { - self.head.ctype = ctype; + self.ctype = ctype; } fn headers_mut(&mut self) -> &mut HeaderMap { - self.headers_mut() + &mut self.headers } fn decode(src: &mut BytesMut) -> Result, ParseError> { @@ -263,7 +262,7 @@ impl MessageType for ClientResponse { } }; - let mut msg = ClientResponse::new(); + let mut msg = ResponseHead::default(); // convert headers let len = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; @@ -281,8 +280,8 @@ impl MessageType for ClientResponse { PayloadType::None }; - msg.head.status = status; - msg.head.version = ver; + msg.status = status; + msg.version = ver; Ok(Some((msg, decoder))) } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index afeabc825..09a17fcf7 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -553,7 +553,7 @@ mod tests { use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::IntoService; - use bytes::{Buf, Bytes, BytesMut}; + use bytes::{Buf, Bytes}; use futures::future::{lazy, ok}; use super::*; diff --git a/src/lib.rs b/src/lib.rs index 85efe5e44..b41ce7ae8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,64 +1,4 @@ -//! Actix web is a small, pragmatic, and extremely fast web framework -//! for Rust. -//! -//! ```rust,ignore -//! use actix_web::{server, App, Path, Responder}; -//! # use std::thread; -//! -//! fn index(info: Path<(String, u32)>) -> impl Responder { -//! format!("Hello {}! id:{}", info.0, info.1) -//! } -//! -//! fn main() { -//! # thread::spawn(|| { -//! server::new(|| { -//! App::new().resource("/{name}/{id}/index.html", |r| r.with(index)) -//! }).bind("127.0.0.1:8080") -//! .unwrap() -//! .run(); -//! # }); -//! } -//! ``` -//! -//! ## Documentation & community resources -//! -//! Besides the API documentation (which you are currently looking -//! at!), several other resources are available: -//! -//! * [User Guide](https://actix.rs/docs/) -//! * [Chat on gitter](https://gitter.im/actix/actix) -//! * [GitHub repository](https://github.com/actix/actix-web) -//! * [Cargo package](https://crates.io/crates/actix-web) -//! -//! To get started navigating the API documentation you may want to -//! consider looking at the following pages: -//! -//! * [App](struct.App.html): This struct represents an actix-web -//! application and is used to configure routes and other common -//! settings. -//! -//! * [HttpServer](server/struct.HttpServer.html): This struct -//! represents an HTTP server instance and is used to instantiate and -//! configure servers. -//! -//! * [Request](struct.Request.html) and -//! [Response](struct.Response.html): These structs -//! represent HTTP requests and responses and expose various methods -//! for inspecting, creating and otherwise utilizing them. -//! -//! ## Features -//! -//! * Supported *HTTP/1.x* protocol -//! * Streaming and pipelining -//! * Keep-alive and slow requests handling -//! * `WebSockets` server/client -//! * Supported Rust version: 1.26 or later -//! -//! ## Package feature -//! -//! * `session` - enables session support, includes `ring` crate as -//! dependency -//! +//! Basic http primitives for actix-net framework. #![allow( clippy::type_complexity, clippy::new_without_default, diff --git a/src/response.rs b/src/response.rs index 5281c2d95..31c0010ab 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1042,8 +1042,6 @@ mod tests { #[test] #[cfg(feature = "cookies")] fn test_into_builder() { - use crate::httpmessage::HttpMessage; - let mut resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); diff --git a/src/ws/client/connect.rs b/src/ws/client/connect.rs index 5e877a645..2760967e0 100644 --- a/src/ws/client/connect.rs +++ b/src/ws/client/connect.rs @@ -4,15 +4,15 @@ use std::str; #[cfg(feature = "cookies")] use cookie::Cookie; use http::header::{HeaderName, HeaderValue}; -use http::{Error as HttpError, HttpTryFrom}; +use http::{Error as HttpError, HttpTryFrom, Uri}; use super::ClientError; -use crate::client::{ClientRequest, ClientRequestBuilder}; use crate::header::IntoHeaderValue; +use crate::message::RequestHead; /// `WebSocket` connection pub struct Connect { - pub(super) request: ClientRequestBuilder, + pub(super) head: RequestHead, pub(super) err: Option, pub(super) http_err: Option, pub(super) origin: Option, @@ -25,7 +25,7 @@ impl Connect { /// Create new websocket connection pub fn new>(uri: S) -> Connect { let mut cl = Connect { - request: ClientRequest::build(), + head: RequestHead::default(), err: None, http_err: None, origin: None, @@ -33,7 +33,12 @@ impl Connect { max_size: 65_536, server_mode: false, }; - cl.request.uri(uri.as_ref()); + + match Uri::try_from(uri.as_ref()) { + Ok(uri) => cl.head.uri = uri, + Err(e) => cl.http_err = Some(e.into()), + } + cl } @@ -51,12 +56,12 @@ impl Connect { self } - #[cfg(feature = "cookies")] - /// Set cookie for handshake request - pub fn cookie(mut self, cookie: Cookie) -> Self { - self.request.cookie(cookie); - self - } + // #[cfg(feature = "cookies")] + // /// Set cookie for handshake request + // pub fn cookie(mut self, cookie: Cookie) -> Self { + // self.request.cookie(cookie); + // self + // } /// Set request Origin pub fn origin(mut self, origin: V) -> Self @@ -90,7 +95,15 @@ impl Connect { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - self.request.header(key, value); + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + self.head.headers.append(key, value); + } + Err(e) => self.http_err = Some(e.into()), + }, + Err(e) => self.http_err = Some(e.into()), + } self } } diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index a0a9b2030..8a0840f90 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -14,8 +14,8 @@ use rand; use sha1::Sha1; use crate::body::BodyLength; -use crate::client::ClientResponse; use crate::h1; +use crate::message::{ConnectionType, Head, ResponseHead}; use crate::ws::Codec; use super::{ClientError, Connect, Protocol}; @@ -89,27 +89,35 @@ where } else { // origin if let Some(origin) = req.origin.take() { - req.request.set_header(header::ORIGIN, origin); + req.head.headers.insert(header::ORIGIN, origin); } - req.request.upgrade("websocket"); - req.request.set_header(header::SEC_WEBSOCKET_VERSION, "13"); + req.head.set_connection_type(ConnectionType::Upgrade); + req.head + .headers + .insert(header::UPGRADE, HeaderValue::from_static("websocket")); + req.head.headers.insert( + header::SEC_WEBSOCKET_VERSION, + HeaderValue::from_static("13"), + ); if let Some(protocols) = req.protocols.take() { - req.request - .set_header(header::SEC_WEBSOCKET_PROTOCOL, protocols.as_str()); + req.head.headers.insert( + header::SEC_WEBSOCKET_PROTOCOL, + HeaderValue::try_from(protocols.as_str()).unwrap(), + ); } - let mut request = match req.request.finish() { - Ok(req) => req, - Err(e) => return Either::A(err(e.into())), + if let Some(e) = req.http_err { + return Either::A(err(e.into())); }; - if request.uri().host().is_none() { + let mut request = req.head; + if request.uri.host().is_none() { return Either::A(err(ClientError::InvalidUrl)); } // supported protocols - let proto = if let Some(scheme) = request.uri().scheme_part() { + let proto = if let Some(scheme) = request.uri.scheme_part() { match Protocol::from(scheme.as_str()) { Some(proto) => proto, None => return Either::A(err(ClientError::InvalidUrl)), @@ -124,14 +132,14 @@ where let sec_key: [u8; 16] = rand::random(); let key = base64::encode(&sec_key); - request.headers_mut().insert( + request.headers.insert( header::SEC_WEBSOCKET_KEY, HeaderValue::try_from(key.as_str()).unwrap(), ); // prep connection - let connect = TcpConnect::new(request.uri().host().unwrap().to_string()) - .set_port(request.uri().port_u16().unwrap_or_else(|| proto.port())); + let connect = TcpConnect::new(request.uri.host().unwrap().to_string()) + .set_port(request.uri.port_u16().unwrap_or_else(|| proto.port())); let fut = Box::new( self.connector @@ -141,7 +149,7 @@ where // h1 protocol let framed = Framed::new(io, h1::ClientCodec::default()); framed - .send((request.into_parts().0, BodyLength::None).into()) + .send((request, BodyLength::None).into()) .map_err(ClientError::from) .and_then(|framed| { framed @@ -172,7 +180,7 @@ where { fut: Box< Future< - Item = (Option, Framed), + Item = (Option, Framed), Error = ClientError, >, >, @@ -198,11 +206,11 @@ where }; // verify response - if res.status() != StatusCode::SWITCHING_PROTOCOLS { - return Err(ClientError::InvalidResponseStatus(res.status())); + if res.status != StatusCode::SWITCHING_PROTOCOLS { + return Err(ClientError::InvalidResponseStatus(res.status)); } // Check for "UPGRADE" to websocket header - let has_hdr = if let Some(hdr) = res.headers().get(header::UPGRADE) { + let has_hdr = if let Some(hdr) = res.headers.get(header::UPGRADE) { if let Ok(s) = hdr.to_str() { s.to_lowercase().contains("websocket") } else { @@ -216,7 +224,7 @@ where return Err(ClientError::InvalidUpgradeHeader); } // Check for "CONNECTION" header - if let Some(conn) = res.headers().get(header::CONNECTION) { + if let Some(conn) = res.headers.get(header::CONNECTION) { if let Ok(s) = conn.to_str() { if !s.to_lowercase().contains("upgrade") { trace!("Invalid connection header: {}", s); @@ -231,7 +239,7 @@ where return Err(ClientError::MissingConnectionHeader); } - if let Some(key) = res.headers().get(header::SEC_WEBSOCKET_ACCEPT) { + if let Some(key) = res.headers.get(header::SEC_WEBSOCKET_ACCEPT) { // field is constructed by concatenating /key/ // with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455) const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index b7535f99c..313bc55cf 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -29,7 +29,7 @@ default = ["session"] session = ["cookie/secure"] # openssl -ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] +ssl = ["openssl", "actix-http/ssl", "actix-server/ssl", "awc/ssl"] [dependencies] actix-codec = "0.1.1" @@ -38,6 +38,7 @@ actix-http = { path=".." } actix-service = "0.3.4" actix-server = "0.4.0" actix-utils = "0.3.4" +awc = { git = "https://github.com/actix/actix-web.git" } base64 = "0.10" bytes = "0.4" diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 26bca787e..77329e700 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -3,15 +3,12 @@ use std::sync::mpsc; use std::{net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_http::body::MessageBody; -use actix_http::client::{ - ClientRequest, ClientRequestBuilder, ClientResponse, ConnectError, Connection, - Connector, SendRequestError, -}; -use actix_http::{http::Uri, ws}; +use actix_http::client::Connector; +use actix_http::ws; use actix_rt::{Runtime, System}; use actix_server::{Server, StreamServiceFactory}; use actix_service::Service; +use awc::{Client, ClientRequest}; use futures::future::{lazy, Future}; use http::Method; use net2::TcpBuilder; @@ -47,6 +44,7 @@ pub struct TestServer; pub struct TestServerRuntime { addr: net::SocketAddr, rt: Runtime, + client: Client, } impl TestServer { @@ -71,11 +69,39 @@ impl TestServer { }); let (system, addr) = rx.recv().unwrap(); + let mut rt = Runtime::new().unwrap(); + + let client = rt + .block_on(lazy(move || { + let connector = { + #[cfg(feature = "ssl")] + { + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + + let mut builder = + SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder.set_alpn_protos(b"\x02h2\x08http/1.1").map_err( + |e| log::error!("Can not set alpn protocol: {:?}", e), + ); + Connector::new() + .timeout(time::Duration::from_millis(500)) + .ssl(builder.build()) + .service() + } + #[cfg(not(feature = "ssl"))] + { + Connector::new() + .timeout(time::Duration::from_millis(500)) + .service() + } + }; + + Ok::(Client::build().connector(connector).finish()) + })) + .unwrap(); System::set_current(system); - TestServerRuntime { - addr, - rt: Runtime::new().unwrap(), - } + TestServerRuntime { addr, rt, client } } /// Get firat available unused address @@ -130,64 +156,38 @@ impl TestServerRuntime { } /// Create `GET` request - pub fn get(&self) -> ClientRequestBuilder { - ClientRequest::get(self.url("/").as_str()) + pub fn get(&self) -> ClientRequest { + self.client.get(self.url("/").as_str()) } /// Create https `GET` request - pub fn sget(&self) -> ClientRequestBuilder { - ClientRequest::get(self.surl("/").as_str()) + pub fn sget(&self) -> ClientRequest { + self.client.get(self.surl("/").as_str()) } /// Create `POST` request - pub fn post(&self) -> ClientRequestBuilder { - ClientRequest::post(self.url("/").as_str()) + pub fn post(&self) -> ClientRequest { + self.client.post(self.url("/").as_str()) + } + + /// Create https `POST` request + pub fn spost(&self) -> ClientRequest { + self.client.post(self.surl("/").as_str()) } /// Create `HEAD` request - pub fn head(&self) -> ClientRequestBuilder { - ClientRequest::head(self.url("/").as_str()) + pub fn head(&self) -> ClientRequest { + self.client.head(self.url("/").as_str()) + } + + /// Create https `HEAD` request + pub fn shead(&self) -> ClientRequest { + self.client.head(self.surl("/").as_str()) } /// Connect to test http server - pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder { - ClientRequest::build() - .method(meth) - .uri(self.url(path).as_str()) - .take() - } - - fn new_connector( - ) -> impl Service + Clone - { - #[cfg(feature = "ssl")] - { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder - .set_alpn_protos(b"\x02h2\x08http/1.1") - .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); - Connector::new() - .timeout(time::Duration::from_millis(500)) - .ssl(builder.build()) - .service() - } - #[cfg(not(feature = "ssl"))] - { - Connector::new() - .timeout(time::Duration::from_millis(500)) - .service() - } - } - - /// Http connector - pub fn connector( - &mut self, - ) -> impl Service + Clone - { - self.execute(|| TestServerRuntime::new_connector()) + pub fn request>(&self, method: Method, path: S) -> ClientRequest { + self.client.request(method, path.as_ref()) } /// Stop http server @@ -213,15 +213,6 @@ impl TestServerRuntime { ) -> Result, ws::ClientError> { self.ws_at("/") } - - /// Send request and read response message - pub fn send_request( - &mut self, - req: ClientRequest, - ) -> Result { - let mut conn = self.connector(); - self.rt.block_on(req.send(&mut conn)) - } } impl Drop for TestServerRuntime { diff --git a/tests/test_client.rs b/tests/test_client.rs index 2832b1b70..1ca7437d6 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -4,7 +4,7 @@ use futures::future::{self, ok}; use futures::{Future, Stream}; use actix_http::{ - client, error::PayloadError, HttpMessage, HttpService, Request, Response, + error::PayloadError, http, HttpMessage, HttpService, Request, Response, }; use actix_http_test::TestServer; @@ -48,26 +48,18 @@ fn test_h1_v2() { .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); - let mut connector = srv.connector(); - - let request = srv.get().finish().unwrap(); - let response = srv.block_on(request.send(&mut connector)).unwrap(); + let response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); - let request = srv.get().header("x-test", "111").finish().unwrap(); - let repr = format!("{:?}", request); - assert!(repr.contains("ClientRequest")); - assert!(repr.contains("x-test")); - - let mut response = srv.block_on(request.send(&mut connector)).unwrap(); + let request = srv.get().header("x-test", "111").send(); + let mut response = srv.block_on(request).unwrap(); assert!(response.status().is_success()); // read response let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - let request = srv.post().finish().unwrap(); - let mut response = srv.block_on(request.send(&mut connector)).unwrap(); + let mut response = srv.block_on(srv.post().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -82,10 +74,7 @@ fn test_connection_close() { .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); - let mut connector = srv.connector(); - - let request = srv.get().close().finish().unwrap(); - let response = srv.block_on(request.send(&mut connector)).unwrap(); + let response = srv.block_on(srv.get().close_connection().send()).unwrap(); assert!(response.status().is_success()); } @@ -102,12 +91,8 @@ fn test_with_query_parameter() { }) .map(|_| ()) }); - let mut connector = srv.connector(); - let request = client::ClientRequest::get(srv.url("/?qp=5")) - .finish() - .unwrap(); - - let response = srv.block_on(request.send(&mut connector)).unwrap(); + let request = srv.request(http::Method::GET, srv.url("/?qp=5")).send(); + let response = srv.block_on(request).unwrap(); assert!(response.status().is_success()); } diff --git a/tests/test_server.rs b/tests/test_server.rs index 8a7316cdf..5777c5691 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -13,8 +13,8 @@ use futures::stream::{once, Stream}; use actix_http::body::Body; use actix_http::error::PayloadError; use actix_http::{ - body, client, error, http, http::header, Error, HttpMessage as HttpMessage2, - HttpService, KeepAlive, Request, Response, + body, error, http, http::header, Error, HttpMessage as HttpMessage2, HttpService, + KeepAlive, Request, Response, }; fn load_body(stream: S) -> impl Future @@ -37,8 +37,7 @@ fn test_h1() { .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); - let req = client::ClientRequest::get(srv.url("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); } @@ -56,8 +55,7 @@ fn test_h1_2() { .map(|_| ()) }); - let req = client::ClientRequest::get(srv.url("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); } @@ -100,8 +98,7 @@ fn test_h2() -> std::io::Result<()> { ) }); - let req = client::ClientRequest::get(srv.surl("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let response = srv.block_on(srv.sget().send()).unwrap(); assert!(response.status().is_success()); Ok(()) } @@ -124,8 +121,7 @@ fn test_h2_1() -> std::io::Result<()> { ) }); - let req = client::ClientRequest::get(srv.surl("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let response = srv.block_on(srv.sget().send()).unwrap(); assert!(response.status().is_success()); Ok(()) } @@ -149,10 +145,7 @@ fn test_h2_body() -> std::io::Result<()> { ) }); - let req = client::ClientRequest::get(srv.surl("/")) - .body(data.clone()) - .unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.sget().send_body(data.clone())).unwrap(); assert!(response.status().is_success()); let body = srv.block_on(load_body(response.take_payload())).unwrap(); @@ -331,24 +324,24 @@ fn test_content_length() { { for i in 0..4 { - let req = client::ClientRequest::get(srv.url(&format!("/{}", i))) - .finish() - .unwrap(); - let response = srv.send_request(req).unwrap(); + let req = srv + .request(http::Method::GET, srv.url(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); assert_eq!(response.headers().get(&header), None); - let req = client::ClientRequest::head(srv.url(&format!("/{}", i))) - .finish() - .unwrap(); - let response = srv.send_request(req).unwrap(); + let req = srv + .request(http::Method::HEAD, srv.url(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); assert_eq!(response.headers().get(&header), None); } for i in 4..6 { - let req = client::ClientRequest::get(srv.url(&format!("/{}", i))) - .finish() - .unwrap(); - let response = srv.send_request(req).unwrap(); + let req = srv + .request(http::Method::GET, srv.url(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); assert_eq!(response.headers().get(&header), Some(&value)); } } @@ -389,24 +382,24 @@ fn test_h2_content_length() { { for i in 0..4 { - let req = client::ClientRequest::get(srv.surl(&format!("/{}", i))) - .finish() - .unwrap(); - let response = srv.send_request(req).unwrap(); + let req = srv + .request(http::Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); assert_eq!(response.headers().get(&header), None); - let req = client::ClientRequest::head(srv.surl(&format!("/{}", i))) - .finish() - .unwrap(); - let response = srv.send_request(req).unwrap(); + let req = srv + .request(http::Method::HEAD, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); assert_eq!(response.headers().get(&header), None); } for i in 4..6 { - let req = client::ClientRequest::get(srv.surl(&format!("/{}", i))) - .finish() - .unwrap(); - let response = srv.send_request(req).unwrap(); + let req = srv + .request(http::Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); assert_eq!(response.headers().get(&header), Some(&value)); } } @@ -442,11 +435,8 @@ fn test_h1_headers() { future::ok::<_, ()>(builder.body(data.clone())) }) }); - let mut connector = srv.connector(); - let req = srv.get().finish().unwrap(); - - let mut response = srv.block_on(req.send(&mut connector)).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -489,10 +479,8 @@ fn test_h2_headers() { future::ok::<_, ()>(builder.body(data.clone())) }).map_err(|_| ())) }); - let mut connector = srv.connector(); - let req = client::ClientRequest::get(srv.surl("/")).finish().unwrap(); - let mut response = srv.block_on(req.send(&mut connector)).unwrap(); + let mut response = srv.block_on(srv.sget().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -528,8 +516,7 @@ fn test_h1_body() { HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().body(STR))) }); - let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -551,8 +538,7 @@ fn test_h2_body2() { ) }); - let req = srv.sget().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.sget().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -566,8 +552,7 @@ fn test_h1_head_empty() { HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); - let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.head().send()).unwrap(); assert!(response.status().is_success()); { @@ -597,8 +582,7 @@ fn test_h2_head_empty() { ) }); - let req = client::ClientRequest::head(srv.surl("/")).finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.shead().send()).unwrap(); assert!(response.status().is_success()); assert_eq!(response.version(), http::Version::HTTP_2); @@ -623,8 +607,7 @@ fn test_h1_head_binary() { }) }); - let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.head().send()).unwrap(); assert!(response.status().is_success()); { @@ -658,8 +641,7 @@ fn test_h2_head_binary() { ) }); - let req = client::ClientRequest::head(srv.surl("/")).finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.shead().send()).unwrap(); assert!(response.status().is_success()); { @@ -681,8 +663,7 @@ fn test_h1_head_binary2() { HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); - let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let response = srv.block_on(srv.head().send()).unwrap(); assert!(response.status().is_success()); { @@ -708,8 +689,7 @@ fn test_h2_head_binary2() { ) }); - let req = client::ClientRequest::head(srv.surl("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let response = srv.block_on(srv.shead().send()).unwrap(); assert!(response.status().is_success()); { @@ -733,8 +713,7 @@ fn test_h1_body_length() { }) }); - let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -761,8 +740,7 @@ fn test_h2_body_length() { ) }); - let req = srv.sget().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.sget().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -783,8 +761,7 @@ fn test_h1_body_chunked_explicit() { }) }); - let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); assert_eq!( response @@ -825,8 +802,7 @@ fn test_h2_body_chunked_explicit() { ) }); - let req = srv.sget().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.sget().send()).unwrap(); assert!(response.status().is_success()); assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); @@ -846,8 +822,7 @@ fn test_h1_body_chunked_implicit() { }) }); - let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); assert_eq!( response @@ -879,8 +854,7 @@ fn test_h1_response_http_error_handling() { })) }); - let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response @@ -912,8 +886,7 @@ fn test_h2_response_http_error_handling() { ) }); - let req = srv.sget().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.sget().send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response @@ -928,8 +901,7 @@ fn test_h1_service_error() { .h1(|_| Err::(error::ErrorBadRequest("error"))) }); - let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response @@ -952,8 +924,7 @@ fn test_h2_service_error() { ) }); - let req = srv.sget().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.sget().send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response