From 4519db36b25992d98061d870fe738e0c04e50afb Mon Sep 17 00:00:00 2001 From: Joshua Parkin Date: Thu, 29 Oct 2020 18:38:49 +0000 Subject: [PATCH 01/81] register fns for custom request-derived logging units (#1749) Co-authored-by: Rob Ede --- CHANGES.md | 2 + src/middleware/logger.rs | 156 +++++++++++++++++++++++++++++++++++---- 2 files changed, 142 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index af34c3b49..fb4fde4f1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ ### Added * Implement `exclude_regex` for Logger middleware. [#1723] * Add request-local data extractor `web::ReqData`. [#1748] +* Add ability to register closure for request middleware logging. [#1749] * Add `app_data` to `ServiceConfig`. [#1757] ### Changed @@ -15,6 +16,7 @@ [#1743]: https://github.com/actix/actix-web/pull/1743 [#1748]: https://github.com/actix/actix-web/pull/1748 [#1750]: https://github.com/actix/actix-web/pull/1750 +[#1749]: https://github.com/actix/actix-web/pull/1749 ## 3.1.0 - 2020-09-29 diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 9a38d345b..b2e5c791f 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -34,21 +34,19 @@ use crate::HttpResponse; /// Default `Logger` could be created with `default` method, it uses the /// default format: /// -/// ```ignore -/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T +/// ```plain +/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T /// ``` +/// /// ```rust -/// use actix_web::middleware::Logger; -/// use actix_web::App; +/// use actix_web::{middleware::Logger, App}; /// -/// fn main() { -/// std::env::set_var("RUST_LOG", "actix_web=info"); -/// env_logger::init(); +/// std::env::set_var("RUST_LOG", "actix_web=info"); +/// env_logger::init(); /// -/// let app = App::new() -/// .wrap(Logger::default()) -/// .wrap(Logger::new("%a %{User-Agent}i")); -/// } +/// let app = App::new() +/// .wrap(Logger::default()) +/// .wrap(Logger::new("%a %{User-Agent}i")); /// ``` /// /// ## Format @@ -80,6 +78,8 @@ use crate::HttpResponse; /// /// `%{FOO}e` os.environ['FOO'] /// +/// `%{FOO}xi` [custom request replacement](Logger::custom_request_replace) labelled "FOO" +/// /// # Security /// **\*** It is calculated using /// [`ConnectionInfo::realip_remote_addr()`](../dev/struct.ConnectionInfo.html#method.realip_remote_addr) @@ -123,12 +123,52 @@ impl Logger { inner.exclude_regex = regex_set; self } + + /// Register a function that receives a ServiceRequest and returns a String for use in the + /// log line. The label passed as the first argument should match a replacement substring in + /// the logger format like `%{label}xi`. + /// + /// It is convention to print "-" to indicate no output instead of an empty string. + /// + /// # Example + /// ```rust + /// # use actix_web::{http::HeaderValue, middleware::Logger}; + /// # fn parse_jwt_id (_req: Option<&HeaderValue>) -> String { "jwt_uid".to_owned() } + /// Logger::new("example %{JWT_ID}xi") + /// .custom_request_replace("JWT_ID", |req| parse_jwt_id(req.headers().get("Authorization"))); + /// ``` + pub fn custom_request_replace( + mut self, + label: &str, + f: impl Fn(&ServiceRequest) -> String + 'static, + ) -> Self { + let inner = Rc::get_mut(&mut self.0).unwrap(); + + let ft = inner.format.0.iter_mut().find(|ft| { + matches!(ft, FormatText::CustomRequest(unit_label, _) if label == unit_label) + }); + + if let Some(FormatText::CustomRequest(_, request_fn)) = ft { + // replace into None or previously registered fn using same label + request_fn.replace(CustomRequestFn { + inner_fn: Rc::new(f), + }); + } else { + // non-printed request replacement function diagnostic + debug!( + "Attempted to register custom request logging function for nonexistent label: {}", + label + ); + } + + self + } } impl Default for Logger { /// Create `Logger` middleware with format: /// - /// ```ignore + /// ```plain /// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T /// ``` fn default() -> Logger { @@ -153,6 +193,17 @@ where type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { + for unit in &self.0.format.0 { + // missing request replacement function diagnostic + if let FormatText::CustomRequest(label, None) = unit { + debug!( + "No custom request replacement function was registered for label {} in\ + logger format.", + label + ); + } + } + ok(LoggerMiddleware { service, inner: self.0.clone(), @@ -311,7 +362,6 @@ impl MessageBody for StreamLog { /// 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 { @@ -327,7 +377,8 @@ impl Format { /// Returns `None` if the format string syntax is incorrect. pub fn new(s: &str) -> Format { log::trace!("Access log format: {}", s); - let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([aioe])|[atPrUsbTD]?)").unwrap(); + let fmt = + Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([aioe]|xi)|[atPrUsbTD]?)").unwrap(); let mut idx = 0; let mut results = Vec::new(); @@ -355,6 +406,7 @@ impl Format { HeaderName::try_from(key.as_str()).unwrap(), ), "e" => FormatText::EnvironHeader(key.as_str().to_owned()), + "xi" => FormatText::CustomRequest(key.as_str().to_owned(), None), _ => unreachable!(), }) } else { @@ -384,7 +436,9 @@ impl Format { /// 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)] +#[non_exhaustive] #[derive(Debug, Clone)] +// TODO: remove pub on next breaking change pub enum FormatText { Str(String), Percent, @@ -400,6 +454,26 @@ pub enum FormatText { RequestHeader(HeaderName), ResponseHeader(HeaderName), EnvironHeader(String), + CustomRequest(String, Option), +} + +// TODO: remove pub on next breaking change +#[doc(hidden)] +#[derive(Clone)] +pub struct CustomRequestFn { + inner_fn: Rc String>, +} + +impl CustomRequestFn { + fn call(&self, req: &ServiceRequest) -> String { + (self.inner_fn)(req) + } +} + +impl fmt::Debug for CustomRequestFn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("custom_request_fn") + } } impl FormatText { @@ -456,7 +530,7 @@ impl FormatText { } fn render_request(&mut self, now: OffsetDateTime, req: &ServiceRequest) { - match *self { + match &*self { FormatText::RequestLine => { *self = if req.query_string().is_empty() { FormatText::Str(format!( @@ -508,11 +582,20 @@ impl FormatText { }; *self = s; } + FormatText::CustomRequest(_, request_fn) => { + let s = match request_fn { + Some(f) => FormatText::Str(f.call(req)), + None => FormatText::Str("-".to_owned()), + }; + + *self = s; + } _ => (), } } } +/// Converter to get a String from something that writes to a Formatter. pub(crate) struct FormatDisplay<'a>( &'a dyn Fn(&mut Formatter<'_>) -> Result<(), fmt::Error>, ); @@ -530,7 +613,7 @@ mod tests { use super::*; use crate::http::{header, StatusCode}; - use crate::test::TestRequest; + use crate::test::{self, TestRequest}; #[actix_rt::test] async fn test_logger() { @@ -699,4 +782,45 @@ mod tests { println!("{}", s); assert!(s.contains("192.0.2.60")); } + + #[actix_rt::test] + async fn test_custom_closure_log() { + let mut logger = Logger::new("test %{CUSTOM}xi") + .custom_request_replace("CUSTOM", |_req: &ServiceRequest| -> String { + String::from("custom_log") + }); + let mut unit = Rc::get_mut(&mut logger.0).unwrap().format.0[1].clone(); + + let label = match &unit { + FormatText::CustomRequest(label, _) => label, + ft => panic!("expected CustomRequest, found {:?}", ft), + }; + + assert_eq!(label, "CUSTOM"); + + let req = TestRequest::default().to_srv_request(); + let now = OffsetDateTime::now_utc(); + + unit.render_request(now, &req); + + let render = |fmt: &mut Formatter<'_>| unit.render(fmt, 1024, now); + + let log_output = FormatDisplay(&render).to_string(); + assert_eq!(log_output, "custom_log"); + } + + #[actix_rt::test] + async fn test_closure_logger_in_middleware() { + let captured = "custom log replacement"; + + let logger = Logger::new("%{CUSTOM}xi") + .custom_request_replace("CUSTOM", move |_req: &ServiceRequest| -> String { + captured.to_owned() + }); + + let mut srv = logger.new_transform(test::ok_service()).await.unwrap(); + + let req = TestRequest::default().to_srv_request(); + srv.call(req).await.unwrap(); + } } From 9963a5ef54119f3a4b791cdd76ae607397334d4d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 30 Oct 2020 02:03:26 +0000 Subject: [PATCH 02/81] expose on_connect v2 (#1754) Co-authored-by: Mikail Bagishov --- CHANGES.md | 2 + Cargo.toml | 14 ++-- actix-http/CHANGES.md | 7 ++ actix-http/src/builder.rs | 41 +++++++++-- actix-http/src/extensions.rs | 30 +++++++- actix-http/src/h1/dispatcher.rs | 18 ++++- actix-http/src/h1/service.rs | 35 ++++++++-- actix-http/src/h2/dispatcher.rs | 8 +++ actix-http/src/h2/service.rs | 46 ++++++++---- actix-http/src/helpers.rs | 1 + actix-http/src/lib.rs | 4 +- actix-http/src/service.rs | 65 ++++++++++++----- actix-http/tests/test_openssl.rs | 2 + actix-http/tests/test_server.rs | 2 + examples/on_connect.rs | 51 ++++++++++++++ src/server.rs | 116 ++++++++++++++++++++++++++----- 16 files changed, 372 insertions(+), 70 deletions(-) create mode 100644 examples/on_connect.rs diff --git a/CHANGES.md b/CHANGES.md index fb4fde4f1..15d44b75c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ * Add request-local data extractor `web::ReqData`. [#1748] * Add ability to register closure for request middleware logging. [#1749] * Add `app_data` to `ServiceConfig`. [#1757] +* Expose `on_connect` for access to the connection stream before request is handled. [#1754] ### Changed * Print non-configured `Data` type when attempting extraction. [#1743] @@ -16,6 +17,7 @@ [#1743]: https://github.com/actix/actix-web/pull/1743 [#1748]: https://github.com/actix/actix-web/pull/1748 [#1750]: https://github.com/actix/actix-web/pull/1750 +[#1754]: https://github.com/actix/actix-web/pull/1754 [#1749]: https://github.com/actix/actix-web/pull/1749 diff --git a/Cargo.toml b/Cargo.toml index 5d64cfd91..4fafc61c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,14 @@ required-features = ["compress"] name = "test_server" required-features = ["compress"] +[[example]] +name = "on_connect" +required-features = [] + +[[example]] +name = "client" +required-features = ["rustls"] + [dependencies] actix-codec = "0.3.0" actix-service = "1.0.6" @@ -105,7 +113,7 @@ tinyvec = { version = "1", features = ["alloc"] } actix = "0.10.0" actix-http = { version = "2.0.0", features = ["actors"] } rand = "0.7" -env_logger = "0.7" +env_logger = "0.8" serde_derive = "1.0" brotli2 = "0.3.2" flate2 = "1.0.13" @@ -125,10 +133,6 @@ actix-files = { path = "actix-files" } actix-multipart = { path = "actix-multipart" } awc = { path = "awc" } -[[example]] -name = "client" -required-features = ["rustls"] - [[bench]] name = "server" harness = false diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 990c9c071..0afb63a6d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,9 +1,16 @@ # Changes ## Unreleased - 2020-xx-xx +### Added +* Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] + +### Changed * Upgrade `base64` to `0.13`. * Upgrade `pin-project` to `1.0`. +[#1754]: https://github.com/actix/actix-web/pull/1754 + + ## 2.0.0 - 2020-09-11 * No significant changes from `2.0.0-beta.4`. diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 271abd43f..b28c69761 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -14,10 +14,11 @@ use crate::helpers::{Data, DataFactory}; use crate::request::Request; use crate::response::Response; use crate::service::HttpService; +use crate::{ConnectCallback, Extensions}; -/// A http service builder +/// A HTTP service builder /// -/// This type can be used to construct an instance of `http service` through a +/// This type can be used to construct an instance of [`HttpService`] through a /// builder-like pattern. pub struct HttpServiceBuilder> { keep_alive: KeepAlive, @@ -27,7 +28,9 @@ pub struct HttpServiceBuilder> { local_addr: Option, expect: X, upgrade: Option, + // DEPRECATED: in favor of on_connect_ext on_connect: Option Box>>, + on_connect_ext: Option>>, _t: PhantomData<(T, S)>, } @@ -49,6 +52,7 @@ where expect: ExpectHandler, upgrade: None, on_connect: None, + on_connect_ext: None, _t: PhantomData, } } @@ -138,6 +142,7 @@ where expect: expect.into_factory(), upgrade: self.upgrade, on_connect: self.on_connect, + on_connect_ext: self.on_connect_ext, _t: PhantomData, } } @@ -167,14 +172,16 @@ where expect: self.expect, upgrade: Some(upgrade.into_factory()), on_connect: self.on_connect, + on_connect_ext: self.on_connect_ext, _t: PhantomData, } } /// Set on-connect callback. /// - /// It get called once per connection and result of the call - /// get stored to the request's extensions. + /// Called once per connection. Return value of the call is stored in request extensions. + /// + /// *SOFT DEPRECATED*: Prefer the `on_connect_ext` style callback. pub fn on_connect(mut self, f: F) -> Self where F: Fn(&T) -> I + 'static, @@ -184,7 +191,20 @@ where self } - /// Finish service configuration and create *http service* for HTTP/1 protocol. + /// Sets the callback to be run on connection establishment. + /// + /// Has mutable access to a data container that will be merged into request extensions. + /// This enables transport layer data (like client certificates) to be accessed in middleware + /// and handlers. + pub fn on_connect_ext(mut self, f: F) -> Self + where + F: Fn(&T, &mut Extensions) + 'static, + { + self.on_connect_ext = Some(Rc::new(f)); + self + } + + /// Finish service configuration and create a HTTP Service for HTTP/1 protocol. pub fn h1(self, service: F) -> H1Service where B: MessageBody, @@ -200,13 +220,15 @@ where self.secure, self.local_addr, ); + H1Service::with_config(cfg, service.into_factory()) .expect(self.expect) .upgrade(self.upgrade) .on_connect(self.on_connect) + .on_connect_ext(self.on_connect_ext) } - /// Finish service configuration and create *http service* for HTTP/2 protocol. + /// Finish service configuration and create a HTTP service for HTTP/2 protocol. pub fn h2(self, service: F) -> H2Service where B: MessageBody + 'static, @@ -223,7 +245,10 @@ where self.secure, self.local_addr, ); - H2Service::with_config(cfg, service.into_factory()).on_connect(self.on_connect) + + H2Service::with_config(cfg, service.into_factory()) + .on_connect(self.on_connect) + .on_connect_ext(self.on_connect_ext) } /// Finish service configuration and create `HttpService` instance. @@ -243,9 +268,11 @@ where self.secure, self.local_addr, ); + HttpService::with_config(cfg, service.into_factory()) .expect(self.expect) .upgrade(self.upgrade) .on_connect(self.on_connect) + .on_connect_ext(self.on_connect_ext) } } diff --git a/actix-http/src/extensions.rs b/actix-http/src/extensions.rs index 09f1b711f..7dda74731 100644 --- a/actix-http/src/extensions.rs +++ b/actix-http/src/extensions.rs @@ -1,5 +1,5 @@ use std::any::{Any, TypeId}; -use std::fmt; +use std::{fmt, mem}; use fxhash::FxHashMap; @@ -66,6 +66,11 @@ impl Extensions { pub fn extend(&mut self, other: Extensions) { self.map.extend(other.map); } + + /// Sets (or overrides) items from `other` into this map. + pub(crate) fn drain_from(&mut self, other: &mut Self) { + self.map.extend(mem::take(&mut other.map)); + } } impl fmt::Debug for Extensions { @@ -213,4 +218,27 @@ mod tests { assert_eq!(extensions.get(), Some(&20u8)); assert_eq!(extensions.get_mut(), Some(&mut 20u8)); } + + #[test] + fn test_drain_from() { + let mut ext = Extensions::new(); + ext.insert(2isize); + + let mut more_ext = Extensions::new(); + + more_ext.insert(5isize); + more_ext.insert(5usize); + + assert_eq!(ext.get::(), Some(&2isize)); + assert_eq!(ext.get::(), None); + assert_eq!(more_ext.get::(), Some(&5isize)); + assert_eq!(more_ext.get::(), Some(&5usize)); + + ext.drain_from(&mut more_ext); + + assert_eq!(ext.get::(), Some(&5isize)); + assert_eq!(ext.get::(), Some(&5usize)); + assert_eq!(more_ext.get::(), None); + assert_eq!(more_ext.get::(), None); + } } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 7c4de9707..ace4144e3 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -12,7 +12,6 @@ use bytes::{Buf, BytesMut}; use log::{error, trace}; use pin_project::pin_project; -use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; @@ -21,6 +20,10 @@ use crate::helpers::DataFactory; use crate::httpmessage::HttpMessage; use crate::request::Request; use crate::response::Response; +use crate::{ + body::{Body, BodySize, MessageBody, ResponseBody}, + Extensions, +}; use super::codec::Codec; use super::payload::{Payload, PayloadSender, PayloadStatus}; @@ -88,6 +91,7 @@ where expect: CloneableService, upgrade: Option>, on_connect: Option>, + on_connect_data: Extensions, flags: Flags, peer_addr: Option, error: Option, @@ -167,7 +171,7 @@ where U: Service), Response = ()>, U::Error: fmt::Display, { - /// Create http/1 dispatcher. + /// Create HTTP/1 dispatcher. pub(crate) fn new( stream: T, config: ServiceConfig, @@ -175,6 +179,7 @@ where expect: CloneableService, upgrade: Option>, on_connect: Option>, + on_connect_data: Extensions, peer_addr: Option, ) -> Self { Dispatcher::with_timeout( @@ -187,6 +192,7 @@ where expect, upgrade, on_connect, + on_connect_data, peer_addr, ) } @@ -202,6 +208,7 @@ where expect: CloneableService, upgrade: Option>, on_connect: Option>, + on_connect_data: Extensions, peer_addr: Option, ) -> Self { let keepalive = config.keep_alive_enabled(); @@ -234,6 +241,7 @@ where expect, upgrade, on_connect, + on_connect_data, flags, peer_addr, ka_expire, @@ -526,11 +534,15 @@ where let pl = this.codec.message_type(); req.head_mut().peer_addr = *this.peer_addr; + // DEPRECATED // set on_connect data if let Some(ref on_connect) = this.on_connect { on_connect.set(&mut req.extensions_mut()); } + // merge on_connect_ext data into request extensions + req.extensions_mut().drain_from(this.on_connect_data); + if pl == MessageType::Stream && this.upgrade.is_some() { this.messages.push_back(DispatcherMessage::Upgrade(req)); break; @@ -927,8 +939,10 @@ mod tests { CloneableService::new(ExpectHandler), None, None, + Extensions::new(), None, ); + match Pin::new(&mut h1).poll(cx) { Poll::Pending => panic!(), Poll::Ready(res) => assert!(res.is_err()), diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 6aafd4089..5008791c0 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -18,6 +18,7 @@ use crate::error::{DispatchError, Error, ParseError}; use crate::helpers::DataFactory; use crate::request::Request; use crate::response::Response; +use crate::{ConnectCallback, Extensions}; use super::codec::Codec; use super::dispatcher::Dispatcher; @@ -30,6 +31,7 @@ pub struct H1Service> { expect: X, upgrade: Option, on_connect: Option Box>>, + on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -52,6 +54,7 @@ where expect: ExpectHandler, upgrade: None, on_connect: None, + on_connect_ext: None, _t: PhantomData, } } @@ -213,6 +216,7 @@ where srv: self.srv, upgrade: self.upgrade, on_connect: self.on_connect, + on_connect_ext: self.on_connect_ext, _t: PhantomData, } } @@ -229,6 +233,7 @@ where srv: self.srv, expect: self.expect, on_connect: self.on_connect, + on_connect_ext: self.on_connect_ext, _t: PhantomData, } } @@ -241,6 +246,12 @@ where self.on_connect = f; self } + + /// Set on connect callback. + pub(crate) fn on_connect_ext(mut self, f: Option>>) -> Self { + self.on_connect_ext = f; + self + } } impl ServiceFactory for H1Service @@ -274,6 +285,7 @@ where expect: None, upgrade: None, on_connect: self.on_connect.clone(), + on_connect_ext: self.on_connect_ext.clone(), cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -303,6 +315,7 @@ where expect: Option, upgrade: Option, on_connect: Option Box>>, + on_connect_ext: Option>>, cfg: Option, _t: PhantomData<(T, B)>, } @@ -352,23 +365,26 @@ where Poll::Ready(result.map(|service| { let this = self.as_mut().project(); + H1ServiceHandler::new( this.cfg.take().unwrap(), service, this.expect.take().unwrap(), this.upgrade.take(), this.on_connect.clone(), + this.on_connect_ext.clone(), ) })) } } -/// `Service` implementation for HTTP1 transport +/// `Service` implementation for HTTP/1 transport pub struct H1ServiceHandler { srv: CloneableService, expect: CloneableService, upgrade: Option>, on_connect: Option Box>>, + on_connect_ext: Option>>, cfg: ServiceConfig, _t: PhantomData<(T, B)>, } @@ -390,6 +406,7 @@ where expect: X, upgrade: Option, on_connect: Option Box>>, + on_connect_ext: Option>>, ) -> H1ServiceHandler { H1ServiceHandler { srv: CloneableService::new(srv), @@ -397,6 +414,7 @@ where upgrade: upgrade.map(CloneableService::new), cfg, on_connect, + on_connect_ext, _t: PhantomData, } } @@ -462,11 +480,13 @@ where } fn call(&mut self, (io, addr): Self::Request) -> Self::Future { - let on_connect = if let Some(ref on_connect) = self.on_connect { - Some(on_connect(&io)) - } else { - None - }; + let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io)); + + let mut connect_extensions = Extensions::new(); + if let Some(ref handler) = self.on_connect_ext { + // run on_connect_ext callback, populating connect extensions + handler(&io, &mut connect_extensions); + } Dispatcher::new( io, @@ -474,7 +494,8 @@ where self.srv.clone(), self.expect.clone(), self.upgrade.clone(), - on_connect, + deprecated_on_connect, + connect_extensions, addr, ) } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index daa651f4d..594339121 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -24,6 +24,7 @@ use crate::message::ResponseHead; use crate::payload::Payload; use crate::request::Request; use crate::response::Response; +use crate::Extensions; const CHUNK_SIZE: usize = 16_384; @@ -36,6 +37,7 @@ where service: CloneableService, connection: Connection, on_connect: Option>, + on_connect_data: Extensions, config: ServiceConfig, peer_addr: Option, ka_expire: Instant, @@ -56,6 +58,7 @@ where service: CloneableService, connection: Connection, on_connect: Option>, + on_connect_data: Extensions, config: ServiceConfig, timeout: Option, peer_addr: Option, @@ -82,6 +85,7 @@ where peer_addr, connection, on_connect, + on_connect_data, ka_expire, ka_timer, _t: PhantomData, @@ -130,11 +134,15 @@ where head.headers = parts.headers.into(); head.peer_addr = this.peer_addr; + // DEPRECATED // set on_connect data if let Some(ref on_connect) = this.on_connect { on_connect.set(&mut req.extensions_mut()); } + // merge on_connect_ext data into request extensions + req.extensions_mut().drain_from(&mut this.on_connect_data); + actix_rt::spawn(ServiceResponse::< S::Future, S::Response, diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 6b5620e02..3103127b4 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -2,7 +2,7 @@ use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; -use std::{net, rc}; +use std::{net, rc::Rc}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::net::TcpStream; @@ -23,6 +23,7 @@ use crate::error::{DispatchError, Error}; use crate::helpers::DataFactory; use crate::request::Request; use crate::response::Response; +use crate::{ConnectCallback, Extensions}; use super::dispatcher::Dispatcher; @@ -30,7 +31,8 @@ use super::dispatcher::Dispatcher; pub struct H2Service { srv: S, cfg: ServiceConfig, - on_connect: Option Box>>, + on_connect: Option Box>>, + on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -50,19 +52,27 @@ where H2Service { cfg, on_connect: None, + on_connect_ext: None, srv: service.into_factory(), _t: PhantomData, } } /// Set on connect callback. + pub(crate) fn on_connect( mut self, - f: Option Box>>, + f: Option Box>>, ) -> Self { self.on_connect = f; self } + + /// Set on connect callback. + pub(crate) fn on_connect_ext(mut self, f: Option>>) -> Self { + self.on_connect_ext = f; + self + } } impl H2Service @@ -203,6 +213,7 @@ where fut: self.srv.new_service(()), cfg: Some(self.cfg.clone()), on_connect: self.on_connect.clone(), + on_connect_ext: self.on_connect_ext.clone(), _t: PhantomData, } } @@ -214,7 +225,8 @@ pub struct H2ServiceResponse { #[pin] fut: S::Future, cfg: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, + on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -237,6 +249,7 @@ where H2ServiceHandler::new( this.cfg.take().unwrap(), this.on_connect.clone(), + this.on_connect_ext.clone(), service, ) })) @@ -247,7 +260,8 @@ where pub struct H2ServiceHandler { srv: CloneableService, cfg: ServiceConfig, - on_connect: Option Box>>, + on_connect: Option Box>>, + on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -261,12 +275,14 @@ where { fn new( cfg: ServiceConfig, - on_connect: Option Box>>, + on_connect: Option Box>>, + on_connect_ext: Option>>, srv: S, ) -> H2ServiceHandler { H2ServiceHandler { cfg, on_connect, + on_connect_ext, srv: CloneableService::new(srv), _t: PhantomData, } @@ -296,18 +312,21 @@ where } fn call(&mut self, (io, addr): Self::Request) -> Self::Future { - let on_connect = if let Some(ref on_connect) = self.on_connect { - Some(on_connect(&io)) - } else { - None - }; + let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io)); + + let mut connect_extensions = Extensions::new(); + if let Some(ref handler) = self.on_connect_ext { + // run on_connect_ext callback, populating connect extensions + handler(&io, &mut connect_extensions); + } H2ServiceHandlerResponse { state: State::Handshake( Some(self.srv.clone()), Some(self.cfg.clone()), addr, - on_connect, + deprecated_on_connect, + Some(connect_extensions), server::handshake(io), ), } @@ -325,6 +344,7 @@ where Option, Option, Option>, + Option, Handshake, ), } @@ -360,6 +380,7 @@ where ref mut config, ref peer_addr, ref mut on_connect, + ref mut on_connect_data, ref mut handshake, ) => match Pin::new(handshake).poll(cx) { Poll::Ready(Ok(conn)) => { @@ -367,6 +388,7 @@ where srv.take().unwrap(), conn, on_connect.take(), + on_connect_data.take().unwrap(), config.take().unwrap(), None, *peer_addr, diff --git a/actix-http/src/helpers.rs b/actix-http/src/helpers.rs index bbf358b66..ac0e0f118 100644 --- a/actix-http/src/helpers.rs +++ b/actix-http/src/helpers.rs @@ -50,6 +50,7 @@ impl<'a> io::Write for Writer<'a> { self.0.extend_from_slice(buf); Ok(buf.len()) } + fn flush(&mut self) -> io::Result<()> { Ok(()) } diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index fab91be2b..e57a3727e 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -1,4 +1,4 @@ -//! Basic http primitives for actix-net framework. +//! Basic HTTP primitives for the Actix ecosystem. #![deny(rust_2018_idioms)] #![allow( @@ -78,3 +78,5 @@ pub enum Protocol { Http1, Http2, } + +type ConnectCallback = dyn Fn(&IO, &mut Extensions); diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 9ee579702..75745209c 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; -use std::{fmt, net, rc}; +use std::{fmt, net, rc::Rc}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; @@ -20,15 +20,17 @@ use crate::error::{DispatchError, Error}; use crate::helpers::DataFactory; use crate::request::Request; use crate::response::Response; -use crate::{h1, h2::Dispatcher, Protocol}; +use crate::{h1, h2::Dispatcher, ConnectCallback, Extensions, Protocol}; -/// `ServiceFactory` HTTP1.1/HTTP2 transport implementation +/// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol. pub struct HttpService> { srv: S, cfg: ServiceConfig, expect: X, upgrade: Option, - on_connect: Option Box>>, + // DEPRECATED: in favor of on_connect_ext + on_connect: Option Box>>, + on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -66,6 +68,7 @@ where expect: h1::ExpectHandler, upgrade: None, on_connect: None, + on_connect_ext: None, _t: PhantomData, } } @@ -81,6 +84,7 @@ where expect: h1::ExpectHandler, upgrade: None, on_connect: None, + on_connect_ext: None, _t: PhantomData, } } @@ -113,6 +117,7 @@ where srv: self.srv, upgrade: self.upgrade, on_connect: self.on_connect, + on_connect_ext: self.on_connect_ext, _t: PhantomData, } } @@ -138,6 +143,7 @@ where srv: self.srv, expect: self.expect, on_connect: self.on_connect, + on_connect_ext: self.on_connect_ext, _t: PhantomData, } } @@ -145,11 +151,17 @@ where /// Set on connect callback. pub(crate) fn on_connect( mut self, - f: Option Box>>, + f: Option Box>>, ) -> Self { self.on_connect = f; self } + + /// Set connect callback with mutable access to request data container. + pub(crate) fn on_connect_ext(mut self, f: Option>>) -> Self { + self.on_connect_ext = f; + self + } } impl HttpService @@ -355,6 +367,7 @@ where expect: None, upgrade: None, on_connect: self.on_connect.clone(), + on_connect_ext: self.on_connect_ext.clone(), cfg: self.cfg.clone(), _t: PhantomData, } @@ -378,7 +391,8 @@ pub struct HttpServiceResponse< fut_upg: Option, expect: Option, upgrade: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, + on_connect_ext: Option>>, cfg: ServiceConfig, _t: PhantomData<(T, B)>, } @@ -429,6 +443,7 @@ where .fut .poll(cx) .map_err(|e| log::error!("Init http service error: {:?}", e))); + Poll::Ready(result.map(|service| { let this = self.as_mut().project(); HttpServiceHandler::new( @@ -437,6 +452,7 @@ where this.expect.take().unwrap(), this.upgrade.take(), this.on_connect.clone(), + this.on_connect_ext.clone(), ) })) } @@ -448,7 +464,8 @@ pub struct HttpServiceHandler { expect: CloneableService, upgrade: Option>, cfg: ServiceConfig, - on_connect: Option Box>>, + on_connect: Option Box>>, + on_connect_ext: Option>>, _t: PhantomData<(T, B, X)>, } @@ -469,11 +486,13 @@ where srv: S, expect: X, upgrade: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, + on_connect_ext: Option>>, ) -> HttpServiceHandler { HttpServiceHandler { cfg, on_connect, + on_connect_ext, srv: CloneableService::new(srv), expect: CloneableService::new(expect), upgrade: upgrade.map(CloneableService::new), @@ -543,11 +562,12 @@ where } fn call(&mut self, (io, proto, peer_addr): Self::Request) -> Self::Future { - let on_connect = if let Some(ref on_connect) = self.on_connect { - Some(on_connect(&io)) - } else { - None - }; + let mut connect_extensions = Extensions::new(); + + let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io)); + if let Some(ref handler) = self.on_connect_ext { + handler(&io, &mut connect_extensions); + } match proto { Protocol::Http2 => HttpServiceHandlerResponse { @@ -555,10 +575,12 @@ where server::handshake(io), self.cfg.clone(), self.srv.clone(), - on_connect, + deprecated_on_connect, + connect_extensions, peer_addr, ))), }, + Protocol::Http1 => HttpServiceHandlerResponse { state: State::H1(h1::Dispatcher::new( io, @@ -566,7 +588,8 @@ where self.srv.clone(), self.expect.clone(), self.upgrade.clone(), - on_connect, + deprecated_on_connect, + connect_extensions, peer_addr, )), }, @@ -595,6 +618,7 @@ where ServiceConfig, CloneableService, Option>, + Extensions, Option, )>, ), @@ -670,9 +694,16 @@ where } else { panic!() }; - let (_, cfg, srv, on_connect, peer_addr) = data.take().unwrap(); + let (_, cfg, srv, on_connect, on_connect_data, peer_addr) = + data.take().unwrap(); self.set(State::H2(Dispatcher::new( - srv, conn, on_connect, cfg, None, peer_addr, + srv, + conn, + on_connect, + on_connect_data, + cfg, + None, + peer_addr, ))); self.poll(cx) } diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 795deacdc..05f01d240 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -411,8 +411,10 @@ async fn test_h2_on_connect() { let srv = test_server(move || { HttpService::build() .on_connect(|_| 10usize) + .on_connect_ext(|_, data| data.insert(20isize)) .h2(|req: Request| { assert!(req.extensions().contains::()); + assert!(req.extensions().contains::()); ok::<_, ()>(Response::Ok().finish()) }) .openssl(ssl_acceptor()) diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 0375b6f66..de6368fda 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -663,8 +663,10 @@ async fn test_h1_on_connect() { let srv = test_server(|| { HttpService::build() .on_connect(|_| 10usize) + .on_connect_ext(|_, data| data.insert(20isize)) .h1(|req: Request| { assert!(req.extensions().contains::()); + assert!(req.extensions().contains::()); future::ok::<_, ()>(Response::Ok().finish()) }) .tcp() diff --git a/examples/on_connect.rs b/examples/on_connect.rs new file mode 100644 index 000000000..bdad7e67e --- /dev/null +++ b/examples/on_connect.rs @@ -0,0 +1,51 @@ +//! This example shows how to use `actix_web::HttpServer::on_connect` to access a lower-level socket +//! properties and pass them to a handler through request-local data. +//! +//! For an example of extracting a client TLS certificate, see: +//! + +use std::{any::Any, env, io, net::SocketAddr}; + +use actix_web::{dev::Extensions, rt::net::TcpStream, web, App, HttpServer}; + +#[derive(Debug, Clone)] +struct ConnectionInfo { + bind: SocketAddr, + peer: SocketAddr, + ttl: Option, +} + +async fn route_whoami(conn_info: web::ReqData) -> String { + format!( + "Here is some info about your connection:\n\n{:#?}", + conn_info + ) +} + +fn get_conn_info(connection: &dyn Any, data: &mut Extensions) { + if let Some(sock) = connection.downcast_ref::() { + data.insert(ConnectionInfo { + bind: sock.local_addr().unwrap(), + peer: sock.peer_addr().unwrap(), + ttl: sock.ttl().ok(), + }); + } else { + unreachable!("connection should only be plaintext since no TLS is set up"); + } +} + +#[actix_web::main] +async fn main() -> io::Result<()> { + if env::var("RUST_LOG").is_err() { + env::set_var("RUST_LOG", "info"); + } + + env_logger::init(); + + HttpServer::new(|| App::new().default_service(web::to(route_whoami))) + .on_connect(get_conn_info) + .bind(("127.0.0.1", 8080))? + .workers(1) + .run() + .await +} diff --git a/src/server.rs b/src/server.rs index 2b86f7416..3badb6e8d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,8 +1,14 @@ -use std::marker::PhantomData; -use std::sync::{Arc, Mutex}; -use std::{fmt, io, net}; +use std::{ + any::Any, + fmt, io, + marker::PhantomData, + net, + sync::{Arc, Mutex}, +}; -use actix_http::{body::MessageBody, Error, HttpService, KeepAlive, Request, Response}; +use actix_http::{ + body::MessageBody, Error, Extensions, HttpService, KeepAlive, Request, Response, +}; use actix_server::{Server, ServerBuilder}; use actix_service::{map_config, IntoServiceFactory, Service, ServiceFactory}; @@ -64,6 +70,7 @@ where backlog: i32, sockets: Vec, builder: ServerBuilder, + on_connect_fn: Option>, _t: PhantomData<(S, B)>, } @@ -91,6 +98,32 @@ where backlog: 1024, sockets: Vec::new(), builder: ServerBuilder::default(), + on_connect_fn: None, + _t: PhantomData, + } + } + + /// Sets function that will be called once before each connection is handled. + /// It will receive a `&std::any::Any`, which contains underlying connection type and an + /// [Extensions] container so that request-local data can be passed to middleware and handlers. + /// + /// For example: + /// - `actix_tls::openssl::SslStream` when using openssl. + /// - `actix_tls::rustls::TlsStream` when using rustls. + /// - `actix_web::rt::net::TcpStream` when no encryption is used. + /// + /// See `on_connect` example for additional details. + pub fn on_connect(self, f: CB) -> HttpServer + where + CB: Fn(&dyn Any, &mut Extensions) + Send + Sync + 'static, + { + HttpServer { + factory: self.factory, + config: self.config, + backlog: self.backlog, + sockets: self.sockets, + builder: self.builder, + on_connect_fn: Some(Arc::new(f)), _t: PhantomData, } } @@ -240,6 +273,7 @@ where addr, scheme: "http", }); + let on_connect_fn = self.on_connect_fn.clone(); self.builder = self.builder.listen( format!("actix-web-service-{}", addr), @@ -252,11 +286,20 @@ where c.host.clone().unwrap_or_else(|| format!("{}", addr)), ); - HttpService::build() + let svc = HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) - .local_addr(addr) - .finish(map_config(factory(), move |_| cfg.clone())) + .local_addr(addr); + + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| { + (handler)(io as &dyn Any, ext) + }) + } else { + svc + }; + + svc.finish(map_config(factory(), move |_| cfg.clone())) .tcp() }, )?; @@ -289,6 +332,8 @@ where scheme: "https", }); + let on_connect_fn = self.on_connect_fn.clone(); + self.builder = self.builder.listen( format!("actix-web-service-{}", addr), lst, @@ -299,11 +344,21 @@ where addr, c.host.clone().unwrap_or_else(|| format!("{}", addr)), ); - HttpService::build() + + let svc = HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown) - .finish(map_config(factory(), move |_| cfg.clone())) + .client_disconnect(c.client_shutdown); + + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| { + (&*handler)(io as &dyn Any, ext) + }) + } else { + svc + }; + + svc.finish(map_config(factory(), move |_| cfg.clone())) .openssl(acceptor.clone()) }, )?; @@ -336,6 +391,8 @@ where scheme: "https", }); + let on_connect_fn = self.on_connect_fn.clone(); + self.builder = self.builder.listen( format!("actix-web-service-{}", addr), lst, @@ -346,11 +403,21 @@ where addr, c.host.clone().unwrap_or_else(|| format!("{}", addr)), ); - HttpService::build() + + let svc = HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown) - .finish(map_config(factory(), move |_| cfg.clone())) + .client_disconnect(c.client_shutdown); + + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| { + (handler)(io as &dyn Any, ext) + }) + } else { + svc + }; + + svc.finish(map_config(factory(), move |_| cfg.clone())) .rustls(config.clone()) }, )?; @@ -441,7 +508,7 @@ where } #[cfg(unix)] - /// Start listening for unix domain connections on existing listener. + /// Start listening for unix domain (UDS) connections on existing listener. pub fn listen_uds( mut self, lst: std::os::unix::net::UnixListener, @@ -460,6 +527,7 @@ where }); let addr = format!("actix-web-service-{:?}", lst.local_addr()?); + let on_connect_fn = self.on_connect_fn.clone(); self.builder = self.builder.listen_uds(addr, lst, move || { let c = cfg.lock().unwrap(); @@ -468,11 +536,23 @@ where socket_addr, c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), ); + pipeline_factory(|io: UnixStream| ok((io, Protocol::Http1, None))).and_then( - HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .finish(map_config(factory(), move |_| config.clone())), + { + let svc = HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout); + + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| { + (&*handler)(io as &dyn Any, ext) + }) + } else { + svc + }; + + svc.finish(map_config(factory(), move |_| config.clone())) + }, ) })?; Ok(self) From 4cb833616ad7c619367695068e0515dda37bf41b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 30 Oct 2020 02:10:05 +0000 Subject: [PATCH 03/81] deprecate builder if-x methods (#1760) --- actix-http/CHANGES.md | 4 ++++ actix-http/src/response.rs | 10 ++++++---- awc/CHANGES.md | 2 ++ awc/src/request.rs | 38 ++++++++++++++++++++++++-------------- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 0afb63a6d..8533cea55 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -7,6 +7,10 @@ ### Changed * Upgrade `base64` to `0.13`. * Upgrade `pin-project` to `1.0`. +* Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760] + +[#1760]: https://github.com/actix/actix-web/pull/1760 + [#1754]: https://github.com/actix/actix-web/pull/1754 diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 2def67168..df2f5be50 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -554,8 +554,9 @@ impl ResponseBuilder { self } - /// This method calls provided closure with builder reference if value is - /// true. + /// This method calls provided closure with builder reference if value is `true`. + #[doc(hidden)] + #[deprecated = "Use an if statement."] pub fn if_true(&mut self, value: bool, f: F) -> &mut Self where F: FnOnce(&mut ResponseBuilder), @@ -566,8 +567,9 @@ impl ResponseBuilder { self } - /// This method calls provided closure with builder reference if value is - /// Some. + /// This method calls provided closure with builder reference if value is `Some`. + #[doc(hidden)] + #[deprecated = "Use an if-let construction."] pub fn if_some(&mut self, value: Option, f: F) -> &mut Self where F: FnOnce(T, &mut ResponseBuilder), diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 0b02b3cfa..0eabe61e9 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,11 +3,13 @@ ## Unreleased - 2020-xx-xx ### Changed * Upgrade `base64` to `0.13`. +* Deprecate `ClientRequest::{if_some, if_true}`. [#1760] ### Fixed * Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature is enabled [#1737] [#1737]: https://github.com/actix/actix-web/pull/1737 +[#1760]: https://github.com/actix/actix-web/pull/1760 ## 2.0.0 - 2020-09-11 diff --git a/awc/src/request.rs b/awc/src/request.rs index 11e1da6a3..1e49aae3c 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -354,8 +354,9 @@ impl ClientRequest { self } - /// This method calls provided closure with builder reference if - /// value is `true`. + /// This method calls provided closure with builder reference if value is `true`. + #[doc(hidden)] + #[deprecated = "Use an if statement."] pub fn if_true(self, value: bool, f: F) -> Self where F: FnOnce(ClientRequest) -> ClientRequest, @@ -367,8 +368,9 @@ impl ClientRequest { } } - /// This method calls provided closure with builder reference if - /// value is `Some`. + /// This method calls provided closure with builder reference if value is `Some`. + #[doc(hidden)] + #[deprecated = "Use an if-let construction."] pub fn if_some(self, value: Option, f: F) -> Self where F: FnOnce(T, ClientRequest) -> ClientRequest, @@ -601,20 +603,27 @@ mod tests { #[actix_rt::test] async fn test_basics() { - let mut req = Client::new() + let req = Client::new() .put("/") .version(Version::HTTP_2) .set(header::Date(SystemTime::now().into())) .content_type("plain/text") - .if_true(true, |req| req.header(header::SERVER, "awc")) - .if_true(false, |req| req.header(header::EXPECT, "awc")) - .if_some(Some("server"), |val, req| { - req.header(header::USER_AGENT, val) - }) - .if_some(Option::<&str>::None, |_, req| { - req.header(header::ALLOW, "1") - }) - .content_length(100); + .header(header::SERVER, "awc"); + + let req = if let Some(val) = Some("server") { + req.header(header::USER_AGENT, val) + } else { + req + }; + + let req = if let Some(_val) = Option::<&str>::None { + req.header(header::ALLOW, "1") + } else { + req + }; + + let mut req = req.content_length(100); + assert!(req.headers().contains_key(header::CONTENT_TYPE)); assert!(req.headers().contains_key(header::DATE)); assert!(req.headers().contains_key(header::SERVER)); @@ -622,6 +631,7 @@ mod tests { assert!(!req.headers().contains_key(header::ALLOW)); assert!(!req.headers().contains_key(header::EXPECT)); assert_eq!(req.head.version, Version::HTTP_2); + let _ = req.headers_mut(); let _ = req.send_body(""); } From 798d744eefca690af7c61e9b4e0e501a6e9ad89f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 30 Oct 2020 02:19:56 +0000 Subject: [PATCH 04/81] prepare http release 2.1.0 --- actix-http/CHANGES.md | 11 +++++++---- actix-http/Cargo.toml | 4 ++-- actix-http/src/lib.rs | 4 +++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 8533cea55..bb5a962f5 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,18 +1,21 @@ # Changes ## Unreleased - 2020-xx-xx + + +## 2.1.0 - 2020-10-30 ### Added * Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] ### Changed -* Upgrade `base64` to `0.13`. -* Upgrade `pin-project` to `1.0`. +* Upgrade `base64` to `0.13`. [#1744] +* Upgrade `pin-project` to `1.0`. [#1733] * Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760] [#1760]: https://github.com/actix/actix-web/pull/1760 - - [#1754]: https://github.com/actix/actix-web/pull/1754 +[#1733]: https://github.com/actix/actix-web/pull/1733 +[#1744]: https://github.com/actix/actix-web/pull/1744 ## 2.0.0 - 2020-09-11 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 9d2f7464f..a2d55460d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "actix-http" -version = "2.0.0" +version = "2.1.0" authors = ["Nikolay Kim "] -description = "Actix HTTP primitives" +description = "HTTP primitives for the Actix ecosystem" readme = "README.md" keywords = ["actix", "http", "framework", "async", "futures"] homepage = "https://actix.rs" diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index e57a3727e..89d64fb77 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -1,4 +1,4 @@ -//! Basic HTTP primitives for the Actix ecosystem. +//! HTTP primitives for the Actix ecosystem. #![deny(rust_2018_idioms)] #![allow( @@ -8,6 +8,8 @@ clippy::borrow_interior_mutable_const )] #![allow(clippy::manual_strip)] // Allow this to keep MSRV(1.42). +#![doc(html_logo_url = "https://actix.rs/img/logo.png")] +#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #[macro_use] extern crate log; From 156c97cef2e480ab4a17e2233ef11c9add1e92ba Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 30 Oct 2020 02:50:53 +0000 Subject: [PATCH 05/81] prepare awc release 2.0.1 --- actix-http/README.md | 20 ++++++++++++-------- awc/CHANGES.md | 9 +++++++-- awc/Cargo.toml | 4 ++-- awc/README.md | 21 +++++++++++++-------- awc/src/lib.rs | 18 ++++++++++-------- 5 files changed, 44 insertions(+), 28 deletions(-) diff --git a/actix-http/README.md b/actix-http/README.md index 96fc54d2e..e536276ca 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -1,14 +1,18 @@ -# Actix http [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![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-http)](https://crates.io/crates/actix-http) [![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 -Actix http +> HTTP primitives for the Actix ecosystem. -## Documentation & community resources +[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=2.1.0)](https://docs.rs/actix-http/2.1.0) +![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http) +[![Dependency Status](https://deps.rs/crate/actix-http/2.1.0/status.svg)](https://deps.rs/crate/actix-http/2.1.0) +[![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -* [User Guide](https://actix.rs/docs/) -* [API Documentation](https://docs.rs/actix-http/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-http](https://crates.io/crates/actix-http) -* Minimum supported Rust version: 1.40 or later +## Documentation & Resources + +- [API Documentation](https://docs.rs/actix-http/2.1.0) +- [Chat on Gitter](https://gitter.im/actix/actix-web) +- Minimum Supported Rust Version (MSRV): 1.42.0 ## Example diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 0eabe61e9..a5090e7c9 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,15 +1,20 @@ # Changes ## Unreleased - 2020-xx-xx + + +## 2.0.1 - 2020-xx-xx ### Changed -* Upgrade `base64` to `0.13`. +* Upgrade `base64` to `0.13`. [#1744] * Deprecate `ClientRequest::{if_some, if_true}`. [#1760] ### Fixed -* Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature is enabled [#1737] +* Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature + is enabled [#1737] [#1737]: https://github.com/actix/actix-web/pull/1737 [#1760]: https://github.com/actix/actix-web/pull/1760 +[#1744]: https://github.com/actix/actix-web/pull/1744 ## 2.0.0 - 2020-09-11 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b7d8b0a22..049c8e664 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "awc" -version = "2.0.0" +version = "2.0.1" authors = ["Nikolay Kim "] -description = "Async HTTP client library that uses the Actix runtime." +description = "Async HTTP and WebSocket client library built on the Actix ecosystem" readme = "README.md" keywords = ["actix", "http", "framework", "async", "web"] homepage = "https://actix.rs" diff --git a/awc/README.md b/awc/README.md index 2b6309c1d..fb2468b4a 100644 --- a/awc/README.md +++ b/awc/README.md @@ -1,14 +1,19 @@ -# Actix http client [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![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/awc)](https://crates.io/crates/awc) [![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) +# awc (Actix Web Client) -An HTTP Client +> Async HTTP and WebSocket client library. -## Documentation & community resources +[![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) +[![Documentation](https://docs.rs/awc/badge.svg?version=2.0.1)](https://docs.rs/awc/2.0.1) +![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/awc) +[![Dependency Status](https://deps.rs/crate/awc/2.0.1/status.svg)](https://deps.rs/crate/awc/2.0.1) +[![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -* [User Guide](https://actix.rs/docs/) -* [API Documentation](https://docs.rs/awc/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [awc](https://crates.io/crates/awc) -* Minimum supported Rust version: 1.40 or later +## Documentation & Resources + +- [API Documentation](https://docs.rs/awc/2.0.1) +- [Example Project](https://github.com/actix/examples/tree/HEAD/awc_https) +- [Chat on Gitter](https://gitter.im/actix/actix-web) +- Minimum Supported Rust Version (MSRV): 1.42.0 ## Example diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 45c52092a..fb6ed086a 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -1,11 +1,4 @@ -#![deny(rust_2018_idioms)] -#![allow( - clippy::type_complexity, - clippy::borrow_interior_mutable_const, - clippy::needless_doctest_main -)] - -//! `awc` is a HTTP and WebSocket client library built using the Actix ecosystem. +//! `awc` is a HTTP and WebSocket client library built on the Actix ecosystem. //! //! ## Making a GET request //! @@ -91,6 +84,15 @@ //! # } //! ``` +#![deny(rust_2018_idioms)] +#![allow( + clippy::type_complexity, + clippy::borrow_interior_mutable_const, + clippy::needless_doctest_main +)] +#![doc(html_logo_url = "https://actix.rs/img/logo.png")] +#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] + use std::cell::RefCell; use std::convert::TryFrom; use std::rc::Rc; From 42f51eb962af48b85b944fe64d24f4a81ad4650e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 30 Oct 2020 03:15:22 +0000 Subject: [PATCH 06/81] prepare web release 3.2.0 --- CHANGES.md | 4 ++++ Cargo.toml | 10 +++++----- README.md | 6 ++++-- awc/CHANGES.md | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 15d44b75c..cc41f839b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2020-xx-xx + + +## 3.2.0 - 2020-10-30 ### Added * Implement `exclude_regex` for Logger middleware. [#1723] * Add request-local data extractor `web::ReqData`. [#1748] @@ -9,6 +12,7 @@ * Expose `on_connect` for access to the connection stream before request is handled. [#1754] ### Changed +* Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. * Print non-configured `Data` type when attempting extraction. [#1743] * Re-export bytes::Buf{Mut} in web module. [#1750] * Upgrade `pin-project` to `1.0`. diff --git a/Cargo.toml b/Cargo.toml index 4fafc61c4..33ec52fae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "actix-web" -version = "3.1.0" +version = "3.2.0" authors = ["Nikolay Kim "] -description = "Actix web is a powerful, pragmatic, and extremely fast web framework for Rust." +description = "Actix web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" keywords = ["actix", "http", "web", "framework", "async"] homepage = "https://actix.rs" @@ -84,8 +84,8 @@ actix-macros = "0.1.0" actix-threadpool = "0.3.1" actix-tls = "2.0.0" -actix-web-codegen = "0.3.0" -actix-http = "2.0.0" +actix-web-codegen = "0.4.0" +actix-http = "2.1.0" awc = { version = "2.0.0", default-features = false } bytes = "0.5.3" @@ -111,7 +111,7 @@ tinyvec = { version = "1", features = ["alloc"] } [dev-dependencies] actix = "0.10.0" -actix-http = { version = "2.0.0", features = ["actors"] } +actix-http = { version = "2.1.0", features = ["actors"] } rand = "0.7" env_logger = "0.8" serde_derive = "1.0" diff --git a/README.md b/README.md index 3e3ce8bf1..4a0dae4f8 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,17 @@

-[![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg)](https://docs.rs/actix-web) +[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=3.2.0)](https://docs.rs/actix-web/3.2.0) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) ![License](https://img.shields.io/crates/l/actix-web.svg) +[![Dependency Status](https://deps.rs/crate/actix-web/3.2.0/status.svg)](https://deps.rs/crate/actix-web/2.2.0)
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![Download](https://img.shields.io/crates/d/actix-web.svg)](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) +[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

diff --git a/awc/CHANGES.md b/awc/CHANGES.md index a5090e7c9..5e87362f0 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,7 +3,7 @@ ## Unreleased - 2020-xx-xx -## 2.0.1 - 2020-xx-xx +## 2.0.1 - 2020-10-30 ### Changed * Upgrade `base64` to `0.13`. [#1744] * Deprecate `ClientRequest::{if_some, if_true}`. [#1760] From 22b451cf2d3c76872bb337482387c5aa9c8f8cb1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 31 Oct 2020 02:39:54 +0000 Subject: [PATCH 07/81] fix deps.rs badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a0dae4f8..b11a8ee7c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Documentation](https://docs.rs/actix-web/badge.svg?version=3.2.0)](https://docs.rs/actix-web/3.2.0) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) ![License](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/3.2.0/status.svg)](https://deps.rs/crate/actix-web/2.2.0) +[![Dependency Status](https://deps.rs/crate/actix-web/3.2.0/status.svg)](https://deps.rs/crate/actix-web/3.2.0)
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) From 5135c1e3a08c140876f5470c5042961d31e3ed19 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Sat, 31 Oct 2020 12:06:51 +0900 Subject: [PATCH 08/81] Update CoC contact information --- CODE_OF_CONDUCT.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 599b28c0d..ae97b3240 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -34,10 +34,13 @@ This Code of Conduct applies both within project spaces and in public spaces whe ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fafhrd91@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at robjtede@icloud.com ([@robjtede]) or huyuumi@neet.club ([@JohnTitor]). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. +[@robjtede]: https://github.com/robjtede +[@JohnTitor]: https://github.com/JohnTitor + ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] From b6385c2b4e00d3fda44fbac0533653ded8135f5b Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Sat, 31 Oct 2020 12:12:19 +0900 Subject: [PATCH 09/81] Remove CoC on actix-http as duplicated --- actix-http/CODE_OF_CONDUCT.md | 46 ----------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 actix-http/CODE_OF_CONDUCT.md diff --git a/actix-http/CODE_OF_CONDUCT.md b/actix-http/CODE_OF_CONDUCT.md deleted file mode 100644 index 599b28c0d..000000000 --- a/actix-http/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fafhrd91@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ From 5468c3c410e408b21e4cfbd4034b72935b24cdcf Mon Sep 17 00:00:00 2001 From: Sebastian Mayr Date: Mon, 2 Nov 2020 03:44:14 -0500 Subject: [PATCH 10/81] Drop content length headers from 101 responses (#1767) Co-authored-by: Sebastian Mayr --- actix-http/CHANGES.md | 4 ++++ actix-http/src/h1/encoder.rs | 39 ++++++++++++++++++++++++++++++------ actix-web-actors/src/ws.rs | 9 ++++----- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index bb5a962f5..edb1409b2 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2020-xx-xx +### Fixed +* Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] + +[#1767]: https://github.com/actix/actix-web/pull/1767 ## 2.1.0 - 2020-10-30 diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index e16b4c3dc..b7648eff1 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -64,14 +64,17 @@ pub(crate) trait MessageType: Sized { // Content length if let Some(status) = self.status() { match status { - StatusCode::NO_CONTENT - | StatusCode::CONTINUE - | StatusCode::PROCESSING => length = BodySize::None, - StatusCode::SWITCHING_PROTOCOLS => { + StatusCode::CONTINUE + | StatusCode::SWITCHING_PROTOCOLS + | StatusCode::PROCESSING + | StatusCode::NO_CONTENT => { + // skip content-length and transfer-encoding headers + // See https://tools.ietf.org/html/rfc7230#section-3.3.1 + // and https://tools.ietf.org/html/rfc7230#section-3.3.2 skip_len = true; - length = BodySize::Stream; + length = BodySize::None } - _ => (), + _ => {} } } match length { @@ -676,4 +679,28 @@ mod tests { assert!(data.contains("authorization: another authorization\r\n")); assert!(data.contains("date: date\r\n")); } + + #[test] + fn test_no_content_length() { + let mut bytes = BytesMut::with_capacity(2048); + + let mut res: Response<()> = + Response::new(StatusCode::SWITCHING_PROTOCOLS).into_body::<()>(); + res.headers_mut() + .insert(DATE, HeaderValue::from_static(&"")); + res.headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::from_static(&"0")); + + let _ = res.encode_headers( + &mut bytes, + Version::HTTP_11, + BodySize::Stream, + ConnectionType::Upgrade, + &ServiceConfig::default(), + ); + let data = + String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); + assert!(!data.contains("content-length: 0\r\n")); + assert!(!data.contains("transfer-encoding: chunked\r\n")); + } } diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 3f5972532..8fd03f6a1 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -164,7 +164,6 @@ pub fn handshake_with_protocols( let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS) .upgrade("websocket") - .header(header::TRANSFER_ENCODING, "chunked") .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) .take(); @@ -664,10 +663,10 @@ mod tests { ) .to_http_request(); - assert_eq!( - StatusCode::SWITCHING_PROTOCOLS, - handshake(&req).unwrap().finish().status() - ); + let resp = handshake(&req).unwrap().finish(); + assert_eq!(StatusCode::SWITCHING_PROTOCOLS, resp.status()); + assert_eq!(None, resp.headers().get(&header::CONTENT_LENGTH)); + assert_eq!(None, resp.headers().get(&header::TRANSFER_ENCODING)); let req = TestRequest::default() .header( From 61b65aa64ad9b881e9f21d2dc0ff7d554c1937c3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 2 Nov 2020 09:23:18 +0000 Subject: [PATCH 11/81] add common 1xx http response builders (#1768) Co-authored-by: Yuki Okushi --- actix-http/CHANGES.md | 4 ++ actix-http/src/httpcodes.rs | 104 +++++++++++++++++++----------------- 2 files changed, 59 insertions(+), 49 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index edb1409b2..2f4e44142 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,10 +1,14 @@ # Changes ## Unreleased - 2020-xx-xx +### Added +* HttpResponse builders for 1xx status codes. [#1768] + ### Fixed * Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] [#1767]: https://github.com/actix/actix-web/pull/1767 +[#1768]: https://github.com/actix/actix-web/pull/1768 ## 2.1.0 - 2020-10-30 diff --git a/actix-http/src/httpcodes.rs b/actix-http/src/httpcodes.rs index 0c7f23fc8..f421d8e23 100644 --- a/actix-http/src/httpcodes.rs +++ b/actix-http/src/httpcodes.rs @@ -1,10 +1,12 @@ -//! Basic http responses +//! Status code based HTTP response builders. + #![allow(non_upper_case_globals)] + use http::StatusCode; use crate::response::{Response, ResponseBuilder}; -macro_rules! STATIC_RESP { +macro_rules! static_resp { ($name:ident, $status:expr) => { #[allow(non_snake_case, missing_docs)] pub fn $name() -> ResponseBuilder { @@ -14,63 +16,67 @@ macro_rules! STATIC_RESP { } impl Response { - STATIC_RESP!(Ok, StatusCode::OK); - STATIC_RESP!(Created, StatusCode::CREATED); - STATIC_RESP!(Accepted, StatusCode::ACCEPTED); - STATIC_RESP!( + static_resp!(Continue, StatusCode::CONTINUE); + static_resp!(SwitchingProtocols, StatusCode::SWITCHING_PROTOCOLS); + static_resp!(Processing, StatusCode::PROCESSING); + + static_resp!(Ok, StatusCode::OK); + static_resp!(Created, StatusCode::CREATED); + static_resp!(Accepted, StatusCode::ACCEPTED); + static_resp!( NonAuthoritativeInformation, StatusCode::NON_AUTHORITATIVE_INFORMATION ); - STATIC_RESP!(NoContent, StatusCode::NO_CONTENT); - STATIC_RESP!(ResetContent, StatusCode::RESET_CONTENT); - STATIC_RESP!(PartialContent, StatusCode::PARTIAL_CONTENT); - STATIC_RESP!(MultiStatus, StatusCode::MULTI_STATUS); - STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED); + static_resp!(NoContent, StatusCode::NO_CONTENT); + static_resp!(ResetContent, StatusCode::RESET_CONTENT); + static_resp!(PartialContent, StatusCode::PARTIAL_CONTENT); + static_resp!(MultiStatus, StatusCode::MULTI_STATUS); + static_resp!(AlreadyReported, StatusCode::ALREADY_REPORTED); - STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); - STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY); - STATIC_RESP!(Found, StatusCode::FOUND); - STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER); - STATIC_RESP!(NotModified, StatusCode::NOT_MODIFIED); - STATIC_RESP!(UseProxy, StatusCode::USE_PROXY); - STATIC_RESP!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT); - STATIC_RESP!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT); + static_resp!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); + static_resp!(MovedPermanently, StatusCode::MOVED_PERMANENTLY); + static_resp!(Found, StatusCode::FOUND); + static_resp!(SeeOther, StatusCode::SEE_OTHER); + static_resp!(NotModified, StatusCode::NOT_MODIFIED); + static_resp!(UseProxy, StatusCode::USE_PROXY); + static_resp!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT); + static_resp!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT); - STATIC_RESP!(BadRequest, StatusCode::BAD_REQUEST); - STATIC_RESP!(NotFound, StatusCode::NOT_FOUND); - STATIC_RESP!(Unauthorized, StatusCode::UNAUTHORIZED); - STATIC_RESP!(PaymentRequired, StatusCode::PAYMENT_REQUIRED); - STATIC_RESP!(Forbidden, StatusCode::FORBIDDEN); - STATIC_RESP!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); - STATIC_RESP!(NotAcceptable, StatusCode::NOT_ACCEPTABLE); - STATIC_RESP!( + static_resp!(BadRequest, StatusCode::BAD_REQUEST); + static_resp!(NotFound, StatusCode::NOT_FOUND); + static_resp!(Unauthorized, StatusCode::UNAUTHORIZED); + static_resp!(PaymentRequired, StatusCode::PAYMENT_REQUIRED); + static_resp!(Forbidden, StatusCode::FORBIDDEN); + static_resp!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); + static_resp!(NotAcceptable, StatusCode::NOT_ACCEPTABLE); + static_resp!( ProxyAuthenticationRequired, StatusCode::PROXY_AUTHENTICATION_REQUIRED ); - STATIC_RESP!(RequestTimeout, StatusCode::REQUEST_TIMEOUT); - STATIC_RESP!(Conflict, StatusCode::CONFLICT); - 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); - STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); - STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); - STATIC_RESP!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY); - STATIC_RESP!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS); + static_resp!(RequestTimeout, StatusCode::REQUEST_TIMEOUT); + static_resp!(Conflict, StatusCode::CONFLICT); + 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); + static_resp!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); + static_resp!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); + static_resp!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY); + static_resp!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS); - STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); - STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED); - STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY); - STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); - STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); - STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED); - STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES); - STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); - STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED); + static_resp!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); + static_resp!(NotImplemented, StatusCode::NOT_IMPLEMENTED); + static_resp!(BadGateway, StatusCode::BAD_GATEWAY); + static_resp!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); + static_resp!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); + static_resp!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED); + static_resp!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES); + static_resp!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); + static_resp!(LoopDetected, StatusCode::LOOP_DETECTED); } #[cfg(test)] From ceac97bb8d11053002987a3ba735fd265a9d65ea Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 4 Nov 2020 15:08:12 +0000 Subject: [PATCH 12/81] Update config.yml --- .github/ISSUE_TEMPLATE/config.yml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3e62958d8..d8c6d66ca 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,15 @@ blank_issues_enabled: true contact_links: - - name: Gitter channel (actix-web) + - name: GitHub Discussions + url: https://github.com/actix/actix-web/discussions + about: Actix Web Q&A + - name: Gitter chat (actix-web) url: https://gitter.im/actix/actix-web - about: Please ask and answer questions about the actix-web here. - - name: Gitter channel (actix) + about: Actix Web Q&A + - name: Gitter chat (actix) url: https://gitter.im/actix/actix - about: Please ask and answer questions about the actix here. + about: Actix (actor framework) Q&A + - name: Actix Discord + url: https://discord.gg/NWpN5mmg3x + about: Actix developer discussion and community chat + From 9b6a089b3675d07805ce4b184b6a317418c7e5da Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 5 Nov 2020 06:20:01 +0800 Subject: [PATCH 13/81] fix awc doc example (#1772) * fix awc readme example Co-authored-by: Rob Ede --- awc/README.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/awc/README.md b/awc/README.md index fb2468b4a..cbe299aaf 100644 --- a/awc/README.md +++ b/awc/README.md @@ -16,23 +16,21 @@ - Minimum Supported Rust Version (MSRV): 1.42.0 ## Example - ```rust use actix_rt::System; use awc::Client; -use futures::future::{Future, lazy}; fn main() { - System::new("test").block_on(lazy(|| { - let mut client = Client::default(); + System::new("test").block_on(async { + let client = Client::default(); - client.get("http://www.rust-lang.org") // <- Create request builder - .header("User-Agent", "Actix-web") - .send() // <- Send http request - .and_then(|response| { // <- server http response - println!("Response: {:?}", response); - Ok(()) - }) - })); + let res = client + .get("http://www.rust-lang.org") // <- Create request builder + .header("User-Agent", "Actix-web") + .send() // <- Send http request + .await; + + println!("Response: {:?}", res); // <- server http response + }); } ``` From 4bfd5c278177847623463594edb0187186c304a4 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Fri, 6 Nov 2020 01:36:15 +0900 Subject: [PATCH 14/81] Upgrade `serde_urlencoded` to `0.7` (#1773) --- CHANGES.md | 2 ++ Cargo.toml | 2 +- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 2 +- awc/CHANGES.md | 3 ++- awc/Cargo.toml | 2 +- test-server/CHANGES.md | 1 + test-server/Cargo.toml | 2 +- 8 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index cc41f839b..9277d5bd2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2020-xx-xx +### Changed +* Upgrade `serde_urlencoded` to `0.7`. ## 3.2.0 - 2020-10-30 diff --git a/Cargo.toml b/Cargo.toml index 33ec52fae..da511888a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,7 +102,7 @@ pin-project = "1.0.0" regex = "1.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -serde_urlencoded = "0.6.1" +serde_urlencoded = "0.7" time = { version = "0.2.7", default-features = false, features = ["std"] } url = "2.1" open-ssl = { package = "openssl", version = "0.10", optional = true } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 2f4e44142..eeed4e14b 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -7,6 +7,9 @@ ### Fixed * Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] +### Changed +* Upgrade `serde_urlencoded` to `0.7`. + [#1767]: https://github.com/actix/actix-web/pull/1767 [#1768]: https://github.com/actix/actix-web/pull/1768 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index a2d55460d..31495e395 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -78,7 +78,7 @@ serde = "1.0" serde_json = "1.0" sha-1 = "0.9" slab = "0.4" -serde_urlencoded = "0.6.1" +serde_urlencoded = "0.7" time = { version = "0.2.7", default-features = false, features = ["std"] } # compression diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 5e87362f0..e184dfbd1 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,7 +1,8 @@ # Changes ## Unreleased - 2020-xx-xx - +### Changed +* Upgrade `serde_urlencoded` to `0.7`. ## 2.0.1 - 2020-10-30 ### Changed diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 049c8e664..d92996fb9 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -53,7 +53,7 @@ percent-encoding = "2.1" rand = "0.7" serde = "1.0" serde_json = "1.0" -serde_urlencoded = "0.6.1" +serde_urlencoded = "0.7" open-ssl = { version = "0.10", package = "openssl", optional = true } rust-tls = { version = "0.18.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index e3a59e5bf..845b6e2dc 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -4,6 +4,7 @@ * add ability to set address for `TestServer` [#1645] * Upgrade `base64` to `0.13`. +* Upgrade `serde_urlencoded` to `0.7`. [#1645]: https://github.com/actix/actix-web/pull/1645 diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 3ee6b8a30..87db93469 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -47,7 +47,7 @@ socket2 = "0.3" serde = "1.0" serde_json = "1.0" slab = "0.4" -serde_urlencoded = "0.6.1" +serde_urlencoded = "0.7" time = { version = "0.2.7", default-features = false, features = ["std"] } open-ssl = { version = "0.10", package = "openssl", optional = true } From e5b86d189c1326f6260ee1d8c4b5af14d93afdff Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 5 Nov 2020 18:46:17 +0100 Subject: [PATCH 15/81] Fix typo in request_data.rs (#1774) --- src/request_data.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/request_data.rs b/src/request_data.rs index c01930418..285154884 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -50,7 +50,7 @@ use crate::{dev::Payload, FromRequest, HttpRequest}; pub struct ReqData(T); impl ReqData { - /// Consumes the `ReqData`, returning it's wrapped data. + /// Consumes the `ReqData`, returning its wrapped data. pub fn into_inner(self) -> T { self.0 } From 9b42333fac0c37dd2a68a3f7cec66b23e344a63b Mon Sep 17 00:00:00 2001 From: Pouya Mobasher Behrouz Date: Fri, 6 Nov 2020 17:04:42 +0330 Subject: [PATCH 16/81] Fix typo in Query extractor docs (#1777) --- src/types/query.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/query.rs b/src/types/query.rs index f9440e1b4..7eded49c5 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -39,7 +39,7 @@ use crate::request::HttpRequest; /// } /// /// // Use `Query` extractor for query information (and destructure it within the signature). -/// // This handler gets called only if the request's query string contains a `username` field. +/// // This handler gets called only if the request's query string contains `id` and `response_type` fields. /// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`. /// async fn index(web::Query(info): web::Query) -> String { /// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) @@ -117,7 +117,7 @@ impl fmt::Display for Query { /// } /// /// // Use `Query` extractor for query information. -/// // This handler get called only if request's query contains `username` field +/// // This handler get called only if request's query contains `id` and `response_type` fields. /// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` /// async fn index(info: web::Query) -> String { /// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) From 49e945c88f9582eb1bf7b52b22975b3aef2a3200 Mon Sep 17 00:00:00 2001 From: Aravinth Date: Mon, 9 Nov 2020 19:31:36 +0530 Subject: [PATCH 17/81] switching to nightly for intra-doc links (#1783) --- .github/workflows/upload-doc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index 99fa663cc..cdf05a979 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -16,7 +16,7 @@ jobs: - name: Install Rust uses: actions-rs/toolchain@v1 with: - toolchain: stable-x86_64-unknown-linux-gnu + toolchain: nightly-x86_64-unknown-linux-gnu profile: minimal override: true From a929209967f87577387c16831427bb7801677cc1 Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Wed, 11 Nov 2020 05:24:38 +0530 Subject: [PATCH 18/81] `actix-files` intra-doc migration (#1785) --- actix-files/src/named.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index dacb51136..a9b95bad1 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -193,7 +193,7 @@ impl NamedFile { /// 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). + /// [`std::ffi::OsStr::to_string_lossy`] #[inline] pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { self.content_disposition = cd; From 4100c50c703798027203310502d3e9a860b9a032 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 20 Nov 2020 18:02:41 +0000 Subject: [PATCH 19/81] add either extractor (#1788) --- CHANGES.md | 5 + src/error.rs | 1 + src/lib.rs | 3 +- src/responder.rs | 76 ------------ src/types/either.rs | 274 ++++++++++++++++++++++++++++++++++++++++++++ src/types/mod.rs | 2 + 6 files changed, 284 insertions(+), 77 deletions(-) create mode 100644 src/types/either.rs diff --git a/CHANGES.md b/CHANGES.md index 9277d5bd2..b928712d0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,14 @@ # Changes ## Unreleased - 2020-xx-xx +### Added +* Add `Either` extractor helper. [#1788] + ### Changed * Upgrade `serde_urlencoded` to `0.7`. +[#1788]: https://github.com/actix/actix-web/pull/1788 + ## 3.2.0 - 2020-10-30 ### Added diff --git a/src/error.rs b/src/error.rs index 659ba05fd..60af8fa11 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,5 @@ //! Error and Result module + pub use actix_http::error::*; use derive_more::{Display, From}; use serde_json::error::Error as JsonError; diff --git a/src/lib.rs b/src/lib.rs index 088444e05..13e02c098 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,10 +102,11 @@ pub use crate::app::App; pub use crate::extract::FromRequest; pub use crate::request::HttpRequest; pub use crate::resource::Resource; -pub use crate::responder::{Either, Responder}; +pub use crate::responder::Responder; pub use crate::route::Route; pub use crate::scope::Scope; pub use crate::server::HttpServer; +pub use crate::types::{Either, EitherExtractError}; pub mod dev { //! The `actix-web` prelude for library developers diff --git a/src/responder.rs b/src/responder.rs index fc80831b8..d1c22323f 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -332,82 +332,6 @@ impl Future for CustomResponderFut { } } -/// Combines two different responder types into a single type -/// -/// ```rust -/// use actix_web::{Either, Error, HttpResponse}; -/// -/// type RegisterResult = Either>; -/// -/// fn index() -> RegisterResult { -/// if is_a_variant() { -/// // <- choose left variant -/// Either::A(HttpResponse::BadRequest().body("Bad data")) -/// } else { -/// Either::B( -/// // <- Right variant -/// Ok(HttpResponse::Ok() -/// .content_type("text/html") -/// .body("Hello!")) -/// ) -/// } -/// } -/// # fn is_a_variant() -> bool { true } -/// # fn main() {} -/// ``` -#[derive(Debug, PartialEq)] -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 Error = Error; - type Future = EitherResponder; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { - match self { - Either::A(a) => EitherResponder::A(a.respond_to(req)), - Either::B(b) => EitherResponder::B(b.respond_to(req)), - } - } -} - -#[pin_project(project = EitherResponderProj)] -pub enum EitherResponder -where - A: Responder, - B: Responder, -{ - A(#[pin] A::Future), - B(#[pin] B::Future), -} - -impl Future for EitherResponder -where - A: Responder, - B: Responder, -{ - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.project() { - EitherResponderProj::A(fut) => { - Poll::Ready(ready!(fut.poll(cx)).map_err(|e| e.into())) - } - EitherResponderProj::B(fut) => { - Poll::Ready(ready!(fut.poll(cx).map_err(|e| e.into()))) - } - } - } -} - impl Responder for InternalError where T: std::fmt::Debug + std::fmt::Display + 'static, diff --git a/src/types/either.rs b/src/types/either.rs new file mode 100644 index 000000000..9f1d81a0b --- /dev/null +++ b/src/types/either.rs @@ -0,0 +1,274 @@ +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use actix_http::{Error, Response}; +use bytes::Bytes; +use futures_util::{future::LocalBoxFuture, ready, FutureExt, TryFutureExt}; +use pin_project::pin_project; + +use crate::{dev, request::HttpRequest, FromRequest, Responder}; + +/// Combines two different responder types into a single type +/// +/// ```rust +/// use actix_web::{Either, Error, HttpResponse}; +/// +/// type RegisterResult = Either>; +/// +/// fn index() -> RegisterResult { +/// if is_a_variant() { +/// // <- choose left variant +/// Either::A(HttpResponse::BadRequest().body("Bad data")) +/// } else { +/// Either::B( +/// // <- Right variant +/// Ok(HttpResponse::Ok() +/// .content_type("text/html") +/// .body("Hello!")) +/// ) +/// } +/// } +/// # fn is_a_variant() -> bool { true } +/// # fn main() {} +/// ``` +#[derive(Debug, PartialEq)] +pub enum Either { + /// First branch of the type + A(A), + /// Second branch of the type + B(B), +} + +#[cfg(test)] +impl Either { + pub(self) fn unwrap_left(self) -> A { + match self { + Either::A(data) => data, + Either::B(_) => { + panic!("Cannot unwrap left branch. Either contains a right branch.") + } + } + } + + pub(self) fn unwrap_right(self) -> B { + match self { + Either::A(_) => { + panic!("Cannot unwrap right branch. Either contains a left branch.") + } + Either::B(data) => data, + } + } +} + +impl Responder for Either +where + A: Responder, + B: Responder, +{ + type Error = Error; + type Future = EitherResponder; + + fn respond_to(self, req: &HttpRequest) -> Self::Future { + match self { + Either::A(a) => EitherResponder::A(a.respond_to(req)), + Either::B(b) => EitherResponder::B(b.respond_to(req)), + } + } +} + +#[pin_project(project = EitherResponderProj)] +pub enum EitherResponder +where + A: Responder, + B: Responder, +{ + A(#[pin] A::Future), + B(#[pin] B::Future), +} + +impl Future for EitherResponder +where + A: Responder, + B: Responder, +{ + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.project() { + EitherResponderProj::A(fut) => { + Poll::Ready(ready!(fut.poll(cx)).map_err(|e| e.into())) + } + EitherResponderProj::B(fut) => { + Poll::Ready(ready!(fut.poll(cx).map_err(|e| e.into()))) + } + } + } +} + +/// A composite error resulting from failure to extract an `Either`. +/// +/// The implementation of `Into` will return the payload buffering error or the +/// error from the primary extractor. To access the fallback error, use a match clause. +#[derive(Debug)] +pub enum EitherExtractError { + /// Error from payload buffering, such as exceeding payload max size limit. + Bytes(Error), + + /// Error from primary extractor. + Extract(A, B), +} + +impl Into for EitherExtractError +where + A: Into, + B: Into, +{ + fn into(self) -> Error { + match self { + EitherExtractError::Bytes(err) => err, + EitherExtractError::Extract(a_err, _b_err) => a_err.into(), + } + } +} + +/// Provides a mechanism for trying two extractors, a primary and a fallback. Useful for +/// "polymorphic payloads" where, for example, a form might be JSON or URL encoded. +/// +/// It is important to note that this extractor, by necessity, buffers the entire request payload +/// as part of its implementation. Though, it does respect a `PayloadConfig`'s maximum size limit. +impl FromRequest for Either +where + A: FromRequest + 'static, + B: FromRequest + 'static, +{ + type Error = EitherExtractError; + type Future = LocalBoxFuture<'static, Result>; + type Config = (); + + fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { + let req2 = req.clone(); + + Bytes::from_request(req, payload) + .map_err(EitherExtractError::Bytes) + .and_then(|bytes| bytes_to_a_or_b(req2, bytes)) + .boxed_local() + } +} + +async fn bytes_to_a_or_b( + req: HttpRequest, + bytes: Bytes, +) -> Result, EitherExtractError> +where + A: FromRequest + 'static, + B: FromRequest + 'static, +{ + let fallback = bytes.clone(); + let a_err; + + let mut pl = payload_from_bytes(bytes); + match A::from_request(&req, &mut pl).await { + Ok(a_data) => return Ok(Either::A(a_data)), + // store A's error for returning if B also fails + Err(err) => a_err = err, + }; + + let mut pl = payload_from_bytes(fallback); + match B::from_request(&req, &mut pl).await { + Ok(b_data) => return Ok(Either::B(b_data)), + Err(b_err) => Err(EitherExtractError::Extract(a_err, b_err)), + } +} + +fn payload_from_bytes(bytes: Bytes) -> dev::Payload { + let (_, mut h1_payload) = actix_http::h1::Payload::create(true); + h1_payload.unread_data(bytes); + dev::Payload::from(h1_payload) +} + +#[cfg(test)] +mod tests { + use serde::{Deserialize, Serialize}; + + use super::*; + use crate::{ + test::TestRequest, + web::{Form, Json}, + }; + + #[derive(Debug, Clone, Serialize, Deserialize)] + struct TestForm { + hello: String, + } + + #[actix_rt::test] + async fn test_either_extract_first_try() { + let (req, mut pl) = TestRequest::default() + .set_form(&TestForm { + hello: "world".to_owned(), + }) + .to_http_parts(); + + let form = Either::, Json>::from_request(&req, &mut pl) + .await + .unwrap() + .unwrap_left() + .into_inner(); + assert_eq!(&form.hello, "world"); + } + + #[actix_rt::test] + async fn test_either_extract_fallback() { + let (req, mut pl) = TestRequest::default() + .set_json(&TestForm { + hello: "world".to_owned(), + }) + .to_http_parts(); + + let form = Either::, Json>::from_request(&req, &mut pl) + .await + .unwrap() + .unwrap_right() + .into_inner(); + assert_eq!(&form.hello, "world"); + } + + #[actix_rt::test] + async fn test_either_extract_recursive_fallback() { + let (req, mut pl) = TestRequest::default() + .set_payload(Bytes::from_static(b"!@$%^&*()")) + .to_http_parts(); + + let payload = + Either::, Json>, Bytes>::from_request( + &req, &mut pl, + ) + .await + .unwrap() + .unwrap_right(); + assert_eq!(&payload.as_ref(), &b"!@$%^&*()"); + } + + #[actix_rt::test] + async fn test_either_extract_recursive_fallback_inner() { + let (req, mut pl) = TestRequest::default() + .set_json(&TestForm { + hello: "world".to_owned(), + }) + .to_http_parts(); + + let form = + Either::, Json>, Bytes>::from_request( + &req, &mut pl, + ) + .await + .unwrap() + .unwrap_left() + .unwrap_right() + .into_inner(); + assert_eq!(&form.hello, "world"); + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index b32711e2a..cedf86dd2 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,5 +1,6 @@ //! Helper types +mod either; pub(crate) mod form; pub(crate) mod json; mod path; @@ -7,6 +8,7 @@ pub(crate) mod payload; mod query; pub(crate) mod readlines; +pub use self::either::{Either, EitherExtractError}; pub use self::form::{Form, FormConfig}; pub use self::json::{Json, JsonConfig}; pub use self::path::{Path, PathConfig}; From 2f11ef089bed1b03f5312b80068e2c50deda325b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 24 Nov 2020 00:29:13 +0000 Subject: [PATCH 20/81] fix rustdoc uploads --- .github/workflows/upload-doc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index cdf05a979..ba87a5637 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -30,7 +30,7 @@ jobs: run: echo "" > target/doc/index.html - name: Deploy to GitHub Pages - uses: JamesIves/github-pages-deploy-action@3.5.8 + uses: JamesIves/github-pages-deploy-action@3.7.1 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} BRANCH: gh-pages From 70f4747a2391ce870c0a33383a2ddc38c7bb1e99 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 24 Nov 2020 10:08:57 +0000 Subject: [PATCH 21/81] add method for getting accept type preference (#1793) --- actix-http/CHANGES.md | 2 + actix-http/src/header/common/accept.rs | 141 ++++++++++++++++++- actix-http/src/header/shared/quality_item.rs | 4 +- 3 files changed, 138 insertions(+), 9 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index eeed4e14b..cafaa5e09 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - 2020-xx-xx ### Added * HttpResponse builders for 1xx status codes. [#1768] +* `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] ### Fixed * Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] @@ -12,6 +13,7 @@ [#1767]: https://github.com/actix/actix-web/pull/1767 [#1768]: https://github.com/actix/actix-web/pull/1768 +[#1793]: https://github.com/actix/actix-web/pull/1793 ## 2.1.0 - 2020-10-30 diff --git a/actix-http/src/header/common/accept.rs b/actix-http/src/header/common/accept.rs index d52eba241..da26b0261 100644 --- a/actix-http/src/header/common/accept.rs +++ b/actix-http/src/header/common/accept.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; + use mime::Mime; use crate::header::{qitem, QualityItem}; @@ -7,7 +9,7 @@ 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 + /// 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 @@ -97,14 +99,14 @@ header! { test_header!( test1, vec![b"audio/*; q=0.2, audio/basic"], - Some(HeaderField(vec![ + Some(Accept(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![ + Some(Accept(vec![ QualityItem::new(mime::TEXT_PLAIN, q(500)), qitem(mime::TEXT_HTML), QualityItem::new( @@ -138,23 +140,148 @@ header! { } impl Accept { - /// A constructor to easily create `Accept: */*`. + /// Construct `Accept: */*`. pub fn star() -> Accept { Accept(vec![qitem(mime::STAR_STAR)]) } - /// A constructor to easily create `Accept: application/json`. + /// Construct `Accept: application/json`. pub fn json() -> Accept { Accept(vec![qitem(mime::APPLICATION_JSON)]) } - /// A constructor to easily create `Accept: text/*`. + /// Construct `Accept: text/*`. pub fn text() -> Accept { Accept(vec![qitem(mime::TEXT_STAR)]) } - /// A constructor to easily create `Accept: image/*`. + /// Construct `Accept: image/*`. pub fn image() -> Accept { Accept(vec![qitem(mime::IMAGE_STAR)]) } + + /// Construct `Accept: text/html`. + pub fn html() -> Accept { + Accept(vec![qitem(mime::TEXT_HTML)]) + } + + /// Returns a sorted list of mime types from highest to lowest preference, accounting for + /// [q-factor weighting] and specificity. + /// + /// [q-factor weighting]: https://tools.ietf.org/html/rfc7231#section-5.3.2 + pub fn mime_precedence(&self) -> Vec { + let mut types = self.0.clone(); + + // use stable sort so items with equal q-factor and specificity retain listed order + types.sort_by(|a, b| { + // sort by q-factor descending + b.quality.cmp(&a.quality).then_with(|| { + // use specificity rules on mime types with + // same q-factor (eg. text/html > text/* > */*) + + // subtypes are not comparable if main type is star, so return + match (a.item.type_(), b.item.type_()) { + (mime::STAR, mime::STAR) => return Ordering::Equal, + + // a is sorted after b + (mime::STAR, _) => return Ordering::Greater, + + // a is sorted before b + (_, mime::STAR) => return Ordering::Less, + + _ => {} + } + + // in both these match expressions, the returned ordering appears + // inverted because sort is high-to-low ("descending") precedence + match (a.item.subtype(), b.item.subtype()) { + (mime::STAR, mime::STAR) => Ordering::Equal, + + // a is sorted after b + (mime::STAR, _) => Ordering::Greater, + + // a is sorted before b + (_, mime::STAR) => Ordering::Less, + + _ => Ordering::Equal, + } + }) + }); + + types.into_iter().map(|qitem| qitem.item).collect() + } + + /// Extracts the most preferable mime type, accounting for [q-factor weighting]. + /// + /// If no q-factors are provided, the first mime type is chosen. Note that items without + /// q-factors are given the maximum preference value. + /// + /// Returns `None` if contained list is empty. + /// + /// [q-factor weighting]: https://tools.ietf.org/html/rfc7231#section-5.3.2 + pub fn mime_preference(&self) -> Option { + let types = self.mime_precedence(); + types.first().cloned() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::header::q; + + #[test] + fn test_mime_precedence() { + let test = Accept(vec![]); + assert!(test.mime_precedence().is_empty()); + + let test = Accept(vec![qitem(mime::APPLICATION_JSON)]); + assert_eq!(test.mime_precedence(), vec!(mime::APPLICATION_JSON)); + + let test = Accept(vec![ + qitem(mime::TEXT_HTML), + "application/xhtml+xml".parse().unwrap(), + QualityItem::new("application/xml".parse().unwrap(), q(0.9)), + QualityItem::new(mime::STAR_STAR, q(0.8)), + ]); + assert_eq!( + test.mime_precedence(), + vec![ + mime::TEXT_HTML, + "application/xhtml+xml".parse().unwrap(), + "application/xml".parse().unwrap(), + mime::STAR_STAR, + ] + ); + + let test = Accept(vec![ + qitem(mime::STAR_STAR), + qitem(mime::IMAGE_STAR), + qitem(mime::IMAGE_PNG), + ]); + assert_eq!( + test.mime_precedence(), + vec![mime::IMAGE_PNG, mime::IMAGE_STAR, mime::STAR_STAR] + ); + } + + #[test] + fn test_mime_preference() { + let test = Accept(vec![ + qitem(mime::TEXT_HTML), + "application/xhtml+xml".parse().unwrap(), + QualityItem::new("application/xml".parse().unwrap(), q(0.9)), + QualityItem::new(mime::STAR_STAR, q(0.8)), + ]); + assert_eq!(test.mime_preference(), Some(mime::TEXT_HTML)); + + let test = Accept(vec![ + QualityItem::new("video/*".parse().unwrap(), q(0.8)), + qitem(mime::IMAGE_PNG), + QualityItem::new(mime::STAR_STAR, q(0.5)), + qitem(mime::IMAGE_SVG), + QualityItem::new(mime::IMAGE_STAR, q(0.8)), + ]); + assert_eq!(test.mime_preference(), Some(mime::IMAGE_PNG)); + } } diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index 98230dec1..f4331a18e 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -18,7 +18,7 @@ use self::internal::IntoQuality; /// /// [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)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Quality(u16); impl Default for Quality { @@ -121,7 +121,7 @@ fn from_f32(f: f32) -> Quality { /// 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()) + QualityItem::new(item, Quality::default()) } /// Convenience function to create a `Quality` from a float or integer. From 5af46775b8d89f3bc005e725a9918f316e6459f9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 24 Nov 2020 11:37:05 +0000 Subject: [PATCH 22/81] refactor quality and use TryFrom instead of custom trait (#1797) --- actix-http/CHANGES.md | 2 + actix-http/src/header/mod.rs | 4 +- actix-http/src/header/shared/charset.rs | 4 +- actix-http/src/header/shared/quality_item.rs | 182 +++++++++++-------- 4 files changed, 106 insertions(+), 86 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index cafaa5e09..a25f44e36 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,6 +4,7 @@ ### Added * HttpResponse builders for 1xx status codes. [#1768] * `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] +* `TryFrom` and `TryFrom` for `http::header::Quality`. [#1797] ### Fixed * Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] @@ -14,6 +15,7 @@ [#1767]: https://github.com/actix/actix-web/pull/1767 [#1768]: https://github.com/actix/actix-web/pull/1768 [#1793]: https://github.com/actix/actix-web/pull/1793 +[#1797]: https://github.com/actix/actix-web/pull/1797 ## 2.1.0 - 2020-10-30 diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 46fb31a62..0f87516eb 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -370,9 +370,7 @@ impl fmt::Display for ExtendedValue { } /// 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, HTTP_VALUE); fmt::Display::fmt(&encoded, f) diff --git a/actix-http/src/header/shared/charset.rs b/actix-http/src/header/shared/charset.rs index 00e7309d4..36bdbf7e2 100644 --- a/actix-http/src/header/shared/charset.rs +++ b/actix-http/src/header/shared/charset.rs @@ -7,9 +7,7 @@ use self::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 +/// See . #[derive(Clone, Debug, PartialEq)] #[allow(non_camel_case_types)] pub enum Charset { diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index f4331a18e..01a3b988a 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -1,10 +1,17 @@ -use std::{cmp, fmt, str}; +use std::{ + cmp, + convert::{TryFrom, TryInto}, + fmt, str, +}; -use self::internal::IntoQuality; +use derive_more::{Display, Error}; + +const MAX_QUALITY: u16 = 1000; +const MAX_FLOAT_QUALITY: f32 = 1.0; /// Represents a quality used in quality values. /// -/// Can be created with the `q` function. +/// Can be created with the [`q`] function. /// /// # Implementation notes /// @@ -21,9 +28,51 @@ use self::internal::IntoQuality; #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Quality(u16); +impl Quality { + /// # Panics + /// Panics in debug mode when value is not in the range 0.0 <= n <= 1.0. + fn from_f32(value: f32) -> Self { + // Check that `value` is within range should be done before calling this method. + // Just in case, this debug_assert should catch if we were forgetful. + debug_assert!( + (0.0f32..=1.0f32).contains(&value), + "q value must be between 0.0 and 1.0" + ); + + Quality((value * MAX_QUALITY as f32) as u16) + } +} + impl Default for Quality { fn default() -> Quality { - Quality(1000) + Quality(MAX_QUALITY) + } +} + +#[derive(Debug, Clone, Display, Error)] +pub struct QualityOutOfBounds; + +impl TryFrom for Quality { + type Error = QualityOutOfBounds; + + fn try_from(value: u16) -> Result { + if (0..=MAX_QUALITY).contains(&value) { + Ok(Quality(value)) + } else { + Err(QualityOutOfBounds) + } + } +} + +impl TryFrom for Quality { + type Error = QualityOutOfBounds; + + fn try_from(value: f32) -> Result { + if (0.0..=MAX_FLOAT_QUALITY).contains(&value) { + Ok(Quality::from_f32(value)) + } else { + Err(QualityOutOfBounds) + } } } @@ -55,8 +104,9 @@ impl cmp::PartialOrd for QualityItem { 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(()), + MAX_QUALITY => Ok(()), 0 => f.write_str("; q=0"), x => write!(f, "; q=0.{}", format!("{:03}", x).trim_end_matches('0')), } @@ -66,56 +116,61 @@ impl fmt::Display for QualityItem { impl str::FromStr for QualityItem { type Err = crate::error::ParseError; - fn from_str(s: &str) -> Result, crate::error::ParseError> { - if !s.is_ascii() { + fn from_str(qitem_str: &str) -> Result, crate::error::ParseError> { + if !qitem_str.is_ascii() { return Err(crate::error::ParseError::Header); } + // Set defaults used if parsing fails. - let mut raw_item = s; + let mut raw_item = qitem_str; let mut quality = 1f32; - let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect(); + let parts: Vec<_> = qitem_str.rsplitn(2, ';').map(str::trim).collect(); + if parts.len() == 2 { + // example for item with q-factor: + // + // gzip; q=0.65 + // ^^^^^^ parts[0] + // ^^ start + // ^^^^ q_val + // ^^^^ parts[1] + if parts[0].len() < 2 { + // Can't possibly be an attribute since an attribute needs at least a name followed + // by an equals sign. And bare identifiers are forbidden. 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 { + let q_val = &parts[0][2..]; + if q_val.len() > 5 { + // longer than 5 indicates an over-precise q-factor 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), + + let q_value = q_val + .parse::() + .map_err(|_| crate::error::ParseError::Header)?; + + if (0f32..=1f32).contains(&q_value) { + quality = q_value; + raw_item = parts[1]; + } else { + 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) + let item = raw_item + .parse::() + .map_err(|_| crate::error::ParseError::Header)?; + + // we already checked above that the quality is within range + Ok(QualityItem::new(item, Quality::from_f32(quality))) + } } /// Convenience function to wrap a value in a `QualityItem` @@ -127,44 +182,13 @@ pub fn qitem(item: T) -> QualityItem { /// 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 {} +pub fn q(val: T) -> Quality +where + T: TryInto, + T::Error: fmt::Debug, +{ + // TODO: on next breaking change, handle unwrap differently + val.try_into().unwrap() } #[cfg(test)] @@ -270,15 +294,13 @@ mod tests { } #[test] - #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] + #[should_panic] 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)] + #[should_panic] fn test_quality_invalid2() { q(2.0); } From f1a9b45437c1f2c8e89b3a54b854398c09c7e21e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 24 Nov 2020 20:23:09 +0000 Subject: [PATCH 23/81] improve docs for Files::new --- actix-files/CHANGES.md | 4 ++++ actix-files/src/files.rs | 26 +++++++++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 978d1c69d..c4d56010f 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2020-xx-xx +## 0.4.1 - 2020-11-24 +* Clarify order of parameters in `Files::new` and improve docs. + + ## 0.4.0 - 2020-10-06 * Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714] diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 5a783e2dd..a99b4699e 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -65,13 +65,25 @@ impl Clone for Files { } impl Files { - /// Create new `Files` instance for specified base directory. + /// Create new `Files` instance for a specified base directory. /// - /// `File` uses `ThreadPool` for blocking filesystem operations. - /// By default pool with 5x threads of available cpus is used. - /// Pool size can be changed by setting ACTIX_THREADPOOL environment variable. - pub fn new>(path: &str, dir: T) -> Files { - let orig_dir = dir.into(); + /// # Argument Order + /// The first argument (`mount_path`) is the root URL at which the static files are served. + /// For example, `/assets` will serve files at `example.com/assets/...`. + /// + /// The second argument (`serve_from`) is the location on disk at which files are loaded. + /// This can be a relative path. For example, `./` would serve files from the current + /// working directory. + /// + /// # Implementation Notes + /// If the mount path is set as the root path `/`, services registered after this one will + /// be inaccessible. Register more specific handlers and services first. + /// + /// `Files` uses a threadpool for blocking filesystem operations. By default, the pool uses a + /// number of threads equal to 5x the number of available logical CPUs. Pool size can be changed + /// by setting ACTIX_THREADPOOL environment variable. + pub fn new>(mount_path: &str, serve_from: T) -> Files { + let orig_dir = serve_from.into(); let dir = match orig_dir.canonicalize() { Ok(canon_dir) => canon_dir, Err(_) => { @@ -81,7 +93,7 @@ impl Files { }; Files { - path: path.to_string(), + path: mount_path.to_owned(), directory: dir, index: None, show_index: false, From 31057beccabbaf82ce8198769dbb940efe7c4a36 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 24 Nov 2020 20:33:23 +0000 Subject: [PATCH 24/81] prepare actix-files release 0.4.1 --- actix-files/Cargo.toml | 8 ++++---- actix-files/README.md | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index c829887ba..f7d32f8ec 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.4.0" +version = "0.4.1" authors = ["Nikolay Kim "] description = "Static file serving for Actix Web" readme = "README.md" @@ -21,14 +21,14 @@ actix-web = { version = "3.0.0", default-features = false } actix-service = "1.0.6" bitflags = "1" bytes = "0.5.3" -futures-core = { version = "0.3.5", default-features = false } -futures-util = { version = "0.3.5", default-features = false } +futures-core = { version = "0.3.7", default-features = false } +futures-util = { version = "0.3.7", default-features = false } derive_more = "0.99.2" log = "0.4" mime = "0.3" mime_guess = "2.0.1" percent-encoding = "2.1" -v_htmlescape = "0.10" +v_htmlescape = "0.11" [dev-dependencies] actix-rt = "1.0.0" diff --git a/actix-files/README.md b/actix-files/README.md index d31439361..685e5dbe5 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -2,12 +2,12 @@ > Static file serving for Actix Web -[![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg)](https://docs.rs/actix-files) +[![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.4.1)](https://docs.rs/actix-files/0.4.1) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.4.0/status.svg)](https://deps.rs/crate/actix-files/0.4.0) +[![dependency status](https://deps.rs/crate/actix-files/0.4.1/status.svg)](https://deps.rs/crate/actix-files/0.4.1) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![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) From efc317d3b0cfaa19a5c5adfe0c78cbb54f43a0a0 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 25 Nov 2020 00:07:56 +0000 Subject: [PATCH 25/81] prepare actix-http and awc releases --- actix-http/CHANGES.md | 6 +++++- actix-http/Cargo.toml | 2 +- actix-http/README.md | 6 +++--- awc/CHANGES.md | 8 +++++++- awc/README.md | 6 +++--- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index a25f44e36..c602ab2e1 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2020-xx-xx + + +## 2.2.0 - 2020-11-25 ### Added * HttpResponse builders for 1xx status codes. [#1768] * `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] @@ -10,8 +13,9 @@ * Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] ### Changed -* Upgrade `serde_urlencoded` to `0.7`. +* Upgrade `serde_urlencoded` to `0.7`. [#1773] +[#1773]: https://github.com/actix/actix-web/pull/1773 [#1767]: https://github.com/actix/actix-web/pull/1767 [#1768]: https://github.com/actix/actix-web/pull/1768 [#1793]: https://github.com/actix/actix-web/pull/1793 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 31495e395..7375c6eb3 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "2.1.0" +version = "2.2.0" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" readme = "README.md" diff --git a/actix-http/README.md b/actix-http/README.md index e536276ca..9103cd184 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,14 +3,14 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=2.1.0)](https://docs.rs/actix-http/2.1.0) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=2.2.0)](https://docs.rs/actix-http/2.2.0) ![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http) -[![Dependency Status](https://deps.rs/crate/actix-http/2.1.0/status.svg)](https://deps.rs/crate/actix-http/2.1.0) +[![Dependency Status](https://deps.rs/crate/actix-http/2.2.0/status.svg)](https://deps.rs/crate/actix-http/2.2.0) [![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Documentation & Resources -- [API Documentation](https://docs.rs/actix-http/2.1.0) +- [API Documentation](https://docs.rs/actix-http) - [Chat on Gitter](https://gitter.im/actix/actix-web) - Minimum Supported Rust Version (MSRV): 1.42.0 diff --git a/awc/CHANGES.md b/awc/CHANGES.md index e184dfbd1..3745079cd 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,8 +1,14 @@ # Changes ## Unreleased - 2020-xx-xx + + +## 2.0.2 - 2020-11-25 ### Changed -* Upgrade `serde_urlencoded` to `0.7`. +* Upgrade `serde_urlencoded` to `0.7`. [#1773] + +[#1773]: https://github.com/actix/actix-web/pull/1773 + ## 2.0.1 - 2020-10-30 ### Changed diff --git a/awc/README.md b/awc/README.md index cbe299aaf..d14dd82dd 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,14 +3,14 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=2.0.1)](https://docs.rs/awc/2.0.1) +[![Documentation](https://docs.rs/awc/badge.svg?version=2.0.2)](https://docs.rs/awc/2.0.2) ![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/2.0.1/status.svg)](https://deps.rs/crate/awc/2.0.1) +[![Dependency Status](https://deps.rs/crate/awc/2.0.2/status.svg)](https://deps.rs/crate/awc/2.0.2) [![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Documentation & Resources -- [API Documentation](https://docs.rs/awc/2.0.1) +- [API Documentation](https://docs.rs/awc) - [Example Project](https://github.com/actix/examples/tree/HEAD/awc_https) - [Chat on Gitter](https://gitter.im/actix/actix-web) - Minimum Supported Rust Version (MSRV): 1.42.0 From e72b787ba701bc6db9a2ade9b498f801e6b1b90d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 25 Nov 2020 00:53:48 +0000 Subject: [PATCH 26/81] prepare actix-web and actix-http-test releases --- CHANGES.md | 6 ++++- Cargo.toml | 2 +- README.md | 4 +-- awc/Cargo.toml | 2 +- test-server/CHANGES.md | 55 ++++++++++++++++-------------------------- test-server/Cargo.toml | 4 +-- test-server/README.md | 20 +++++++++------ test-server/src/lib.rs | 5 ++++ 8 files changed, 50 insertions(+), 48 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b928712d0..1364d6a9b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,12 +1,16 @@ # Changes ## Unreleased - 2020-xx-xx + + +## 3.3.0 - 2020-11-25 ### Added * Add `Either` extractor helper. [#1788] ### Changed -* Upgrade `serde_urlencoded` to `0.7`. +* Upgrade `serde_urlencoded` to `0.7`. [#1773] +[#1773]: https://github.com/actix/actix-web/pull/1773 [#1788]: https://github.com/actix/actix-web/pull/1788 diff --git a/Cargo.toml b/Cargo.toml index da511888a..3a3a92da9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "3.2.0" +version = "3.3.0" authors = ["Nikolay Kim "] description = "Actix web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" diff --git a/README.md b/README.md index b11a8ee7c..c1d857f81 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=3.2.0)](https://docs.rs/actix-web/3.2.0) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=3.3.0)](https://docs.rs/actix-web/3.3.0) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) ![License](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/3.2.0/status.svg)](https://deps.rs/crate/actix-web/3.2.0) +[![Dependency Status](https://deps.rs/crate/actix-web/3.3.0/status.svg)](https://deps.rs/crate/actix-web/3.3.0)
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/awc/Cargo.toml b/awc/Cargo.toml index d92996fb9..d5b632a51 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "2.0.1" +version = "2.0.2" authors = ["Nikolay Kim "] description = "Async HTTP and WebSocket client library built on the Actix ecosystem" readme = "README.md" diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index 845b6e2dc..835b75ddc 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -2,15 +2,20 @@ ## Unreleased - 2020-xx-xx -* add ability to set address for `TestServer` [#1645] -* Upgrade `base64` to `0.13`. -* Upgrade `serde_urlencoded` to `0.7`. +## 2.1.0 - 2020-11-25 +* Add ability to set address for `TestServer`. [#1645] +* Upgrade `base64` to `0.13`. +* Upgrade `serde_urlencoded` to `0.7`. [#1773] + +[#1773]: https://github.com/actix/actix-web/pull/1773 [#1645]: https://github.com/actix/actix-web/pull/1645 + ## 2.0.0 - 2020-09-11 * Update actix-codec and actix-utils dependencies. + ## 2.0.0-alpha.1 - 2020-05-23 * Update the `time` dependency to 0.2.7 * Update `actix-connect` dependency to 2.0.0-alpha.2 @@ -20,74 +25,56 @@ * Update `base64` dependency to 0.12 * Update `env_logger` dependency to 0.7 -## [1.0.0] - 2019-12-13 - -### Changed - +## 1.0.0 - 2019-12-13 * Replaced `TestServer::start()` with `test_server()` -## [1.0.0-alpha.3] - 2019-12-07 - -### Changed - +## 1.0.0-alpha.3 - 2019-12-07 * Migrate to `std::future` -## [0.2.5] - 2019-09-17 - -### Changed - +## 0.2.5 - 2019-09-17 * Update serde_urlencoded to "0.6.1" * Increase TestServerRuntime timeouts from 500ms to 3000ms - -### Fixed - * Do not override current `System` -## [0.2.4] - 2019-07-18 - +## 0.2.4 - 2019-07-18 * Update actix-server to 0.6 -## [0.2.3] - 2019-07-16 +## 0.2.3 - 2019-07-16 * Add `delete`, `options`, `patch` methods to `TestServerRunner` -## [0.2.2] - 2019-06-16 +## 0.2.2 - 2019-06-16 * Add .put() and .sput() methods -## [0.2.1] - 2019-06-05 +## 0.2.1 - 2019-06-05 * Add license files -## [0.2.0] - 2019-05-12 +## 0.2.0 - 2019-05-12 * Update awc and actix-http deps -## [0.1.1] - 2019-04-24 +## 0.1.1 - 2019-04-24 * Always make new connection for http client -## [0.1.0] - 2019-04-16 - +## 0.1.0 - 2019-04-16 * No changes -## [0.1.0-alpha.3] - 2019-04-02 - +## 0.1.0-alpha.3 - 2019-04-02 * Request functions accept path #743 -## [0.1.0-alpha.2] - 2019-03-29 - +## 0.1.0-alpha.2 - 2019-03-29 * Added TestServerRuntime::load_body() method - * Update actix-http and awc libraries -## [0.1.0-alpha.1] - 2019-03-28 - +## 0.1.0-alpha.1 - 2019-03-28 * Initial impl diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 87db93469..8b23bef1c 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "actix-http-test" -version = "2.0.0" +version = "2.1.0" authors = ["Nikolay Kim "] -description = "Actix HTTP test server" +description = "Various helpers for Actix applications to use during testing" readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" diff --git a/test-server/README.md b/test-server/README.md index db0791db7..c847c8515 100644 --- a/test-server/README.md +++ b/test-server/README.md @@ -1,9 +1,15 @@ -# Actix http test server [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![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-http-test)](https://crates.io/crates/actix-http-test) [![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-test -## Documentation & community resources +> Various helpers for Actix applications to use during testing. -* [User Guide](https://actix.rs/docs/) -* [API Documentation](https://docs.rs/actix-http-test/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-http-test](https://crates.io/crates/actix-http-test) -* Minimum supported Rust version: 1.40 or later +[![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) +[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=2.1.0)](https://docs.rs/actix-http-test/2.1.0) +![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http-test) +[![Dependency Status](https://deps.rs/crate/actix-http-test/2.1.0/status.svg)](https://deps.rs/crate/actix-http-test/2.1.0) +[![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +## Documentation & Resources + +- [API Documentation](https://docs.rs/actix-http-test) +- [Chat on Gitter](https://gitter.im/actix/actix-web) +- Minimum Supported Rust Version (MSRV): 1.42.0 diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 4159c8d86..f881dfb4c 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -1,4 +1,9 @@ //! Various helpers for Actix applications to use during testing. + +#![deny(rust_2018_idioms)] +#![doc(html_logo_url = "https://actix.rs/img/logo.png")] +#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] + use std::sync::mpsc; use std::{net, thread, time}; From fe6ad816cc730e545cf27e26d68ba26ddc20afe6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 25 Nov 2020 00:54:00 +0000 Subject: [PATCH 27/81] update dotgraphs --- docs/graphs/web-focus.dot | 42 ++++++++++++++++++++------------------- docs/graphs/web-only.dot | 2 ++ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/docs/graphs/web-focus.dot b/docs/graphs/web-focus.dot index 7abd51268..bcae36616 100644 --- a/docs/graphs/web-focus.dot +++ b/docs/graphs/web-focus.dot @@ -2,29 +2,31 @@ digraph { subgraph cluster_web { label="actix/actix-web" "awc" - "actix-web" - "actix-files" - "actix-http" - "actix-multipart" - "actix-web-actors" - "actix-web-codegen" + "web" + "files" + "http" + "multipart" + "web-actors" + "codegen" + "http-test" } - "actix-web" -> { "actix-codec" "actix-service" "actix-utils" "actix-router" "actix-rt" "actix-server" "actix-testing" "actix-macros" "actix-threadpool" "actix-tls" "actix-web-codegen" "actix-http" "awc" } - "awc" -> { "actix-codec" "actix-service" "actix-http" "actix-rt" } - "actix-web-actors" -> { "actix" "actix-web" "actix-http" "actix-codec" } - "actix-multipart" -> { "actix-web" "actix-service" "actix-utils" } - "actix-http" -> { "actix-service" "actix-codec" "actix-connect" "actix-utils" "actix-rt" "actix-threadpool" } - "actix-http" -> { "actix" "actix-tls" }[color=blue] // optional - "actix-files" -> { "actix-web" "actix-http" } + "web" -> { "codec" "service" "utils" "router" "rt" "server" "testing" "macros" "threadpool" "tls" "codegen" "http" "awc" } + "awc" -> { "codec" "service" "http" "rt" } + "web-actors" -> { "actix" "web" "http" "codec" } + "multipart" -> { "web" "service" "utils" } + "http" -> { "service" "codec" "connect" "utils" "rt" "threadpool" } + "http" -> { "actix" "tls" }[color=blue] // optional + "files" -> { "web" "http" } + "http-test" -> { "service" "codec" "connect" "utils" "rt" "server" "testing" "awc" } // net - "actix-utils" -> { "actix-service" "actix-rt" "actix-codec" } - "actix-tracing" -> { "actix-service" } - "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" } - "actix-testing" -> { "actix-rt" "actix-macros" "actix-server" "actix-service" } - "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" } - "actix-rt" -> { "actix-macros" "actix-threadpool" } - "actix-connect" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" } + "utils" -> { "service" "rt" "codec" } + "tracing" -> { "service" } + "tls" -> { "service" "codec" "utils" } + "testing" -> { "rt" "macros" "server" "service" } + "server" -> { "service" "rt" "codec" "utils" } + "rt" -> { "macros" "threadpool" } + "connect" -> { "service" "codec" "utils" "rt" } } diff --git a/docs/graphs/web-only.dot b/docs/graphs/web-only.dot index 6e41fdc27..ee653e33b 100644 --- a/docs/graphs/web-only.dot +++ b/docs/graphs/web-only.dot @@ -8,6 +8,7 @@ digraph { "actix-multipart" "actix-web-actors" "actix-web-codegen" + "actix-http-test" } "actix-web" -> { "actix-web-codegen" "actix-http" "awc" } @@ -16,4 +17,5 @@ digraph { "actix-multipart" -> { "actix-web" } "actix-http" -> { "actix" }[color=blue] // optional "actix-files" -> { "actix-web" "actix-http" } + "actix-http-test" -> { "awc" } } From 0b5b463cfa951d96ec6b0167964ef613b0d2b091 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 29 Nov 2020 16:33:45 +0000 Subject: [PATCH 28/81] prepare web and awc releases closes #1799 --- Cargo.toml | 10 +++++----- README.md | 6 +++--- awc/Cargo.toml | 4 ++-- awc/README.md | 4 ++-- docs/graphs/web-focus.dot | 2 +- docs/graphs/web-only.dot | 4 ++-- src/lib.rs | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3a3a92da9..bdd87184c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "actix-web" -version = "3.3.0" +version = "3.3.1" authors = ["Nikolay Kim "] -description = "Actix web is a powerful, pragmatic, and extremely fast web framework for Rust" +description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" keywords = ["actix", "http", "web", "framework", "async"] homepage = "https://actix.rs" @@ -85,11 +85,11 @@ actix-threadpool = "0.3.1" actix-tls = "2.0.0" actix-web-codegen = "0.4.0" -actix-http = "2.1.0" -awc = { version = "2.0.0", default-features = false } +actix-http = "2.2.0" +awc = { version = "2.0.3", default-features = false } bytes = "0.5.3" -derive_more = "0.99.2" +derive_more = "0.99.5" encoding_rs = "0.8" futures-channel = { version = "0.3.5", default-features = false } futures-core = { version = "0.3.5", default-features = false } diff --git a/README.md b/README.md index c1d857f81..f67da03c1 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@

Actix web

- Actix web is a powerful, pragmatic, and extremely fast web framework for Rust + Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=3.3.0)](https://docs.rs/actix-web/3.3.0) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=3.3.1)](https://docs.rs/actix-web/3.3.1) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) ![License](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/3.3.0/status.svg)](https://deps.rs/crate/actix-web/3.3.0) +[![Dependency Status](https://deps.rs/crate/actix-web/3.3.1/status.svg)](https://deps.rs/crate/actix-web/3.3.1)
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/awc/Cargo.toml b/awc/Cargo.toml index d5b632a51..3c1963d6b 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "2.0.2" +version = "2.0.3" authors = ["Nikolay Kim "] description = "Async HTTP and WebSocket client library built on the Actix ecosystem" readme = "README.md" @@ -39,7 +39,7 @@ compress = ["actix-http/compress"] [dependencies] actix-codec = "0.3.0" actix-service = "1.0.6" -actix-http = "2.0.0" +actix-http = "2.2.0" actix-rt = "1.0.0" base64 = "0.13" diff --git a/awc/README.md b/awc/README.md index d14dd82dd..b97d4fa00 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=2.0.2)](https://docs.rs/awc/2.0.2) +[![Documentation](https://docs.rs/awc/badge.svg?version=2.0.3)](https://docs.rs/awc/2.0.3) ![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/2.0.2/status.svg)](https://deps.rs/crate/awc/2.0.2) +[![Dependency Status](https://deps.rs/crate/awc/2.0.3/status.svg)](https://deps.rs/crate/awc/2.0.3) [![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Documentation & Resources diff --git a/docs/graphs/web-focus.dot b/docs/graphs/web-focus.dot index bcae36616..17228fe62 100644 --- a/docs/graphs/web-focus.dot +++ b/docs/graphs/web-focus.dot @@ -17,7 +17,7 @@ digraph { "multipart" -> { "web" "service" "utils" } "http" -> { "service" "codec" "connect" "utils" "rt" "threadpool" } "http" -> { "actix" "tls" }[color=blue] // optional - "files" -> { "web" "http" } + "files" -> { "web" } "http-test" -> { "service" "codec" "connect" "utils" "rt" "server" "testing" "awc" } // net diff --git a/docs/graphs/web-only.dot b/docs/graphs/web-only.dot index ee653e33b..9e1bb2805 100644 --- a/docs/graphs/web-only.dot +++ b/docs/graphs/web-only.dot @@ -11,11 +11,11 @@ digraph { "actix-http-test" } - "actix-web" -> { "actix-web-codegen" "actix-http" "awc" } + "actix-web" -> { "actix-web-codegen" "actix-http" "awc" } "awc" -> { "actix-http" } "actix-web-actors" -> { "actix" "actix-web" "actix-http" } "actix-multipart" -> { "actix-web" } "actix-http" -> { "actix" }[color=blue] // optional - "actix-files" -> { "actix-web" "actix-http" } + "actix-files" -> { "actix-web" } "actix-http-test" -> { "awc" } } diff --git a/src/lib.rs b/src/lib.rs index 13e02c098..a8fc50d83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -//! Actix web is a powerful, pragmatic, and extremely fast web framework for Rust. +//! Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust. //! //! ## Example //! From ea8bf361041b35aa281835540f1e8aba9679aceb Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 29 Nov 2020 16:35:35 +0000 Subject: [PATCH 29/81] update web and awc changelogs --- CHANGES.md | 2 ++ awc/CHANGES.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 1364d6a9b..f30049c9d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2020-xx-xx +### Fixed +* Ensure `actix-http` dependency uses same `serde_urlencoded`. ## 3.3.0 - 2020-11-25 diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 3745079cd..dfd8e3704 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2020-xx-xx +### Fixed +* Ensure `actix-http` dependency uses same `serde_urlencoded`. ## 2.0.2 - 2020-11-25 From 32d59ca904bceaf7638a25c0ba367a9560dfc3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20F=C3=A4rnstrand?= Date: Mon, 30 Nov 2020 20:18:02 +0100 Subject: [PATCH 30/81] Upgrade socket2 dependency (#1803) Upgrades to a version not making invalid assumptions about the memory layout of std::net::SocketAddr --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bdd87184c..f8da9d90c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,7 +97,7 @@ futures-util = { version = "0.3.5", default-features = false } fxhash = "0.2.1" log = "0.4" mime = "0.3" -socket2 = "0.3" +socket2 = "0.3.16" pin-project = "1.0.0" regex = "1.4" serde = { version = "1.0", features = ["derive"] } From 7981e0068a8d201e2f669772a0e81e5a73523688 Mon Sep 17 00:00:00 2001 From: Maciej Hirsz <1096222+maciejhirsz@users.noreply.github.com> Date: Tue, 1 Dec 2020 02:22:15 +0100 Subject: [PATCH 31/81] Remove a panic in normalize middleware (#1762) Co-authored-by: Yuki Okushi --- CHANGES.md | 1 + src/middleware/normalize.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f30049c9d..1175aa54a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - 2020-xx-xx ### Fixed * Ensure `actix-http` dependency uses same `serde_urlencoded`. +* Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. ## 3.3.0 - 2020-11-25 diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index e0ecd90dc..ac8ad71d5 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -137,9 +137,9 @@ where // so the change can not be deduced from the length comparison if path != original_path { let mut parts = head.uri.clone().into_parts(); - let pq = parts.path_and_query.as_ref().unwrap(); + let query = parts.path_and_query.as_ref().and_then(|pq| pq.query()); - let path = if let Some(q) = pq.query() { + let path = if let Some(q) = query { Bytes::from(format!("{}?{}", path, q)) } else { Bytes::copy_from_slice(path.as_bytes()) From 1f70ef155d76c4b96e8b7fdc01d338bf6b9b2185 Mon Sep 17 00:00:00 2001 From: Joshua Parkin Date: Tue, 1 Dec 2020 13:39:41 +0000 Subject: [PATCH 32/81] Fix match_pattern() returning None for scope with resource of empty path (#1798) * fix match_pattern function not returning pattern where scope has resource of path "" * remove print in test * make comparison on existing else if block * add fix to changelog --- CHANGES.md | 3 +++ src/request.rs | 36 ++++++++++++++++++++++++++++++++++++ src/rmap.rs | 2 +- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 1175aa54a..edf4380fb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,9 @@ ### Fixed * Ensure `actix-http` dependency uses same `serde_urlencoded`. * Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. +* Fix match_pattern() returning None for scope with resource of empty path. [#1798] + +[#1798]: https://github.com/actix/actix-web/pull/1798 ## 3.3.0 - 2020-11-25 diff --git a/src/request.rs b/src/request.rs index a1b42f926..bd4bbbf58 100644 --- a/src/request.rs +++ b/src/request.rs @@ -675,4 +675,40 @@ mod tests { let res = call_service(&mut srv, req).await; assert_eq!(res.status(), StatusCode::OK); } + + #[actix_rt::test] + async fn extract_path_pattern_complex() { + let mut srv = init_service( + App::new() + .service(web::scope("/user").service(web::scope("/{id}").service( + web::resource("").to(move |req: HttpRequest| { + assert_eq!(req.match_pattern(), Some("/user/{id}".to_owned())); + + HttpResponse::Ok().finish() + }), + ))) + .service(web::resource("/").to(move |req: HttpRequest| { + assert_eq!(req.match_pattern(), Some("/".to_owned())); + + HttpResponse::Ok().finish() + })) + .default_service(web::to(move |req: HttpRequest| { + assert!(req.match_pattern().is_none()); + HttpResponse::Ok().finish() + })), + ) + .await; + + let req = TestRequest::get().uri("/user/test").to_request(); + let res = call_service(&mut srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + + let req = TestRequest::get().uri("/").to_request(); + let res = call_service(&mut srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + + let req = TestRequest::get().uri("/not-exist").to_request(); + let res = call_service(&mut srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + } } diff --git a/src/rmap.rs b/src/rmap.rs index 05c1f3f15..6827a11b2 100644 --- a/src/rmap.rs +++ b/src/rmap.rs @@ -86,7 +86,7 @@ impl ResourceMap { if let Some(plen) = pattern.is_prefix_match(path) { return rmap.has_resource(&path[plen..]); } - } else if pattern.is_match(path) { + } else if pattern.is_match(path) || pattern.pattern() == "" && path == "/" { return true; } } From 24d525d9784c61b1956f14841e9c78395037b0d6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 1 Dec 2020 22:22:46 +0000 Subject: [PATCH 33/81] prepare web 3.3.2 release --- CHANGES.md | 17 +++++++++++++---- Cargo.toml | 2 +- README.md | 4 ++-- awc/CHANGES.md | 3 +++ 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index edf4380fb..d9984224f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,12 +1,21 @@ # Changes ## Unreleased - 2020-xx-xx -### Fixed -* Ensure `actix-http` dependency uses same `serde_urlencoded`. -* Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. -* Fix match_pattern() returning None for scope with resource of empty path. [#1798] + +## 3.3.2 - 2020-12-01 +### Fixed +* Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762] +* Fix `match_pattern()` returning `None` for scope with empty path resource. [#1798] +* Increase minimum `socket2` version. [#1803] + +[#1762]: https://github.com/actix/actix-web/pull/1762 [#1798]: https://github.com/actix/actix-web/pull/1798 +[#1803]: https://github.com/actix/actix-web/pull/1803 + + +## 3.3.1 - 2020-11-29 +* Ensure `actix-http` dependency uses same `serde_urlencoded`. ## 3.3.0 - 2020-11-25 diff --git a/Cargo.toml b/Cargo.toml index f8da9d90c..a7b571123 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "3.3.1" +version = "3.3.2" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" diff --git a/README.md b/README.md index f67da03c1..b9f2b7594 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=3.3.1)](https://docs.rs/actix-web/3.3.1) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=3.3.2)](https://docs.rs/actix-web/3.3.2) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) ![License](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/3.3.1/status.svg)](https://deps.rs/crate/actix-web/3.3.1) +[![Dependency Status](https://deps.rs/crate/actix-web/3.3.2/status.svg)](https://deps.rs/crate/actix-web/3.3.2)
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index dfd8e3704..7ca415336 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2020-xx-xx + + +## 2.0.3 - 2020-11-29 ### Fixed * Ensure `actix-http` dependency uses same `serde_urlencoded`. From d0c6ca7671d8e77a78432cf38cb4fb570f582f39 Mon Sep 17 00:00:00 2001 From: Arniu Tseng Date: Thu, 3 Dec 2020 01:23:30 +0800 Subject: [PATCH 34/81] test-server => actix-http-test (#1807) --- Cargo.toml | 6 +++--- {test-server => actix-http-test}/CHANGES.md | 0 {test-server => actix-http-test}/Cargo.toml | 0 {test-server => actix-http-test}/LICENSE-APACHE | 0 {test-server => actix-http-test}/LICENSE-MIT | 0 {test-server => actix-http-test}/README.md | 0 {test-server => actix-http-test}/src/lib.rs | 0 7 files changed, 3 insertions(+), 3 deletions(-) rename {test-server => actix-http-test}/CHANGES.md (100%) rename {test-server => actix-http-test}/Cargo.toml (100%) rename {test-server => actix-http-test}/LICENSE-APACHE (100%) rename {test-server => actix-http-test}/LICENSE-MIT (100%) rename {test-server => actix-http-test}/README.md (100%) rename {test-server => actix-http-test}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index a7b571123..31c4cca7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ members = [ "actix-multipart", "actix-web-actors", "actix-web-codegen", - "test-server", + "actix-http-test", ] [features] @@ -127,10 +127,10 @@ codegen-units = 1 [patch.crates-io] actix-web = { path = "." } actix-http = { path = "actix-http" } -actix-http-test = { path = "test-server" } +actix-http-test = { path = "actix-http-test" } actix-web-codegen = { path = "actix-web-codegen" } -actix-files = { path = "actix-files" } actix-multipart = { path = "actix-multipart" } +actix-files = { path = "actix-files" } awc = { path = "awc" } [[bench]] diff --git a/test-server/CHANGES.md b/actix-http-test/CHANGES.md similarity index 100% rename from test-server/CHANGES.md rename to actix-http-test/CHANGES.md diff --git a/test-server/Cargo.toml b/actix-http-test/Cargo.toml similarity index 100% rename from test-server/Cargo.toml rename to actix-http-test/Cargo.toml diff --git a/test-server/LICENSE-APACHE b/actix-http-test/LICENSE-APACHE similarity index 100% rename from test-server/LICENSE-APACHE rename to actix-http-test/LICENSE-APACHE diff --git a/test-server/LICENSE-MIT b/actix-http-test/LICENSE-MIT similarity index 100% rename from test-server/LICENSE-MIT rename to actix-http-test/LICENSE-MIT diff --git a/test-server/README.md b/actix-http-test/README.md similarity index 100% rename from test-server/README.md rename to actix-http-test/README.md diff --git a/test-server/src/lib.rs b/actix-http-test/src/lib.rs similarity index 100% rename from test-server/src/lib.rs rename to actix-http-test/src/lib.rs From b75a9b7a20b8cdc24842d38c7aa406e2b49f90c7 Mon Sep 17 00:00:00 2001 From: Petar Dambovaliev Date: Fri, 4 Dec 2020 20:57:56 +0100 Subject: [PATCH 35/81] add error to message in test helper func (#1812) --- CHANGES.md | 5 +++++ src/test.rs | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d9984224f..87c021b1e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ # Changes ## Unreleased - 2020-xx-xx +### Fixed +* added the actual parsing error to `test::read_body_json` [#1812] + +[#1812]: https://github.com/actix/actix-web/pull/1812 + ## 3.3.2 - 2020-12-01 diff --git a/src/test.rs b/src/test.rs index ee51b71ee..cff6c3e51 100644 --- a/src/test.rs +++ b/src/test.rs @@ -269,8 +269,9 @@ where { let body = read_body(res).await; - serde_json::from_slice(&body) - .unwrap_or_else(|_| panic!("read_response_json failed during deserialization")) + serde_json::from_slice(&body).unwrap_or_else(|e| { + panic!("read_response_json failed during deserialization: {}", e) + }) } pub async fn load_stream(mut stream: S) -> Result From ff79c33fd41ce9511e0bfb4835c688a2d5ac2a5d Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 6 Dec 2020 19:42:15 +0800 Subject: [PATCH 36/81] remove a box (#1814) --- src/route.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/route.rs b/src/route.rs index e9e9d1f5d..8cc1edfc2 100644 --- a/src/route.rs +++ b/src/route.rs @@ -135,7 +135,7 @@ impl Service for RouteService { } fn call(&mut self, req: ServiceRequest) -> Self::Future { - self.service.call(req).boxed_local() + self.service.call(req) } } From 7a3776b770405c13eeb0dd5bf95f9c545910b53a Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 9 Dec 2020 18:47:59 +0800 Subject: [PATCH 37/81] remove two unused generics on BoxedRouteFuture types. (#1820) --- src/route.rs | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/src/route.rs b/src/route.rs index 8cc1edfc2..45efd9e3c 100644 --- a/src/route.rs +++ b/src/route.rs @@ -16,24 +16,24 @@ use crate::responder::Responder; use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; -type BoxedRouteService = Box< +type BoxedRouteService = Box< dyn Service< - Request = Req, - Response = Res, + Request = ServiceRequest, + Response = ServiceResponse, Error = Error, - Future = LocalBoxFuture<'static, Result>, + Future = LocalBoxFuture<'static, Result>, >, >; -type BoxedRouteNewService = Box< +type BoxedRouteNewService = Box< dyn ServiceFactory< Config = (), - Request = Req, - Response = Res, + Request = ServiceRequest, + Response = ServiceResponse, Error = Error, InitError = (), - Service = BoxedRouteService, - Future = LocalBoxFuture<'static, Result, ()>>, + Service = BoxedRouteService, + Future = LocalBoxFuture<'static, Result>, >, >; @@ -42,7 +42,7 @@ type BoxedRouteNewService = Box< /// Route uses builder-like pattern for configuration. /// If handler is not explicitly set, default *404 Not Found* handler is used. pub struct Route { - service: BoxedRouteNewService, + service: BoxedRouteNewService, guards: Rc>>, } @@ -80,15 +80,8 @@ impl ServiceFactory for Route { } } -type RouteFuture = LocalBoxFuture< - 'static, - Result, ()>, ->; - -#[pin_project::pin_project] pub struct CreateRouteService { - #[pin] - fut: RouteFuture, + fut: LocalBoxFuture<'static, Result>, guards: Rc>>, } @@ -96,9 +89,9 @@ impl Future for CreateRouteService { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); + let this = self.get_mut(); - match this.fut.poll(cx)? { + match this.fut.as_mut().poll(cx)? { Poll::Ready(service) => Poll::Ready(Ok(RouteService { service, guards: this.guards.clone(), @@ -109,7 +102,7 @@ impl Future for CreateRouteService { } pub struct RouteService { - service: BoxedRouteService, + service: BoxedRouteService, guards: Rc>>, } @@ -275,12 +268,12 @@ where T::Service: 'static, ::Future: 'static, { - type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; + type Config = (); + type Service = BoxedRouteService; type InitError = (); - type Service = BoxedRouteService; type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { @@ -288,8 +281,7 @@ where .new_service(()) .map(|result| match result { Ok(service) => { - let service: BoxedRouteService<_, _> = - Box::new(RouteServiceWrapper { service }); + let service = Box::new(RouteServiceWrapper { service }) as _; Ok(service) } Err(_) => Err(()), From ae63eb8bb2576eeda9d261a358b954c3cd002fb3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 9 Dec 2020 11:22:19 +0000 Subject: [PATCH 38/81] fix clippy warnings (#1806) * fix clippy warnings * prevent CI fail status caused by codecov --- actix-http/src/header/common/content_disposition.rs | 3 +-- actix-http/src/header/shared/entity.rs | 8 +++++--- awc/src/ws.rs | 11 ++++++++--- codecov.yml | 11 ++++++++++- src/middleware/compress.rs | 5 +---- src/middleware/condition.rs | 1 + src/middleware/errhandlers.rs | 1 + src/types/payload.rs | 7 ++++--- 8 files changed, 31 insertions(+), 16 deletions(-) diff --git a/actix-http/src/header/common/content_disposition.rs b/actix-http/src/header/common/content_disposition.rs index 37da830ca..826cfef63 100644 --- a/actix-http/src/header/common/content_disposition.rs +++ b/actix-http/src/header/common/content_disposition.rs @@ -550,8 +550,7 @@ impl fmt::Display for ContentDisposition { write!(f, "{}", self.disposition)?; self.parameters .iter() - .map(|param| write!(f, "; {}", param)) - .collect() + .try_for_each(|param| write!(f, "; {}", param)) } } diff --git a/actix-http/src/header/shared/entity.rs b/actix-http/src/header/shared/entity.rs index 3525a19c6..344cfb864 100644 --- a/actix-http/src/header/shared/entity.rs +++ b/actix-http/src/header/shared/entity.rs @@ -7,10 +7,12 @@ use crate::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer}; /// 1. `%x21`, or /// 2. in the range `%x23` to `%x7E`, or /// 3. above `%x80` +fn entity_validate_char(c: u8) -> bool { + c == 0x21 || (0x23..=0x7e).contains(&c) || (c >= 0x80) +} + fn check_slice_validity(slice: &str) -> bool { - slice - .bytes() - .all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80')) + slice.bytes().all(entity_validate_char) } /// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3) diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 57e80bd46..aa474697b 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -70,9 +70,14 @@ impl WebsocketsRequest { >::Error: Into, { let mut err = None; - let mut head = RequestHead::default(); - head.method = Method::GET; - head.version = Version::HTTP_11; + + #[allow(clippy::field_reassign_with_default)] + let mut head = { + let mut head = RequestHead::default(); + head.method = Method::GET; + head.version = Version::HTTP_11; + head + }; match Uri::try_from(uri) { Ok(uri) => head.uri = uri, diff --git a/codecov.yml b/codecov.yml index 90cdfab47..102e8969d 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,4 +1,13 @@ -ignore: # ignore codecoverage on following paths +coverage: + status: + project: + default: + threshold: 10% # make CI green + patch: + default: + threshold: 10% # make CI green + +ignore: # ignore code coverage on following paths - "**/tests" - "test-server" - "**/benches" diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index fe3ba841c..7575d7455 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -192,10 +192,7 @@ impl AcceptEncoding { }; let quality = match parts.len() { 1 => encoding.quality(), - _ => match f64::from_str(parts[1]) { - Ok(q) => q, - Err(_) => 0.0, - }, + _ => f64::from_str(parts[1]).unwrap_or(0.0), }; Some(AcceptEncoding { encoding, quality }) } diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index ab1c69746..9061c7458 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -105,6 +105,7 @@ mod tests { use crate::test::{self, TestRequest}; use crate::HttpResponse; + #[allow(clippy::unnecessary_wraps)] fn render_500(mut res: ServiceResponse) -> Result> { res.response_mut() .headers_mut() diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index 93a5d3f22..c0cb9594e 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -154,6 +154,7 @@ mod tests { use crate::test::{self, TestRequest}; use crate::HttpResponse; + #[allow(clippy::unnecessary_wraps)] fn render_500(mut res: ServiceResponse) -> Result> { res.response_mut() .headers_mut() diff --git a/src/types/payload.rs b/src/types/payload.rs index 4ff5ef4b4..fdc1f8c8a 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -241,9 +241,10 @@ pub struct PayloadConfig { impl PayloadConfig { /// Create `PayloadConfig` instance and set max size of payload. pub fn new(limit: usize) -> Self { - let mut cfg = Self::default(); - cfg.limit = limit; - cfg + Self { + limit, + ..Default::default() + } } /// Change max size of payload. By default max size is 256Kb From 542db82282ab0455a28bd006d147c5510035f634 Mon Sep 17 00:00:00 2001 From: Juan Aguilar Date: Sat, 12 Dec 2020 21:07:06 +0100 Subject: [PATCH 39/81] Simplify wake up of task (#1826) --- actix-http/src/h1/payload.rs | 4 +--- actix-multipart/src/server.rs | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 6a348810c..d4cfee146 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -182,9 +182,7 @@ impl Inner { self.len += data.len(); self.items.push_back(data); self.need_read = self.len < MAX_BUFFER_SIZE; - if let Some(task) = self.task.take() { - task.wake() - } + self.task.wake(); } #[cfg(test)] diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index b9ebf97cc..b476f1791 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -725,9 +725,7 @@ impl Drop for Safety { if Rc::strong_count(&self.payload) != self.level { self.clean.set(true); } - if let Some(task) = self.task.take() { - task.wake() - } + self.task.wake(); } } From fabc68659bee4f9a04e8c0ac59a0e20c4e3804c1 Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Sun, 13 Dec 2020 18:58:39 +0530 Subject: [PATCH 40/81] Intradoc links conversion (#1827) * switching to nightly for intra-doc links * actix-files intra-doc conversion * more specific Result * intradoc conversion complete * rm blank comments and readme doc link fixes * macros and broken links --- actix-http-test/src/lib.rs | 2 +- actix-http/src/error.rs | 2 +- actix-http/src/header/common/mod.rs | 2 +- actix-http/src/header/map.rs | 4 ---- actix-web-codegen/README.md | 2 +- actix-web-codegen/src/lib.rs | 24 +++++++++++------------- awc/src/ws.rs | 2 +- src/config.rs | 2 +- src/info.rs | 2 +- src/lib.rs | 8 ++++---- src/middleware/logger.rs | 3 +-- src/server.rs | 2 +- src/service.rs | 6 +++--- src/types/form.rs | 2 +- src/types/json.rs | 6 +++--- src/types/path.rs | 2 +- src/types/payload.rs | 4 ++-- src/types/query.rs | 2 +- 18 files changed, 35 insertions(+), 42 deletions(-) diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index f881dfb4c..3ab3f8a0d 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -53,7 +53,7 @@ pub async fn test_server>(factory: F) -> TestServer test_server_with_addr(tcp, factory).await } -/// Start [`test server`](./fn.test_server.html) on a concrete Address +/// Start [`test server`](test_server()) on a concrete Address pub async fn test_server_with_addr>( tcp: net::TcpListener, factory: F, diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index e93c077af..0ebd4c05c 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -25,7 +25,7 @@ pub use crate::cookie::ParseError as CookieParseError; use crate::helpers::Writer; use crate::response::{Response, ResponseBuilder}; -/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) +/// A specialized [`std::result::Result`] /// for actix web operations /// /// This typedef is generally used to avoid writing out diff --git a/actix-http/src/header/common/mod.rs b/actix-http/src/header/common/mod.rs index 83489b864..c3d18613c 100644 --- a/actix-http/src/header/common/mod.rs +++ b/actix-http/src/header/common/mod.rs @@ -3,7 +3,7 @@ //! ## Mime //! //! Several header fields use MIME values for their contents. Keeping with the -//! strongly-typed theme, the [mime](https://docs.rs/mime) crate +//! strongly-typed theme, the [mime] crate //! is used, such as `ContentType(pub Mime)`. #![cfg_attr(rustfmt, rustfmt_skip)] diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 36c050b8f..6ab3509f7 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -8,8 +8,6 @@ use http::header::{HeaderName, HeaderValue}; /// A set of HTTP headers /// /// `HeaderMap` is an multi-map of [`HeaderName`] to values. -/// -/// [`HeaderName`]: struct.HeaderName.html #[derive(Debug, Clone)] pub struct HeaderMap { pub(crate) inner: FxHashMap, @@ -141,8 +139,6 @@ impl HeaderMap { /// The returned view does not incur any allocations and allows iterating /// the values associated with the key. See [`GetAll`] for more details. /// Returns `None` if there are no values associated with the key. - /// - /// [`GetAll`]: struct.GetAll.html pub fn get_all(&self, name: N) -> GetAll<'_> { GetAll { idx: 0, diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 6eca847b8..283591e86 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -3,7 +3,7 @@ > Helper and convenience macros for Actix Web [![crates.io](https://meritbadge.herokuapp.com/actix-web-codegen)](https://crates.io/crates/actix-web-codegen) -[![Documentation](https://docs.rs/actix-web-codegen/badge.svg)](https://docs.rs/actix-web) +[![Documentation](https://docs.rs/actix-web-codegen/badge.svg)](https://docs.rs/actix-web-codegen/0.4.0/actix_web_codegen/) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index af2bc7f18..50e5be712 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -8,7 +8,7 @@ //! are re-exported. //! //! # Runtime Setup -//! Used for setting up the actix async runtime. See [main] macro docs. +//! Used for setting up the actix async runtime. See [macro@main] macro docs. //! //! ```rust //! #[actix_web_codegen::main] // or `#[actix_web::main]` in Actix Web apps @@ -34,7 +34,7 @@ //! //! # Multiple Method Handlers //! Similar to the single method handler macro but takes one or more arguments for the HTTP methods -//! it should respond to. See [route] macro docs. +//! it should respond to. See [macro@route] macro docs. //! //! ```rust //! # use actix_web::HttpResponse; @@ -46,17 +46,15 @@ //! ``` //! //! [actix-web attributes docs]: https://docs.rs/actix-web/*/actix_web/#attributes -//! [main]: attr.main.html -//! [route]: attr.route.html -//! [GET]: attr.get.html -//! [POST]: attr.post.html -//! [PUT]: attr.put.html -//! [DELETE]: attr.delete.html -//! [HEAD]: attr.head.html -//! [CONNECT]: attr.connect.html -//! [OPTIONS]: attr.options.html -//! [TRACE]: attr.trace.html -//! [PATCH]: attr.patch.html +//! [GET]: macro@get +//! [POST]: macro@post +//! [PUT]: macro@put +//! [HEAD]: macro@head +//! [CONNECT]: macro@macro@connect +//! [OPTIONS]: macro@options +//! [TRACE]: macro@trace +//! [PATCH]: macro@patch +//! [DELETE]: macro@delete #![recursion_limit = "512"] diff --git a/awc/src/ws.rs b/awc/src/ws.rs index aa474697b..dd43d08b3 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -1,6 +1,6 @@ //! Websockets client //! -//! Type definitions required to use [`awc::Client`](../struct.Client.html) as a WebSocket client. +//! Type definitions required to use [`awc::Client`](super::Client) as a WebSocket client. //! //! # Example //! diff --git a/src/config.rs b/src/config.rs index 03ba82732..01959daa1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -141,7 +141,7 @@ impl AppConfig { /// Server host name. /// /// Host name is used by application router as a hostname for url generation. - /// Check [ConnectionInfo](./struct.ConnectionInfo.html#method.host) + /// Check [ConnectionInfo](super::dev::ConnectionInfo::host()) /// documentation for more information. /// /// By default host name is set to a "localhost" value. diff --git a/src/info.rs b/src/info.rs index 1d9b402a7..975604041 100644 --- a/src/info.rs +++ b/src/info.rs @@ -174,7 +174,7 @@ impl ConnectionInfo { /// Do not use this function for security purposes, unless you can ensure the Forwarded and /// X-Forwarded-For headers cannot be spoofed by the client. If you want the client's socket /// address explicitly, use - /// [`HttpRequest::peer_addr()`](../web/struct.HttpRequest.html#method.peer_addr) instead. + /// [`HttpRequest::peer_addr()`](super::web::HttpRequest::peer_addr()) instead. #[inline] pub fn realip_remote_addr(&self) -> Option<&str> { if let Some(ref r) = self.realip_remote_addr { diff --git a/src/lib.rs b/src/lib.rs index a8fc50d83..b8346d966 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,16 +29,16 @@ //! //! To get started navigating the API docs, you may consider looking at the following pages first: //! -//! * [App](struct.App.html): This struct represents an Actix web application and is used to +//! * [App]: This struct represents an Actix web application and is used to //! configure routes and other common application settings. //! -//! * [HttpServer](struct.HttpServer.html): This struct represents an HTTP server instance and is +//! * [HttpServer]: This struct represents an HTTP server instance and is //! used to instantiate and configure servers. //! -//! * [web](web/index.html): This module provides essential types for route registration as well as +//! * [web]: This module provides essential types for route registration as well as //! common utilities for request handlers. //! -//! * [HttpRequest](struct.HttpRequest.html) and [HttpResponse](struct.HttpResponse.html): These +//! * [HttpRequest] and [HttpResponse]: These //! structs represent HTTP requests and responses and expose methods for creating, inspecting, //! and otherwise utilizing them. //! diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index b2e5c791f..563cb6c32 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -82,11 +82,10 @@ use crate::HttpResponse; /// /// # Security /// **\*** It is calculated using -/// [`ConnectionInfo::realip_remote_addr()`](../dev/struct.ConnectionInfo.html#method.realip_remote_addr) +/// [`ConnectionInfo::realip_remote_addr()`](crate::dev::ConnectionInfo::realip_remote_addr()) /// /// If you use this value ensure that all requests come from trusted hosts, since it is trivial /// for the remote client to simulate being another client. -/// pub struct Logger(Rc); struct Inner { diff --git a/src/server.rs b/src/server.rs index 3badb6e8d..be97e8a0d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -213,7 +213,7 @@ where /// Set server host name. /// /// Host name is used by application router as a hostname for url generation. - /// Check [ConnectionInfo](./dev/struct.ConnectionInfo.html#method.host) + /// Check [ConnectionInfo](super::dev::ConnectionInfo::host()) /// documentation for more information. /// /// By default host name is set to a "localhost" value. diff --git a/src/service.rs b/src/service.rs index a861ba38c..189ba5554 100644 --- a/src/service.rs +++ b/src/service.rs @@ -195,13 +195,13 @@ impl ServiceRequest { self.0.match_info() } - /// Counterpart to [`HttpRequest::match_name`](../struct.HttpRequest.html#method.match_name). + /// Counterpart to [`HttpRequest::match_name`](super::HttpRequest::match_name()). #[inline] pub fn match_name(&self) -> Option<&str> { self.0.match_name() } - /// Counterpart to [`HttpRequest::match_pattern`](../struct.HttpRequest.html#method.match_pattern). + /// Counterpart to [`HttpRequest::match_pattern`](super::HttpRequest::match_pattern()). #[inline] pub fn match_pattern(&self) -> Option { self.0.match_pattern() @@ -225,7 +225,7 @@ impl ServiceRequest { self.0.app_config() } - /// Counterpart to [`HttpRequest::app_data`](../struct.HttpRequest.html#method.app_data). + /// Counterpart to [`HttpRequest::app_data`](super::HttpRequest::app_data()). pub fn app_data(&self) -> Option<&T> { for container in (self.0).0.app_data.iter().rev() { if let Some(data) = container.get::() { diff --git a/src/types/form.rs b/src/types/form.rs index 2a7101287..82ea73216 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -35,7 +35,7 @@ use crate::{responder::Responder, web}; /// To extract typed information from request's body, the type `T` must /// implement the `Deserialize` trait from *serde*. /// -/// [**FormConfig**](struct.FormConfig.html) allows to configure extraction +/// [**FormConfig**](FormConfig) allows to configure extraction /// process. /// /// ### Example diff --git a/src/types/json.rs b/src/types/json.rs index 081a022e8..83c9f21b0 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -31,7 +31,7 @@ use crate::{responder::Responder, web}; /// To extract typed information from request's body, the type `T` must /// implement the `Deserialize` trait from *serde*. /// -/// [**JsonConfig**](struct.JsonConfig.html) allows to configure extraction +/// [**JsonConfig**](JsonConfig) allows to configure extraction /// process. /// /// ## Example @@ -142,7 +142,7 @@ impl Responder for Json { /// To extract typed information from request's body, the type `T` must /// implement the `Deserialize` trait from *serde*. /// -/// [**JsonConfig**](struct.JsonConfig.html) allows to configure extraction +/// [**JsonConfig**](JsonConfig) allows to configure extraction /// process. /// /// ## Example @@ -306,7 +306,7 @@ impl Default for JsonConfig { /// Returns error: /// /// * content type is not `application/json` -/// (unless specified in [`JsonConfig`](struct.JsonConfig.html)) +/// (unless specified in [`JsonConfig`]) /// * content length is greater than 256k pub struct JsonBody { limit: usize, diff --git a/src/types/path.rs b/src/types/path.rs index dbb5f3ee0..640ff4346 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -15,7 +15,7 @@ use crate::FromRequest; #[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from the request's path. /// -/// [**PathConfig**](struct.PathConfig.html) allows to configure extraction process. +/// [**PathConfig**](PathConfig) allows to configure extraction process. /// /// ## Example /// diff --git a/src/types/payload.rs b/src/types/payload.rs index fdc1f8c8a..acb8b9a82 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -111,7 +111,7 @@ impl FromRequest for Payload { /// /// Loads request's payload and construct Bytes instance. /// -/// [**PayloadConfig**](struct.PayloadConfig.html) allows to configure +/// [**PayloadConfig**](PayloadConfig) allows to configure /// extraction process. /// /// ## Example @@ -159,7 +159,7 @@ impl FromRequest for Bytes { /// /// Text extractor automatically decode body according to the request's charset. /// -/// [**PayloadConfig**](struct.PayloadConfig.html) allows to configure +/// [**PayloadConfig**](PayloadConfig) allows to configure /// extraction process. /// /// ## Example diff --git a/src/types/query.rs b/src/types/query.rs index 7eded49c5..27df220fc 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -18,7 +18,7 @@ use crate::request::HttpRequest; /// be decoded into any type which depends upon data ordering e.g. tuples or tuple-structs. /// Attempts to do so will *fail at runtime*. /// -/// [**QueryConfig**](struct.QueryConfig.html) allows to configure extraction process. +/// [**QueryConfig**](QueryConfig) allows to configure extraction process. /// /// ## Example /// From d7ce6484457be229c4c9b4fcd3581d599b7ff9f1 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 17 Dec 2020 02:34:10 +0800 Subject: [PATCH 41/81] remove boxed future for Option and Result extract type (#1829) * remove boxed future for Option and Result extract type * use ready macro * fix fmt --- src/extract.rs | 81 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 22 deletions(-) diff --git a/src/extract.rs b/src/extract.rs index df9c34cb3..5916b1bc5 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -4,7 +4,8 @@ use std::pin::Pin; use std::task::{Context, Poll}; use actix_http::error::Error; -use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; +use futures_util::future::{ready, Ready}; +use futures_util::ready; use crate::dev::Payload; use crate::request::HttpRequest; @@ -95,21 +96,41 @@ where T: FromRequest, T::Future: 'static, { - type Config = T::Config; type Error = Error; - type Future = LocalBoxFuture<'static, Result, Error>>; + type Future = FromRequestOptFuture; + type Config = T::Config; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - T::from_request(req, payload) - .then(|r| match r { - Ok(v) => ok(Some(v)), - Err(e) => { - log::debug!("Error for Option extractor: {}", e.into()); - ok(None) - } - }) - .boxed_local() + FromRequestOptFuture { + fut: T::from_request(req, payload), + } + } +} + +#[pin_project::pin_project] +pub struct FromRequestOptFuture { + #[pin] + fut: Fut, +} + +impl Future for FromRequestOptFuture +where + Fut: Future>, + E: Into, +{ + type Output = Result, Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let res = ready!(this.fut.poll(cx)); + match res { + Ok(t) => Poll::Ready(Ok(Some(t))), + Err(e) => { + log::debug!("Error for Option extractor: {}", e.into()); + Poll::Ready(Ok(None)) + } + } } } @@ -165,29 +186,45 @@ where T::Error: 'static, T::Future: 'static, { - type Config = T::Config; type Error = Error; - type Future = LocalBoxFuture<'static, Result, Error>>; + type Future = FromRequestResFuture; + type Config = T::Config; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - T::from_request(req, payload) - .then(|res| match res { - Ok(v) => ok(Ok(v)), - Err(e) => ok(Err(e)), - }) - .boxed_local() + FromRequestResFuture { + fut: T::from_request(req, payload), + } + } +} + +#[pin_project::pin_project] +pub struct FromRequestResFuture { + #[pin] + fut: Fut, +} + +impl Future for FromRequestResFuture +where + Fut: Future>, +{ + type Output = Result, Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let res = ready!(this.fut.poll(cx)); + Poll::Ready(Ok(res)) } } #[doc(hidden)] impl FromRequest for () { - type Config = (); type Error = Error; type Future = Ready>; + type Config = (); fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future { - ok(()) + ready(Ok(())) } } From 1a361273e7bf15fb43b8f7a10334a0edae4fde0a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 16 Dec 2020 22:40:26 +0000 Subject: [PATCH 42/81] optimize bytes and string payload extractors (#1831) --- src/types/payload.rs | 72 ++++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/src/types/payload.rs b/src/types/payload.rs index acb8b9a82..fd4d3e945 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -7,10 +7,15 @@ use std::task::{Context, Poll}; use actix_http::error::{Error, ErrorBadRequest, PayloadError}; use actix_http::HttpMessage; use bytes::{Bytes, BytesMut}; -use encoding_rs::UTF_8; +use encoding_rs::{Encoding, UTF_8}; use futures_core::stream::Stream; -use futures_util::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready}; -use futures_util::StreamExt; +use futures_util::{ + future::{ + err, ok, Either, ErrInto, FutureExt as _, LocalBoxFuture, Ready, + TryFutureExt as _, + }, + stream::StreamExt as _, +}; use mime::Mime; use crate::extract::FromRequest; @@ -135,10 +140,7 @@ impl FromRequest for Payload { impl FromRequest for Bytes { type Config = PayloadConfig; type Error = Error; - type Future = Either< - LocalBoxFuture<'static, Result>, - Ready>, - >; + type Future = Either, Ready>>; #[inline] fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { @@ -151,7 +153,7 @@ impl FromRequest for Bytes { let limit = cfg.limit; let fut = HttpMessageBody::new(req, payload).limit(limit); - Either::Left(async move { Ok(fut.await?) }.boxed_local()) + Either::Left(fut.err_into()) } } @@ -185,10 +187,7 @@ impl FromRequest for Bytes { impl FromRequest for String { type Config = PayloadConfig; type Error = Error; - type Future = Either< - LocalBoxFuture<'static, Result>, - Ready>, - >; + type Future = Either>>; #[inline] fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { @@ -205,25 +204,40 @@ impl FromRequest for String { Err(e) => return Either::Right(err(e.into())), }; let limit = cfg.limit; - let fut = HttpMessageBody::new(req, payload).limit(limit); + let body_fut = HttpMessageBody::new(req, payload).limit(limit); - Either::Left( - async move { - let body = fut.await?; + Either::Left(StringExtractFut { body_fut, encoding }) + } +} - if encoding == UTF_8 { - Ok(str::from_utf8(body.as_ref()) - .map_err(|_| ErrorBadRequest("Can not decode body"))? - .to_owned()) - } else { - Ok(encoding - .decode_without_bom_handling_and_without_replacement(&body) - .map(|s| s.into_owned()) - .ok_or_else(|| ErrorBadRequest("Can not decode body"))?) - } - } - .boxed_local(), - ) +pub struct StringExtractFut { + body_fut: HttpMessageBody, + encoding: &'static Encoding, +} + +impl<'a> Future for StringExtractFut { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let encoding = self.encoding; + + Pin::new(&mut self.body_fut).poll(cx).map(|out| { + let body = out?; + bytes_to_string(body, encoding) + }) + } +} + +fn bytes_to_string(body: Bytes, encoding: &'static Encoding) -> Result { + if encoding == UTF_8 { + Ok(str::from_utf8(body.as_ref()) + .map_err(|_| ErrorBadRequest("Can not decode body"))? + .to_owned()) + } else { + Ok(encoding + .decode_without_bom_handling_and_without_replacement(&body) + .map(|s| s.into_owned()) + .ok_or_else(|| ErrorBadRequest("Can not decode body"))?) } } From 97f615c245a9ad86a129a94e50a600f2006da306 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 17 Dec 2020 07:34:33 +0800 Subject: [PATCH 43/81] remove boxed futures on Json extract type (#1832) --- src/types/json.rs | 211 ++++++++++++++++++++++++++++------------------ 1 file changed, 128 insertions(+), 83 deletions(-) diff --git a/src/types/json.rs b/src/types/json.rs index 83c9f21b0..95613a0ce 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -1,14 +1,16 @@ //! Json extractor/responder use std::future::Future; +use std::marker::PhantomData; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; use std::{fmt, ops}; use bytes::BytesMut; -use futures_util::future::{err, ok, FutureExt, LocalBoxFuture, Ready}; -use futures_util::StreamExt; +use futures_util::future::{ready, Ready}; +use futures_util::ready; +use futures_util::stream::Stream; use serde::de::DeserializeOwned; use serde::Serialize; @@ -127,12 +129,12 @@ impl Responder for Json { fn respond_to(self, _: &HttpRequest) -> Self::Future { let body = match serde_json::to_string(&self.0) { Ok(body) => body, - Err(e) => return err(e.into()), + Err(e) => return ready(Err(e.into())), }; - ok(Response::build(StatusCode::OK) + ready(Ok(Response::build(StatusCode::OK) .content_type("application/json") - .body(body)) + .body(body))) } } @@ -173,37 +175,64 @@ where T: DeserializeOwned + 'static, { type Error = Error; - type Future = LocalBoxFuture<'static, Result>; + type Future = JsonExtractFut; type Config = JsonConfig; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - let req2 = req.clone(); let config = JsonConfig::from_req(req); let limit = config.limit; let ctype = config.content_type.clone(); let err_handler = config.err_handler.clone(); - JsonBody::new(req, payload, ctype) - .limit(limit) - .map(move |res| match res { - Err(e) => { - log::debug!( - "Failed to deserialize Json from payload. \ - Request path: {}", - req2.path() - ); + JsonExtractFut { + req: Some(req.clone()), + fut: JsonBody::new(req, payload, ctype).limit(limit), + err_handler, + } + } +} - if let Some(err) = err_handler { - Err((*err)(e, &req2)) - } else { - Err(e.into()) - } +type JsonErrorHandler = + Option Error + Send + Sync>>; + +pub struct JsonExtractFut { + req: Option, + fut: JsonBody, + err_handler: JsonErrorHandler, +} + +impl Future for JsonExtractFut +where + T: DeserializeOwned + 'static, +{ + type Output = Result, Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + + let res = ready!(Pin::new(&mut this.fut).poll(cx)); + + let res = match res { + Err(e) => { + let req = this.req.take().unwrap(); + log::debug!( + "Failed to deserialize Json from payload. \ + Request path: {}", + req.path() + ); + + if let Some(err) = this.err_handler.as_ref() { + Err((*err)(e, &req)) + } else { + Err(e.into()) } - Ok(data) => Ok(Json(data)), - }) - .boxed_local() + } + Ok(data) => Ok(Json(data)), + }; + + Poll::Ready(res) } } @@ -248,8 +277,7 @@ where #[derive(Clone)] pub struct JsonConfig { limit: usize, - err_handler: - Option Error + Send + Sync>>, + err_handler: JsonErrorHandler, content_type: Option bool + Send + Sync>>, } @@ -308,17 +336,22 @@ impl Default for JsonConfig { /// * content type is not `application/json` /// (unless specified in [`JsonConfig`]) /// * content length is greater than 256k -pub struct JsonBody { - limit: usize, - length: Option, - #[cfg(feature = "compress")] - stream: Option>, - #[cfg(not(feature = "compress"))] - stream: Option, - err: Option, - fut: Option>>, +pub enum JsonBody { + Error(Option), + Body { + limit: usize, + length: Option, + #[cfg(feature = "compress")] + payload: Decompress, + #[cfg(not(feature = "compress"))] + payload: Payload, + buf: BytesMut, + _res: PhantomData, + }, } +impl Unpin for JsonBody {} + impl JsonBody where U: DeserializeOwned + 'static, @@ -340,39 +373,58 @@ where }; if !json { - return JsonBody { - limit: 262_144, - length: None, - stream: None, - fut: None, - err: Some(JsonPayloadError::ContentType), - }; + return JsonBody::Error(Some(JsonPayloadError::ContentType)); } - let len = req + let length = req .headers() .get(&CONTENT_LENGTH) .and_then(|l| l.to_str().ok()) .and_then(|s| s.parse::().ok()); + // Notice the content_length is not checked against limit of json config here. + // As the internal usage always call JsonBody::limit after JsonBody::new. + // And limit check to return an error variant of JsonBody happens there. + #[cfg(feature = "compress")] let payload = Decompress::from_headers(payload.take(), req.headers()); #[cfg(not(feature = "compress"))] let payload = payload.take(); - JsonBody { + JsonBody::Body { limit: 262_144, - length: len, - stream: Some(payload), - fut: None, - err: None, + length, + payload, + buf: BytesMut::with_capacity(8192), + _res: PhantomData, } } /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self + pub fn limit(self, limit: usize) -> Self { + match self { + JsonBody::Body { + length, + payload, + buf, + .. + } => { + if let Some(len) = length { + if len > limit { + return JsonBody::Error(Some(JsonPayloadError::Overflow)); + } + } + + JsonBody::Body { + limit, + length, + payload, + buf, + _res: PhantomData, + } + } + JsonBody::Error(e) => JsonBody::Error(e), + } } } @@ -382,41 +434,34 @@ where { type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if let Some(ref mut fut) = self.fut { - return Pin::new(fut).poll(cx); - } + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); - if let Some(err) = self.err.take() { - return Poll::Ready(Err(err)); - } - - let limit = self.limit; - if let Some(len) = self.length.take() { - if len > limit { - return Poll::Ready(Err(JsonPayloadError::Overflow)); - } - } - let mut stream = self.stream.take().unwrap(); - - self.fut = Some( - async move { - let mut body = BytesMut::with_capacity(8192); - - while let Some(item) = stream.next().await { - let chunk = item?; - if (body.len() + chunk.len()) > limit { - return Err(JsonPayloadError::Overflow); - } else { - body.extend_from_slice(&chunk); + match this { + JsonBody::Body { + limit, + buf, + payload, + .. + } => loop { + let res = ready!(Pin::new(&mut *payload).poll_next(cx)); + match res { + Some(chunk) => { + let chunk = chunk?; + if (buf.len() + chunk.len()) > *limit { + return Poll::Ready(Err(JsonPayloadError::Overflow)); + } else { + buf.extend_from_slice(&chunk); + } + } + None => { + let json = serde_json::from_slice::(&buf)?; + return Poll::Ready(Ok(json)); } } - Ok(serde_json::from_slice::(&body)?) - } - .boxed_local(), - ); - - self.poll(cx) + }, + JsonBody::Error(e) => Poll::Ready(Err(e.take().unwrap())), + } } } From 2a5215c1d6cce12ff3f4bdc4e7ac73190d4aa9e0 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 17 Dec 2020 19:40:49 +0800 Subject: [PATCH 44/81] Remove boxed future from HttpMessage (#1834) --- src/types/payload.rs | 109 ++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 63 deletions(-) diff --git a/src/types/payload.rs b/src/types/payload.rs index fd4d3e945..9228b37aa 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -10,11 +10,8 @@ use bytes::{Bytes, BytesMut}; use encoding_rs::{Encoding, UTF_8}; use futures_core::stream::Stream; use futures_util::{ - future::{ - err, ok, Either, ErrInto, FutureExt as _, LocalBoxFuture, Ready, - TryFutureExt as _, - }, - stream::StreamExt as _, + future::{err, ok, Either, ErrInto, Ready, TryFutureExt as _}, + ready, }; use mime::Mime; @@ -305,10 +302,12 @@ impl PayloadConfig { // Allow shared refs to default. const DEFAULT_CONFIG: PayloadConfig = PayloadConfig { - limit: 262_144, // 2^18 bytes (~256kB) + limit: DEFAULT_CONFIG_LIMIT, mimetype: None, }; +const DEFAULT_CONFIG_LIMIT: usize = 262_144; // 2^18 bytes (~256kB) + impl Default for PayloadConfig { fn default() -> Self { DEFAULT_CONFIG.clone() @@ -326,99 +325,83 @@ pub struct HttpMessageBody { limit: usize, length: Option, #[cfg(feature = "compress")] - stream: Option>, + stream: dev::Decompress, #[cfg(not(feature = "compress"))] - stream: Option, + stream: dev::Payload, + buf: BytesMut, err: Option, - fut: Option>>, } impl HttpMessageBody { /// Create `MessageBody` for request. #[allow(clippy::borrow_interior_mutable_const)] pub fn new(req: &HttpRequest, payload: &mut dev::Payload) -> HttpMessageBody { - let mut len = None; + let mut length = None; + let mut err = 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); + match l.to_str() { + Ok(s) => match s.parse::() { + Ok(l) if l > DEFAULT_CONFIG_LIMIT => { + err = Some(PayloadError::Overflow) + } + Ok(l) => length = Some(l), + Err(_) => err = Some(PayloadError::UnknownLength), + }, + Err(_) => err = Some(PayloadError::UnknownLength), } } #[cfg(feature = "compress")] - let stream = Some(dev::Decompress::from_headers(payload.take(), req.headers())); + let stream = dev::Decompress::from_headers(payload.take(), req.headers()); #[cfg(not(feature = "compress"))] - let stream = Some(payload.take()); + let stream = payload.take(); HttpMessageBody { stream, - limit: 262_144, - length: len, - fut: None, - err: None, + limit: DEFAULT_CONFIG_LIMIT, + length, + buf: BytesMut::with_capacity(8192), + err, } } /// Change max size of payload. By default max size is 256Kb pub fn limit(mut self, limit: usize) -> Self { + if let Some(l) = self.length { + if l > limit { + self.err = Some(PayloadError::Overflow); + } + } self.limit = limit; self } - - fn err(e: PayloadError) -> Self { - HttpMessageBody { - stream: None, - limit: 262_144, - fut: None, - err: Some(e), - length: None, - } - } } impl Future for HttpMessageBody { type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if let Some(ref mut fut) = self.fut { - return Pin::new(fut).poll(cx); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + + if let Some(e) = this.err.take() { + return Poll::Ready(Err(e)); } - if let Some(err) = self.err.take() { - return Poll::Ready(Err(err)); - } - - if let Some(len) = self.length.take() { - if len > self.limit { - return Poll::Ready(Err(PayloadError::Overflow)); - } - } - - // future - let limit = self.limit; - let mut stream = self.stream.take().unwrap(); - self.fut = Some( - async move { - let mut body = BytesMut::with_capacity(8192); - - while let Some(item) = stream.next().await { - let chunk = item?; - if body.len() + chunk.len() > limit { - return Err(PayloadError::Overflow); + loop { + let res = ready!(Pin::new(&mut this.stream).poll_next(cx)); + match res { + Some(chunk) => { + let chunk = chunk?; + if this.buf.len() + chunk.len() > this.limit { + return Poll::Ready(Err(PayloadError::Overflow)); } else { - body.extend_from_slice(&chunk); + this.buf.extend_from_slice(&chunk); } } - Ok(body.freeze()) + None => return Poll::Ready(Ok(this.buf.split().freeze())), } - .boxed_local(), - ); - self.poll(cx) + } } } From c7b4c6edfa0a3e7e6c6618584ec189ed5b3d99bd Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Thu, 17 Dec 2020 21:38:52 +0900 Subject: [PATCH 45/81] Disable PR comment from codecov --- codecov.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codecov.yml b/codecov.yml index 102e8969d..e6bc40203 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,3 +1,5 @@ +comment: false + coverage: status: project: From a4dbaa8ed11651b33125d39d8cee0f90a3bfed61 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 19 Dec 2020 07:08:59 +0800 Subject: [PATCH 46/81] remove boxed future in DefaultHeaders middleware (#1838) --- src/middleware/defaultheaders.rs | 72 +++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 6d43aba95..a6f1a4336 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -1,10 +1,14 @@ //! Middleware for setting default response headers use std::convert::TryFrom; +use std::future::Future; +use std::marker::PhantomData; +use std::pin::Pin; use std::rc::Rc; use std::task::{Context, Poll}; use actix_service::{Service, Transform}; -use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; +use futures_util::future::{ready, Ready}; +use futures_util::ready; use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; use crate::http::{Error as HttpError, HeaderMap}; @@ -97,15 +101,15 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type InitError = (); type Transform = DefaultHeadersMiddleware; + type InitError = (); type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { - ok(DefaultHeadersMiddleware { + ready(Ok(DefaultHeadersMiddleware { service, inner: self.inner.clone(), - }) + })) } } @@ -122,36 +126,56 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = LocalBoxFuture<'static, Result>; + type Future = DefaultHeaderFuture; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.service.poll_ready(cx) } - #[allow(clippy::borrow_interior_mutable_const)] fn call(&mut self, req: ServiceRequest) -> Self::Future { let inner = self.inner.clone(); let fut = self.service.call(req); - async move { - let mut res = fut.await?; - - // set response headers - for (key, value) in inner.headers.iter() { - if !res.headers().contains_key(key) { - res.headers_mut().insert(key.clone(), value.clone()); - } - } - // default content-type - if inner.ct && !res.headers().contains_key(&CONTENT_TYPE) { - res.headers_mut().insert( - CONTENT_TYPE, - HeaderValue::from_static("application/octet-stream"), - ); - } - Ok(res) + DefaultHeaderFuture { + fut, + inner, + _body: PhantomData, } - .boxed_local() + } +} + +#[pin_project::pin_project] +pub struct DefaultHeaderFuture { + #[pin] + fut: S::Future, + inner: Rc, + _body: PhantomData, +} + +impl Future for DefaultHeaderFuture +where + S: Service, Error = Error>, +{ + type Output = ::Output; + + #[allow(clippy::borrow_interior_mutable_const)] + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let mut res = ready!(this.fut.poll(cx))?; + // set response headers + for (key, value) in this.inner.headers.iter() { + if !res.headers().contains_key(key) { + res.headers_mut().insert(key.clone(), value.clone()); + } + } + // default content-type + if this.inner.ct && !res.headers().contains_key(&CONTENT_TYPE) { + res.headers_mut().insert( + CONTENT_TYPE, + HeaderValue::from_static("application/octet-stream"), + ); + } + Poll::Ready(Ok(res)) } } From 79de04d8625deff301fe76fdf8649543f2976ae5 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 20 Dec 2020 00:33:34 +0800 Subject: [PATCH 47/81] optimise Extract service (#1841) --- src/handler.rs | 110 +++++++++++++++++++++---------------------------- 1 file changed, 47 insertions(+), 63 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 669512ab3..db6c5ce0a 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -90,26 +90,20 @@ where } fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { - HandlerServiceResponse { - fut: self.hnd.call(param), - fut2: None, - req: Some(req), - } + let fut = self.hnd.call(param); + HandlerServiceResponse::Future(fut, Some(req)) } } #[doc(hidden)] -#[pin_project] -pub struct HandlerServiceResponse +#[pin_project(project = HandlerProj)] +pub enum HandlerServiceResponse where T: Future, R: Responder, { - #[pin] - fut: T, - #[pin] - fut2: Option, - req: Option, + Future(#[pin] T, Option), + Responder(#[pin] R::Future, Option), } impl Future for HandlerServiceResponse @@ -120,28 +114,26 @@ where type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - - if let Some(fut) = this.fut2.as_pin_mut() { - return match fut.poll(cx) { - Poll::Ready(Ok(res)) => { - Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res))) + loop { + match self.as_mut().project() { + HandlerProj::Future(fut, req) => { + let res = ready!(fut.poll(cx)); + let fut = res.respond_to(req.as_ref().unwrap()); + let state = HandlerServiceResponse::Responder(fut, req.take()); + self.as_mut().set(state); } - Poll::Pending => Poll::Pending, - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res))) + HandlerProj::Responder(fut, req) => { + let res = ready!(fut.poll(cx)); + let req = req.take().unwrap(); + return match res { + Ok(res) => Poll::Ready(Ok(ServiceResponse::new(req, res))), + Err(e) => { + let res: Response = e.into().into(); + Poll::Ready(Ok(ServiceResponse::new(req, res))) + } + }; } - }; - } - - match this.fut.poll(cx) { - Poll::Ready(res) => { - let fut = res.respond_to(this.req.as_ref().unwrap()); - self.as_mut().project().fut2.set(Some(fut)); - self.poll(cx) } - Poll::Pending => Poll::Pending, } } } @@ -169,12 +161,12 @@ where Error = Infallible, > + Clone, { - type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = (Error, ServiceRequest); - type InitError = (); + type Config = (); type Service = ExtractService; + type InitError = (); type Future = Ready>; fn new_service(&self, _: ()) -> Self::Future { @@ -210,24 +202,14 @@ where fn call(&mut self, req: ServiceRequest) -> Self::Future { let (req, mut payload) = req.into_parts(); let fut = T::from_request(&req, &mut payload); - - ExtractResponse { - fut, - req, - fut_s: None, - service: self.service.clone(), - } + ExtractResponse::Future(fut, Some(req), self.service.clone()) } } -#[pin_project] -pub struct ExtractResponse { - req: HttpRequest, - service: S, - #[pin] - fut: T::Future, - #[pin] - fut_s: Option, +#[pin_project(project = ExtractProj)] +pub enum ExtractResponse { + Future(#[pin] T::Future, Option, S), + Response(#[pin] S::Future), } impl Future for ExtractResponse @@ -241,21 +223,23 @@ where type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - - if let Some(fut) = this.fut_s.as_pin_mut() { - return fut.poll(cx).map_err(|_| panic!()); - } - - match ready!(this.fut.poll(cx)) { - Err(e) => { - let req = ServiceRequest::new(this.req.clone()); - Poll::Ready(Err((e.into(), req))) - } - Ok(item) => { - let fut = Some(this.service.call((item, this.req.clone()))); - self.as_mut().project().fut_s.set(fut); - self.poll(cx) + loop { + match self.as_mut().project() { + ExtractProj::Future(fut, req, srv) => { + let res = ready!(fut.poll(cx)); + let req = req.take().unwrap(); + match res { + Err(e) => { + let req = ServiceRequest::new(req); + return Poll::Ready(Err((e.into(), req))); + } + Ok(item) => { + let fut = srv.call((item, req)); + self.as_mut().set(ExtractResponse::Response(fut)); + } + } + } + ExtractProj::Response(fut) => return fut.poll(cx).map_err(|_| panic!()), } } } From 6cbf27508af7e8c38d2fe174fdb26062b35340ed Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 20 Dec 2020 10:20:29 +0800 Subject: [PATCH 48/81] simplify ExtractService's return type (#1842) --- src/handler.rs | 8 ++++---- src/route.rs | 32 ++++++-------------------------- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index db6c5ce0a..0dc06b3ce 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -163,7 +163,7 @@ where { type Request = ServiceRequest; type Response = ServiceResponse; - type Error = (Error, ServiceRequest); + type Error = Error; type Config = (); type Service = ExtractService; type InitError = (); @@ -192,7 +192,7 @@ where { type Request = ServiceRequest; type Response = ServiceResponse; - type Error = (Error, ServiceRequest); + type Error = Error; type Future = ExtractResponse; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { @@ -220,7 +220,7 @@ where Error = Infallible, >, { - type Output = Result; + type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { loop { @@ -231,7 +231,7 @@ where match res { Err(e) => { let req = ServiceRequest::new(req); - return Poll::Ready(Err((e.into(), req))); + return Poll::Ready(Ok(req.error_response(e.into()))); } Ok(item) => { let fut = srv.call((item, req)); diff --git a/src/route.rs b/src/route.rs index 45efd9e3c..f8ef458f9 100644 --- a/src/route.rs +++ b/src/route.rs @@ -234,7 +234,7 @@ impl Route { struct RouteNewService where - T: ServiceFactory, + T: ServiceFactory, { service: T, } @@ -245,7 +245,7 @@ where Config = (), Request = ServiceRequest, Response = ServiceResponse, - Error = (Error, ServiceRequest), + Error = Error, >, T::Future: 'static, T::Service: 'static, @@ -262,7 +262,7 @@ where Config = (), Request = ServiceRequest, Response = ServiceResponse, - Error = (Error, ServiceRequest), + Error = Error, >, T::Future: 'static, T::Service: 'static, @@ -297,11 +297,7 @@ struct RouteServiceWrapper { impl Service for RouteServiceWrapper where T::Future: 'static, - T: Service< - Request = ServiceRequest, - Response = ServiceResponse, - Error = (Error, ServiceRequest), - >, + T: Service, { type Request = ServiceRequest; type Response = ServiceResponse; @@ -309,27 +305,11 @@ where type Future = LocalBoxFuture<'static, Result>; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx).map_err(|(e, _)| e) + self.service.poll_ready(cx) } fn call(&mut self, req: ServiceRequest) -> Self::Future { - // let mut fut = self.service.call(req); - self.service - .call(req) - .map(|res| match res { - Ok(res) => Ok(res), - Err((err, req)) => Ok(req.error_response(err)), - }) - .boxed_local() - - // match fut.poll() { - // Poll::Ready(Ok(res)) => Either::Left(ok(res)), - // Poll::Ready(Err((e, req))) => Either::Left(ok(req.error_response(e))), - // Poll::Pending => Either::Right(Box::new(fut.then(|res| match res { - // Ok(res) => Ok(res), - // Err((err, req)) => Ok(req.error_response(err)), - // }))), - // } + Box::pin(self.service.call(req)) } } From 95ccf1c9bc09e24f11126c6e48927d8e5ea3cf9c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 22 Dec 2020 00:42:20 +0800 Subject: [PATCH 49/81] replace actix_utils::oneshot with futures_channle::oneshot (#1844) --- actix-http/src/client/pool.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 08abc6277..a8687dbeb 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -9,8 +9,9 @@ use std::time::{Duration, Instant}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::time::{delay_for, Delay}; use actix_service::Service; -use actix_utils::{oneshot, task::LocalWaker}; +use actix_utils::task::LocalWaker; use bytes::Bytes; +use futures_channel::oneshot; use futures_util::future::{poll_fn, FutureExt, LocalBoxFuture}; use fxhash::FxHashMap; use h2::client::{Connection, SendRequest}; From 4dccd092f36bab2856a65bd7a0edf06727f2d841 Mon Sep 17 00:00:00 2001 From: Logan Magee Date: Tue, 22 Dec 2020 14:45:31 -0900 Subject: [PATCH 50/81] Bump rand from 0.7.x to 0.8.x (#1845) --- CHANGES.md | 3 +++ Cargo.toml | 2 +- actix-http/CHANGES.md | 3 ++- actix-http/Cargo.toml | 2 +- awc/CHANGES.md | 2 ++ awc/Cargo.toml | 2 +- awc/tests/test_client.rs | 2 ++ tests/test_server.rs | 5 +++++ 8 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 87c021b1e..ee9b9308d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2020-xx-xx +### Changed +* Bumped `rand` to `0.8` + ### Fixed * added the actual parsing error to `test::read_body_json` [#1812] diff --git a/Cargo.toml b/Cargo.toml index 31c4cca7e..6ed327f56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,7 +112,7 @@ tinyvec = { version = "1", features = ["alloc"] } [dev-dependencies] actix = "0.10.0" actix-http = { version = "2.1.0", features = ["actors"] } -rand = "0.7" +rand = "0.8" env_logger = "0.8" serde_derive = "1.0" brotli2 = "0.3.2" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c602ab2e1..81577688d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,7 +1,8 @@ # Changes ## Unreleased - 2020-xx-xx - +### Changed +* Bumped `rand` to `0.8` ## 2.2.0 - 2020-11-25 ### Added diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 7375c6eb3..7cf344487 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -72,7 +72,7 @@ log = "0.4" mime = "0.3" percent-encoding = "2.1" pin-project = "1.0.0" -rand = "0.7" +rand = "0.8" regex = "1.3" serde = "1.0" serde_json = "1.0" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 7ca415336..e4f801bbe 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2020-xx-xx +### Changed +* Bumped `rand` to `0.8` ## 2.0.3 - 2020-11-29 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 3c1963d6b..2e92526d2 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -50,7 +50,7 @@ futures-core = { version = "0.3.5", default-features = false } log =" 0.4" mime = "0.3" percent-encoding = "2.1" -rand = "0.7" +rand = "0.8" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index a9552d0d5..0024c6652 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -480,6 +480,7 @@ async fn test_client_gzip_encoding_large_random() { let data = rand::thread_rng() .sample_iter(&rand::distributions::Alphanumeric) .take(100_000) + .map(char::from) .collect::(); let srv = test::start(|| { @@ -529,6 +530,7 @@ async fn test_client_brotli_encoding_large_random() { let data = rand::thread_rng() .sample_iter(&rand::distributions::Alphanumeric) .take(70_000) + .map(char::from) .collect::(); let srv = test::start(|| { diff --git a/tests/test_server.rs b/tests/test_server.rs index f8a9ab86d..c6c316f0d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -248,6 +248,7 @@ async fn test_body_gzip_large_random() { let data = rand::thread_rng() .sample_iter(&Alphanumeric) .take(70_000) + .map(char::from) .collect::(); let srv_data = data.clone(); @@ -529,6 +530,7 @@ async fn test_reading_gzip_encoding_large_random() { let data = rand::thread_rng() .sample_iter(&Alphanumeric) .take(60_000) + .map(char::from) .collect::(); let srv = test::start_with(test::config().h1(), || { @@ -614,6 +616,7 @@ async fn test_reading_deflate_encoding_large_random() { let data = rand::thread_rng() .sample_iter(&Alphanumeric) .take(160_000) + .map(char::from) .collect::(); let srv = test::start_with(test::config().h1(), || { @@ -672,6 +675,7 @@ async fn test_brotli_encoding_large() { let data = rand::thread_rng() .sample_iter(&Alphanumeric) .take(320_000) + .map(char::from) .collect::(); let srv = test::start_with(test::config().h1(), || { @@ -753,6 +757,7 @@ async fn test_reading_deflate_encoding_large_random_rustls() { let data = rand::thread_rng() .sample_iter(&Alphanumeric) .take(160_000) + .map(char::from) .collect::(); // load ssl keys From 05f104c24012b0c518b6bace0bc551821c94724f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 23 Dec 2020 00:19:20 +0000 Subject: [PATCH 51/81] improve NormalizePath docs (#1839) --- src/middleware/normalize.rs | 82 +++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index ac8ad71d5..ad9f51079 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -1,10 +1,11 @@ -//! `Middleware` to normalize request's URI +//! For middleware documentation, see [`NormalizePath`]. + use std::task::{Context, Poll}; use actix_http::http::{PathAndQuery, Uri}; use actix_service::{Service, Transform}; use bytes::Bytes; -use futures_util::future::{ok, Ready}; +use futures_util::future::{ready, Ready}; use regex::Regex; use crate::service::{ServiceRequest, ServiceResponse}; @@ -17,10 +18,12 @@ pub enum TrailingSlash { /// Always add a trailing slash to the end of the path. /// This will require all routes to end in a trailing slash for them to be accessible. Always, + /// Only merge any present multiple trailing slashes. /// - /// Note: This option provides the best compatibility with the v2 version of this middlware. + /// Note: This option provides the best compatibility with the v2 version of this middleware. MergeOnly, + /// Trim trailing slashes from the end of the path. Trim, } @@ -32,28 +35,53 @@ impl Default for TrailingSlash { } #[derive(Default, Clone, Copy)] -/// `Middleware` to normalize request's URI in place +/// Middleware to normalize a request's path so that routes can be matched less strictly. /// -/// Performs following: -/// -/// - Merges multiple slashes into one. +/// # Normalization Steps +/// - Merges multiple consecutive slashes into one. (For example, `/path//one` always +/// becomes `/path/one`.) /// - Appends a trailing slash if one is not present, removes one if present, or keeps trailing -/// slashes as-is, depending on the supplied `TrailingSlash` variant. +/// slashes as-is, depending on which [`TrailingSlash`] variant is supplied +/// to [`new`](NormalizePath::new()). /// +/// # Default Behavior +/// The default constructor chooses to strip trailing slashes from the end +/// ([`TrailingSlash::Trim`]), the effect is that route definitions should be defined without +/// trailing slashes or else they will be inaccessible. +/// +/// # Example /// ```rust -/// use actix_web::{web, http, middleware, App, HttpResponse}; +/// use actix_web::{web, middleware, App}; /// -/// # fn main() { +/// # #[actix_rt::test] +/// # async fn normalize() { /// let app = App::new() /// .wrap(middleware::NormalizePath::default()) -/// .service( -/// web::resource("/test") -/// .route(web::get().to(|| HttpResponse::Ok())) -/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed())) -/// ); +/// .route("/test", web::get().to(|| async { "test" })) +/// .route("/unmatchable/", web::get().to(|| async { "unmatchable" })); +/// +/// use actix_web::http::StatusCode; +/// use actix_web::test::{call_service, init_service, TestRequest}; +/// +/// let mut app = init_service(app).await; +/// +/// let req = TestRequest::with_uri("/test").to_request(); +/// let res = call_service(&mut app, req).await; +/// assert_eq!(res.status(), StatusCode::OK); +/// +/// let req = TestRequest::with_uri("/test/").to_request(); +/// let res = call_service(&mut app, req).await; +/// assert_eq!(res.status(), StatusCode::OK); +/// +/// let req = TestRequest::with_uri("/unmatchable").to_request(); +/// let res = call_service(&mut app, req).await; +/// assert_eq!(res.status(), StatusCode::NOT_FOUND); +/// +/// let req = TestRequest::with_uri("/unmatchable/").to_request(); +/// let res = call_service(&mut app, req).await; +/// assert_eq!(res.status(), StatusCode::NOT_FOUND); /// # } /// ``` - pub struct NormalizePath(TrailingSlash); impl NormalizePath { @@ -76,11 +104,11 @@ where type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { - ok(NormalizePathNormalization { + ready(Ok(NormalizePathNormalization { service, merge_slash: Regex::new("//+").unwrap(), trailing_slash_behavior: self.0, - }) + })) } } @@ -160,9 +188,11 @@ mod tests { use actix_service::IntoService; use super::*; - use crate::dev::ServiceRequest; - use crate::test::{call_service, init_service, TestRequest}; - use crate::{web, App, HttpResponse}; + use crate::{ + dev::ServiceRequest, + test::{call_service, init_service, TestRequest}, + web, App, HttpResponse, + }; #[actix_rt::test] async fn test_wrap() { @@ -244,7 +274,7 @@ mod tests { } #[actix_rt::test] - async fn keep_trailing_slash_unchange() { + async fn keep_trailing_slash_unchanged() { let mut app = init_service( App::new() .wrap(NormalizePath(TrailingSlash::MergeOnly)) @@ -279,7 +309,7 @@ mod tests { async fn test_in_place_normalization() { let srv = |req: ServiceRequest| { assert_eq!("/v1/something/", req.path()); - ok(req.into_response(HttpResponse::Ok().finish())) + ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; let mut normalize = NormalizePath::default() @@ -310,7 +340,7 @@ mod tests { let srv = |req: ServiceRequest| { assert_eq!(URI, req.path()); - ok(req.into_response(HttpResponse::Ok().finish())) + ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; let mut normalize = NormalizePath::default() @@ -324,12 +354,12 @@ mod tests { } #[actix_rt::test] - async fn should_normalize_notrail() { + async fn should_normalize_no_trail() { const URI: &str = "/v1/something"; let srv = |req: ServiceRequest| { assert_eq!(URI.to_string() + "/", req.path()); - ok(req.into_response(HttpResponse::Ok().finish())) + ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; let mut normalize = NormalizePath::default() From 2a7f2c1d59df14f3ea503f53dc5441099032a8cf Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 23 Dec 2020 01:28:17 +0000 Subject: [PATCH 52/81] dispatcher internals testing (#1840) --- actix-http/src/cloneable.rs | 2 +- actix-http/src/extensions.rs | 2 +- actix-http/src/h1/codec.rs | 9 +- actix-http/src/h1/dispatcher.rs | 386 ++++++++++++++++++++++++++++++-- actix-http/src/h1/expect.rs | 10 +- actix-http/src/h1/upgrade.rs | 6 +- actix-http/src/test.rs | 148 ++++++++++-- actix-http/src/ws/mod.rs | 14 +- 8 files changed, 519 insertions(+), 58 deletions(-) diff --git a/actix-http/src/cloneable.rs b/actix-http/src/cloneable.rs index b64c299fc..0e77c455c 100644 --- a/actix-http/src/cloneable.rs +++ b/actix-http/src/cloneable.rs @@ -4,12 +4,12 @@ use std::task::{Context, Poll}; use actix_service::Service; -#[doc(hidden)] /// Service that allows to turn non-clone service to a service with `Clone` impl /// /// # Panics /// CloneableService might panic with some creative use of thread local storage. /// See https://github.com/actix/actix-web/issues/1295 for example +#[doc(hidden)] pub(crate) struct CloneableService(Rc>); impl CloneableService { diff --git a/actix-http/src/extensions.rs b/actix-http/src/extensions.rs index 7dda74731..b20dfe11d 100644 --- a/actix-http/src/extensions.rs +++ b/actix-http/src/extensions.rs @@ -3,8 +3,8 @@ use std::{fmt, mem}; use fxhash::FxHashMap; -#[derive(Default)] /// A type map of request extensions. +#[derive(Default)] pub struct Extensions { /// Use FxHasher with a std HashMap with for faster /// lookups on the small `TypeId` (u64 equivalent) keys. diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 036f16670..c9a62dc30 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -58,6 +58,7 @@ impl Codec { } else { Flags::empty() }; + Codec { config, flags, @@ -69,26 +70,26 @@ impl Codec { } } + /// Check if request is upgrade. #[inline] - /// Check if request is upgrade pub fn upgrade(&self) -> bool { self.ctype == ConnectionType::Upgrade } + /// Check if last response is keep-alive. #[inline] - /// Check if last response is keep-alive pub fn keepalive(&self) -> bool { self.ctype == ConnectionType::KeepAlive } + /// Check if keep-alive enabled on server level. #[inline] - /// Check if keep-alive enabled on server level pub fn keepalive_enabled(&self) -> bool { self.flags.contains(Flags::KEEPALIVE_ENABLED) } + /// Check last request's message type. #[inline] - /// Check last request's message type pub fn message_type(&self) -> MessageType { if self.flags.contains(Flags::STREAM) { MessageType::Stream diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index ace4144e3..1311a0987 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1,8 +1,11 @@ -use std::collections::VecDeque; -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{fmt, io, net}; +use std::{ + collections::VecDeque, + fmt, + future::Future, + io, mem, net, + pin::Pin, + task::{Context, Poll}, +}; use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts}; use actix_rt::time::{delay_until, Delay, Instant}; @@ -59,6 +62,9 @@ where { #[pin] inner: DispatcherState, + + #[cfg(test)] + poll_count: u64, } #[pin_project(project = DispatcherStateProj)] @@ -247,6 +253,9 @@ where ka_expire, ka_timer, }), + + #[cfg(test)] + poll_count: 0, } } } @@ -511,12 +520,12 @@ where } } - /// Process one incoming requests + /// Process one incoming request. pub(self) fn poll_request( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Result { - // limit a mount of non processed requests + // limit amount of non-processed requests if self.messages.len() >= MAX_PIPELINED_MESSAGES || !self.can_read(cx) { return Ok(false); } @@ -725,6 +734,12 @@ where #[inline] fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.as_mut().project(); + + #[cfg(test)] + { + *this.poll_count += 1; + } + match this.inner.project() { DispatcherStateProj::Normal(mut inner) => { inner.as_mut().poll_keepalive(cx)?; @@ -788,10 +803,10 @@ where let inner_p = inner.as_mut().project(); let mut parts = FramedParts::with_read_buf( inner_p.io.take().unwrap(), - std::mem::take(inner_p.codec), - std::mem::take(inner_p.read_buf), + mem::take(inner_p.codec), + mem::take(inner_p.read_buf), ); - parts.write_buf = std::mem::take(inner_p.write_buf); + parts.write_buf = mem::take(inner_p.write_buf); let framed = Framed::from_parts(parts); let upgrade = inner_p.upgrade.take().unwrap().call((req, framed)); @@ -803,8 +818,11 @@ where } // we didn't get WouldBlock from write operation, - // so data get written to kernel completely (OSX) + // so data get written to kernel completely (macOS) // and we have to write again otherwise response can get stuck + // + // TODO: what? is WouldBlock good or bad? + // want to find a reference for this macOS behavior if inner.as_mut().poll_flush(cx)? || !drain { break; } @@ -854,6 +872,11 @@ where } } +/// Returns either: +/// - `Ok(Some(true))` - data was read and done reading all data. +/// - `Ok(Some(false))` - data was read but there should be more to read. +/// - `Ok(None)` - no data was read but there should be more to read later. +/// - Unhandled Errors fn read_available( cx: &mut Context<'_>, io: &mut T, @@ -887,17 +910,17 @@ where read_some = true; } } - Poll::Ready(Err(e)) => { - return if e.kind() == io::ErrorKind::WouldBlock { + Poll::Ready(Err(err)) => { + return if err.kind() == io::ErrorKind::WouldBlock { if read_some { Ok(Some(false)) } else { Ok(None) } - } else if e.kind() == io::ErrorKind::ConnectionReset && read_some { + } else if err.kind() == io::ErrorKind::ConnectionReset && read_some { Ok(Some(true)) } else { - Err(e) + Err(err) } } } @@ -917,13 +940,64 @@ where #[cfg(test)] mod tests { - use actix_service::IntoService; - use futures_util::future::{lazy, ok}; + use std::{marker::PhantomData, str}; + + use actix_service::fn_service; + use futures_util::future::{lazy, ready}; use super::*; - use crate::error::Error; - use crate::h1::{ExpectHandler, UpgradeHandler}; use crate::test::TestBuffer; + use crate::{error::Error, KeepAlive}; + use crate::{ + h1::{ExpectHandler, UpgradeHandler}, + test::TestSeqBuffer, + }; + + fn find_slice(haystack: &[u8], needle: &[u8], from: usize) -> Option { + haystack[from..] + .windows(needle.len()) + .position(|window| window == needle) + } + + fn stabilize_date_header(payload: &mut [u8]) { + let mut from = 0; + + while let Some(pos) = find_slice(&payload, b"date", from) { + payload[(from + pos)..(from + pos + 35)] + .copy_from_slice(b"date: Thu, 01 Jan 1970 12:34:56 UTC"); + from += 35; + } + } + + fn ok_service() -> impl Service + { + fn_service(|_req: Request| ready(Ok::<_, Error>(Response::Ok().finish()))) + } + + fn echo_path_service( + ) -> impl Service { + fn_service(|req: Request| { + let path = req.path().as_bytes(); + ready(Ok::<_, Error>(Response::Ok().body(Body::from_slice(path)))) + }) + } + + fn echo_payload_service( + ) -> impl Service { + fn_service(|mut req: Request| { + Box::pin(async move { + use futures_util::stream::StreamExt as _; + + let mut pl = req.take_payload(); + let mut body = BytesMut::new(); + while let Some(chunk) = pl.next().await { + body.extend_from_slice(chunk.unwrap().bytes()) + } + + Ok::<_, Error>(Response::Ok().body(body)) + }) + }) + } #[actix_rt::test] async fn test_req_parse_err() { @@ -933,9 +1007,7 @@ mod tests { let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, ServiceConfig::default(), - CloneableService::new( - (|_| ok::<_, Error>(Response::Ok().finish())).into_service(), - ), + CloneableService::new(ok_service()), CloneableService::new(ExpectHandler), None, None, @@ -958,4 +1030,274 @@ mod tests { }) .await; } + + #[actix_rt::test] + async fn test_pipelining() { + lazy(|cx| { + let buf = TestBuffer::new( + "\ + GET /abcd HTTP/1.1\r\n\r\n\ + GET /def HTTP/1.1\r\n\r\n\ + ", + ); + + let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); + + let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf, + cfg, + CloneableService::new(echo_path_service()), + CloneableService::new(ExpectHandler), + None, + None, + Extensions::new(), + None, + ); + + assert!(matches!(&h1.inner, DispatcherState::Normal(_))); + + match Pin::new(&mut h1).poll(cx) { + Poll::Pending => panic!("first poll should not be pending"), + Poll::Ready(res) => assert!(res.is_ok()), + } + + // polls: initial => shutdown + assert_eq!(h1.poll_count, 2); + + if let DispatcherState::Normal(ref mut inner) = h1.inner { + let res = &mut inner.io.take().unwrap().write_buf[..]; + stabilize_date_header(res); + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /abcd\ + HTTP/1.1 200 OK\r\n\ + content-length: 4\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /def\ + "; + + assert_eq!(res.to_vec(), exp.to_vec()); + } + }) + .await; + + lazy(|cx| { + let buf = TestBuffer::new( + "\ + GET /abcd HTTP/1.1\r\n\r\n\ + GET /def HTTP/1\r\n\r\n\ + ", + ); + + let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); + + let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf, + cfg, + CloneableService::new(echo_path_service()), + CloneableService::new(ExpectHandler), + None, + None, + Extensions::new(), + None, + ); + + assert!(matches!(&h1.inner, DispatcherState::Normal(_))); + + match Pin::new(&mut h1).poll(cx) { + Poll::Pending => panic!("first poll should not be pending"), + Poll::Ready(res) => assert!(res.is_err()), + } + + // polls: initial => shutdown + assert_eq!(h1.poll_count, 1); + + if let DispatcherState::Normal(ref mut inner) = h1.inner { + let res = &mut inner.io.take().unwrap().write_buf[..]; + stabilize_date_header(res); + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /abcd\ + HTTP/1.1 400 Bad Request\r\n\ + content-length: 0\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + "; + + assert_eq!(res.to_vec(), exp.to_vec()); + } + }) + .await; + } + + #[actix_rt::test] + async fn test_expect() { + lazy(|cx| { + let mut buf = TestSeqBuffer::empty(); + let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + buf.clone(), + cfg, + CloneableService::new(echo_payload_service()), + CloneableService::new(ExpectHandler), + None, + None, + Extensions::new(), + None, + ); + + buf.extend_read_buf( + "\ + POST /upload HTTP/1.1\r\n\ + Content-Length: 5\r\n\ + Expect: 100-continue\r\n\ + \r\n\ + ", + ); + + assert!(Pin::new(&mut h1).poll(cx).is_pending()); + assert!(matches!(&h1.inner, DispatcherState::Normal(_))); + + // polls: manual + assert_eq!(h1.poll_count, 1); + eprintln!("poll count: {}", h1.poll_count); + + if let DispatcherState::Normal(ref inner) = h1.inner { + let io = inner.io.as_ref().unwrap(); + let res = &io.write_buf()[..]; + assert_eq!( + str::from_utf8(res).unwrap(), + "HTTP/1.1 100 Continue\r\n\r\n" + ); + } + + buf.extend_read_buf("12345"); + assert!(Pin::new(&mut h1).poll(cx).is_ready()); + + // polls: manual manual shutdown + assert_eq!(h1.poll_count, 3); + + if let DispatcherState::Normal(ref inner) = h1.inner { + let io = inner.io.as_ref().unwrap(); + let mut res = (&io.write_buf()[..]).to_owned(); + stabilize_date_header(&mut res); + + assert_eq!( + str::from_utf8(&res).unwrap(), + "\ + HTTP/1.1 100 Continue\r\n\ + \r\n\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\ + \r\n\ + 12345\ + " + ); + } + }) + .await; + } + + #[actix_rt::test] + async fn test_eager_expect() { + lazy(|cx| { + let mut buf = TestSeqBuffer::empty(); + let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + buf.clone(), + cfg, + CloneableService::new(echo_path_service()), + CloneableService::new(ExpectHandler), + None, + None, + Extensions::new(), + None, + ); + + buf.extend_read_buf( + "\ + POST /upload HTTP/1.1\r\n\ + Content-Length: 5\r\n\ + Expect: 100-continue\r\n\ + \r\n\ + ", + ); + + assert!(Pin::new(&mut h1).poll(cx).is_ready()); + assert!(matches!(&h1.inner, DispatcherState::Normal(_))); + + // polls: manual shutdown + assert_eq!(h1.poll_count, 2); + + if let DispatcherState::Normal(ref inner) = h1.inner { + let io = inner.io.as_ref().unwrap(); + let mut res = (&io.write_buf()[..]).to_owned(); + stabilize_date_header(&mut res); + + // Despite the content-length header and even though the request payload has not + // been sent, this test expects a complete service response since the payload + // is not used at all. The service passed to dispatcher is path echo and doesn't + // consume payload bytes. + assert_eq!( + str::from_utf8(&res).unwrap(), + "\ + HTTP/1.1 100 Continue\r\n\ + \r\n\ + HTTP/1.1 200 OK\r\n\ + content-length: 7\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\ + \r\n\ + /upload\ + " + ); + } + }) + .await; + } + + #[actix_rt::test] + async fn test_upgrade() { + lazy(|cx| { + let mut buf = TestSeqBuffer::empty(); + let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + buf.clone(), + cfg, + CloneableService::new(ok_service()), + CloneableService::new(ExpectHandler), + Some(CloneableService::new(UpgradeHandler(PhantomData))), + None, + Extensions::new(), + None, + ); + + buf.extend_read_buf( + "\ + GET /ws HTTP/1.1\r\n\ + Connection: Upgrade\r\n\ + Upgrade: websocket\r\n\ + \r\n\ + ", + ); + + assert!(Pin::new(&mut h1).poll(cx).is_ready()); + assert!(matches!(&h1.inner, DispatcherState::Upgrade(_))); + + // polls: manual shutdown + assert_eq!(h1.poll_count, 2); + }) + .await; + } } diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index 6c08df08e..b89c7ff74 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -1,7 +1,7 @@ use std::task::{Context, Poll}; use actix_service::{Service, ServiceFactory}; -use futures_util::future::{ok, Ready}; +use futures_util::future::{ready, Ready}; use crate::error::Error; use crate::request::Request; @@ -17,8 +17,8 @@ impl ServiceFactory for ExpectHandler { type InitError = Error; type Future = Ready>; - fn new_service(&self, _: ()) -> Self::Future { - ok(ExpectHandler) + fn new_service(&self, _: Self::Config) -> Self::Future { + ready(Ok(ExpectHandler)) } } @@ -33,6 +33,8 @@ impl Service for ExpectHandler { } fn call(&mut self, req: Request) -> Self::Future { - ok(req) + ready(Ok(req)) + // TODO: add some way to trigger error + // Err(error::ErrorExpectationFailed("test")) } } diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs index 22ba99e26..8615f27a8 100644 --- a/actix-http/src/h1/upgrade.rs +++ b/actix-http/src/h1/upgrade.rs @@ -3,13 +3,13 @@ use std::task::{Context, Poll}; use actix_codec::Framed; use actix_service::{Service, ServiceFactory}; -use futures_util::future::Ready; +use futures_util::future::{ready, Ready}; use crate::error::Error; use crate::h1::Codec; use crate::request::Request; -pub struct UpgradeHandler(PhantomData); +pub struct UpgradeHandler(pub(crate) PhantomData); impl ServiceFactory for UpgradeHandler { type Config = (); @@ -36,6 +36,6 @@ impl Service for UpgradeHandler { } fn call(&mut self, _: Self::Request) -> Self::Future { - unimplemented!() + ready(Ok(())) } } diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index b79f5a73c..4512e72c2 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -1,9 +1,14 @@ -//! Test Various helpers for Actix applications to use during testing. -use std::convert::TryFrom; -use std::io::{self, Read, Write}; -use std::pin::Pin; -use std::str::FromStr; -use std::task::{Context, Poll}; +//! Various testing helpers for use in internal and app tests. + +use std::{ + cell::{Ref, RefCell}, + convert::TryFrom, + io::{self, Read, Write}, + pin::Pin, + rc::Rc, + str::FromStr, + task::{Context, Poll}, +}; use actix_codec::{AsyncRead, AsyncWrite}; use bytes::{Bytes, BytesMut}; @@ -183,7 +188,7 @@ fn parts(parts: &mut Option) -> &mut Inner { parts.as_mut().expect("cannot reuse test request builder") } -/// Async io buffer +/// Async I/O test buffer. pub struct TestBuffer { pub read_buf: BytesMut, pub write_buf: BytesMut, @@ -191,24 +196,24 @@ pub struct TestBuffer { } impl TestBuffer { - /// Create new TestBuffer instance - pub fn new(data: T) -> TestBuffer + /// Create new `TestBuffer` instance with initial read buffer. + pub fn new(data: T) -> Self where - BytesMut: From, + T: Into, { - TestBuffer { - read_buf: BytesMut::from(data), + Self { + read_buf: data.into(), write_buf: BytesMut::new(), err: None, } } - /// Create new empty TestBuffer instance - pub fn empty() -> TestBuffer { - TestBuffer::new("") + /// Create new empty `TestBuffer` instance. + pub fn empty() -> Self { + Self::new("") } - /// Add extra data to read buffer. + /// Add data to read buffer. pub fn extend_read_buf>(&mut self, data: T) { self.read_buf.extend_from_slice(data.as_ref()) } @@ -236,6 +241,7 @@ impl io::Write for TestBuffer { self.write_buf.extend(buf); Ok(buf.len()) } + fn flush(&mut self) -> io::Result<()> { Ok(()) } @@ -268,3 +274,113 @@ impl AsyncWrite for TestBuffer { Poll::Ready(Ok(())) } } + +/// Async I/O test buffer with ability to incrementally add to the read buffer. +#[derive(Clone)] +pub struct TestSeqBuffer(Rc>); + +impl TestSeqBuffer { + /// Create new `TestBuffer` instance with initial read buffer. + pub fn new(data: T) -> Self + where + T: Into, + { + Self(Rc::new(RefCell::new(TestSeqInner { + read_buf: data.into(), + write_buf: BytesMut::new(), + err: None, + }))) + } + + /// Create new empty `TestBuffer` instance. + pub fn empty() -> Self { + Self::new("") + } + + pub fn read_buf(&self) -> Ref<'_, BytesMut> { + Ref::map(self.0.borrow(), |inner| &inner.read_buf) + } + + pub fn write_buf(&self) -> Ref<'_, BytesMut> { + Ref::map(self.0.borrow(), |inner| &inner.write_buf) + } + + pub fn err(&self) -> Ref<'_, Option> { + Ref::map(self.0.borrow(), |inner| &inner.err) + } + + /// Add data to read buffer. + pub fn extend_read_buf>(&mut self, data: T) { + self.0 + .borrow_mut() + .read_buf + .extend_from_slice(data.as_ref()) + } +} + +pub struct TestSeqInner { + read_buf: BytesMut, + write_buf: BytesMut, + err: Option, +} + +impl io::Read for TestSeqBuffer { + fn read(&mut self, dst: &mut [u8]) -> Result { + if self.0.borrow().read_buf.is_empty() { + if self.0.borrow().err.is_some() { + Err(self.0.borrow_mut().err.take().unwrap()) + } else { + Err(io::Error::new(io::ErrorKind::WouldBlock, "")) + } + } else { + let size = std::cmp::min(self.0.borrow().read_buf.len(), dst.len()); + let b = self.0.borrow_mut().read_buf.split_to(size); + dst[..size].copy_from_slice(&b); + Ok(size) + } + } +} + +impl io::Write for TestSeqBuffer { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.borrow_mut().write_buf.extend(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl AsyncRead for TestSeqBuffer { + fn poll_read( + self: Pin<&mut Self>, + _: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let r = self.get_mut().read(buf); + match r { + Ok(n) => Poll::Ready(Ok(n)), + Err(err) if err.kind() == io::ErrorKind::WouldBlock => Poll::Pending, + Err(err) => Poll::Ready(Err(err)), + } + } +} + +impl AsyncWrite for TestSeqBuffer { + fn poll_write( + self: Pin<&mut Self>, + _: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Poll::Ready(self.get_mut().write(buf)) + } + + fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } +} diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 6ffdecc35..cd212fb7e 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -197,13 +197,13 @@ mod tests { let req = TestRequest::default().method(Method::POST).finish(); assert_eq!( HandshakeError::GetMethodRequired, - verify_handshake(req.head()).err().unwrap() + verify_handshake(req.head()).unwrap_err(), ); let req = TestRequest::default().finish(); assert_eq!( HandshakeError::NoWebsocketUpgrade, - verify_handshake(req.head()).err().unwrap() + verify_handshake(req.head()).unwrap_err(), ); let req = TestRequest::default() @@ -211,7 +211,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::NoWebsocketUpgrade, - verify_handshake(req.head()).err().unwrap() + verify_handshake(req.head()).unwrap_err(), ); let req = TestRequest::default() @@ -222,7 +222,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::NoConnectionUpgrade, - verify_handshake(req.head()).err().unwrap() + verify_handshake(req.head()).unwrap_err(), ); let req = TestRequest::default() @@ -237,7 +237,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::NoVersionHeader, - verify_handshake(req.head()).err().unwrap() + verify_handshake(req.head()).unwrap_err(), ); let req = TestRequest::default() @@ -256,7 +256,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::UnsupportedVersion, - verify_handshake(req.head()).err().unwrap() + verify_handshake(req.head()).unwrap_err(), ); let req = TestRequest::default() @@ -275,7 +275,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::BadWebsocketKey, - verify_handshake(req.head()).err().unwrap() + verify_handshake(req.head()).unwrap_err(), ); let req = TestRequest::default() From 3a192400a68135c0c1e5a069145e91f91cba3aa2 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 23 Dec 2020 23:47:07 +0800 Subject: [PATCH 53/81] Simplify handler (#1843) --- src/handler.rs | 184 ++++++++++++++++--------------------------------- src/route.rs | 9 ++- 2 files changed, 63 insertions(+), 130 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 0dc06b3ce..3d0a2382e 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,4 +1,3 @@ -use std::convert::Infallible; use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; @@ -6,7 +5,7 @@ use std::task::{Context, Poll}; use actix_http::{Error, Response}; use actix_service::{Service, ServiceFactory}; -use futures_util::future::{ok, Ready}; +use futures_util::future::{ready, Ready}; use futures_util::ready; use pin_project::pin_project; @@ -36,9 +35,11 @@ where } #[doc(hidden)] +/// Extract arguments from request, run factory function and make response. pub struct Handler where F: Factory, + T: FromRequest, R: Future, O: Responder, { @@ -49,6 +50,7 @@ where impl Handler where F: Factory, + T: FromRequest, R: Future, O: Responder, { @@ -63,6 +65,7 @@ where impl Clone for Handler where F: Factory, + T: FromRequest, R: Future, O: Responder, { @@ -74,172 +77,103 @@ where } } -impl Service for Handler +impl ServiceFactory for Handler where F: Factory, + T: FromRequest, R: Future, O: Responder, -{ - type Request = (T, HttpRequest); - type Response = ServiceResponse; - type Error = Infallible; - type Future = HandlerServiceResponse; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { - let fut = self.hnd.call(param); - HandlerServiceResponse::Future(fut, Some(req)) - } -} - -#[doc(hidden)] -#[pin_project(project = HandlerProj)] -pub enum HandlerServiceResponse -where - T: Future, - R: Responder, -{ - Future(#[pin] T, Option), - Responder(#[pin] R::Future, Option), -} - -impl Future for HandlerServiceResponse -where - T: Future, - R: Responder, -{ - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - loop { - match self.as_mut().project() { - HandlerProj::Future(fut, req) => { - let res = ready!(fut.poll(cx)); - let fut = res.respond_to(req.as_ref().unwrap()); - let state = HandlerServiceResponse::Responder(fut, req.take()); - self.as_mut().set(state); - } - HandlerProj::Responder(fut, req) => { - let res = ready!(fut.poll(cx)); - let req = req.take().unwrap(); - return match res { - Ok(res) => Poll::Ready(Ok(ServiceResponse::new(req, res))), - Err(e) => { - let res: Response = e.into().into(); - Poll::Ready(Ok(ServiceResponse::new(req, res))) - } - }; - } - } - } - } -} - -/// Extract arguments from request -pub struct Extract { - service: S, - _t: PhantomData, -} - -impl Extract { - pub fn new(service: S) -> Self { - Extract { - service, - _t: PhantomData, - } - } -} - -impl ServiceFactory for Extract -where - S: Service< - Request = (T, HttpRequest), - Response = ServiceResponse, - Error = Infallible, - > + Clone, { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Config = (); - type Service = ExtractService; + type Service = Self; type InitError = (); type Future = Ready>; fn new_service(&self, _: ()) -> Self::Future { - ok(ExtractService { - _t: PhantomData, - service: self.service.clone(), - }) + ready(Ok(self.clone())) } } -pub struct ExtractService { - service: S, - _t: PhantomData, -} - -impl Service for ExtractService +// Handler is both it's ServiceFactory and Service Type. +impl Service for Handler where - S: Service< - Request = (T, HttpRequest), - Response = ServiceResponse, - Error = Infallible, - > + Clone, + F: Factory, + T: FromRequest, + R: Future, + O: Responder, { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = ExtractResponse; + type Future = HandlerServiceFuture; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&mut self, req: Self::Request) -> Self::Future { let (req, mut payload) = req.into_parts(); let fut = T::from_request(&req, &mut payload); - ExtractResponse::Future(fut, Some(req), self.service.clone()) + HandlerServiceFuture::Extract(fut, Some(req), self.hnd.clone()) } } -#[pin_project(project = ExtractProj)] -pub enum ExtractResponse { - Future(#[pin] T::Future, Option, S), - Response(#[pin] S::Future), +#[doc(hidden)] +#[pin_project(project = HandlerProj)] +pub enum HandlerServiceFuture +where + F: Factory, + T: FromRequest, + R: Future, + O: Responder, +{ + Extract(#[pin] T::Future, Option, F), + Handle(#[pin] R, Option), + Respond(#[pin] O::Future, Option), } -impl Future for ExtractResponse +impl Future for HandlerServiceFuture where - S: Service< - Request = (T, HttpRequest), - Response = ServiceResponse, - Error = Infallible, - >, + F: Factory, + T: FromRequest, + R: Future, + O: Responder, { + // Error type in this future is a placeholder type. + // all instances of error must be converted to ServiceResponse and return in Ok. type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { loop { match self.as_mut().project() { - ExtractProj::Future(fut, req, srv) => { - let res = ready!(fut.poll(cx)); - let req = req.take().unwrap(); - match res { - Err(e) => { - let req = ServiceRequest::new(req); - return Poll::Ready(Ok(req.error_response(e.into()))); - } + HandlerProj::Extract(fut, req, handle) => { + match ready!(fut.poll(cx)) { Ok(item) => { - let fut = srv.call((item, req)); - self.as_mut().set(ExtractResponse::Response(fut)); + let fut = handle.call(item); + let state = HandlerServiceFuture::Handle(fut, req.take()); + self.as_mut().set(state); } - } + Err(e) => { + let res: Response = e.into().into(); + let req = req.take().unwrap(); + return Poll::Ready(Ok(ServiceResponse::new(req, res))); + } + }; + } + HandlerProj::Handle(fut, req) => { + let res = ready!(fut.poll(cx)); + let fut = res.respond_to(req.as_ref().unwrap()); + let state = HandlerServiceFuture::Respond(fut, req.take()); + self.as_mut().set(state); + } + HandlerProj::Respond(fut, req) => { + let res = ready!(fut.poll(cx)).unwrap_or_else(|e| e.into().into()); + let req = req.take().unwrap(); + return Poll::Ready(Ok(ServiceResponse::new(req, res))); } - ExtractProj::Response(fut) => return fut.poll(cx).map_err(|_| panic!()), } } } diff --git a/src/route.rs b/src/route.rs index f8ef458f9..439ae6c4a 100644 --- a/src/route.rs +++ b/src/route.rs @@ -11,7 +11,7 @@ use futures_util::future::{ready, FutureExt, LocalBoxFuture}; use crate::extract::FromRequest; use crate::guard::{self, Guard}; -use crate::handler::{Extract, Factory, Handler}; +use crate::handler::{Factory, Handler}; use crate::responder::Responder; use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; @@ -51,9 +51,9 @@ impl Route { #[allow(clippy::new_without_default)] pub fn new() -> Route { Route { - service: Box::new(RouteNewService::new(Extract::new(Handler::new(|| { + service: Box::new(RouteNewService::new(Handler::new(|| { ready(HttpResponse::NotFound()) - })))), + }))), guards: Rc::new(Vec::new()), } } @@ -226,8 +226,7 @@ impl Route { R: Future + 'static, U: Responder + 'static, { - self.service = - Box::new(RouteNewService::new(Extract::new(Handler::new(handler)))); + self.service = Box::new(RouteNewService::new(Handler::new(handler))); self } } From 87655b3028eab8f077089725e476b6141e45b469 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 24 Dec 2020 07:58:25 +0800 Subject: [PATCH 54/81] reduce one clone on Arc. (#1850) --- src/types/json.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/json.rs b/src/types/json.rs index 95613a0ce..dc0870a6e 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -183,7 +183,7 @@ where let config = JsonConfig::from_req(req); let limit = config.limit; - let ctype = config.content_type.clone(); + let ctype = config.content_type.as_deref(); let err_handler = config.err_handler.clone(); JsonExtractFut { @@ -361,7 +361,7 @@ where pub fn new( req: &HttpRequest, payload: &mut Payload, - ctype: Option bool + Send + Sync>>, + ctype: Option<&(dyn Fn(mime::Mime) -> bool + Send + Sync)>, ) -> Self { // check content-type let json = if let Ok(Some(mime)) = req.mime_type() { From ecf08d5156a27671b9d36c866f3b64acff83f319 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 25 Dec 2020 03:15:17 +0800 Subject: [PATCH 55/81] Remove boxed future from h1 Dispatcher (#1836) --- actix-http/src/h1/dispatcher.rs | 196 +++++++++++++++++++------------- 1 file changed, 118 insertions(+), 78 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 1311a0987..ea8f91e0d 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -130,8 +130,8 @@ where B: MessageBody, { None, - ExpectCall(Pin>), - ServiceCall(Pin>), + ExpectCall(#[pin] X::Future), + ServiceCall(#[pin] S::Future), SendPayload(#[pin] ResponseBody), } @@ -347,7 +347,7 @@ where self: Pin<&mut Self>, message: Response<()>, body: ResponseBody, - ) -> Result, DispatchError> { + ) -> Result<(), DispatchError> { let mut this = self.project(); this.codec .encode(Message::Item((message, body.size())), &mut this.write_buf) @@ -360,9 +360,10 @@ where this.flags.set(Flags::KEEPALIVE, this.codec.keepalive()); match body.size() { - BodySize::None | BodySize::Empty => Ok(State::None), - _ => Ok(State::SendPayload(body)), - } + BodySize::None | BodySize::Empty => this.state.set(State::None), + _ => this.state.set(State::SendPayload(body)), + }; + Ok(()) } fn send_continue(self: Pin<&mut Self>) { @@ -377,49 +378,52 @@ where ) -> Result { loop { let mut this = self.as_mut().project(); - let state = match this.state.project() { + // state is not changed on Poll::Pending. + // other variant and conditions always trigger a state change(or an error). + let state_change = match this.state.project() { StateProj::None => match this.messages.pop_front() { Some(DispatcherMessage::Item(req)) => { - Some(self.as_mut().handle_request(req, cx)?) + self.as_mut().handle_request(req, cx)?; + true } - Some(DispatcherMessage::Error(res)) => Some( + Some(DispatcherMessage::Error(res)) => { self.as_mut() - .send_response(res, ResponseBody::Other(Body::Empty))?, - ), + .send_response(res, ResponseBody::Other(Body::Empty))?; + true + } Some(DispatcherMessage::Upgrade(req)) => { return Ok(PollResponse::Upgrade(req)); } - None => None, + None => false, }, - StateProj::ExpectCall(fut) => match fut.as_mut().poll(cx) { + StateProj::ExpectCall(fut) => match fut.poll(cx) { Poll::Ready(Ok(req)) => { self.as_mut().send_continue(); this = self.as_mut().project(); - this.state - .set(State::ServiceCall(Box::pin(this.service.call(req)))); + this.state.set(State::ServiceCall(this.service.call(req))); continue; } Poll::Ready(Err(e)) => { let res: Response = e.into().into(); let (res, body) = res.replace_body(()); - Some(self.as_mut().send_response(res, body.into_body())?) + self.as_mut().send_response(res, body.into_body())?; + true } - Poll::Pending => None, + Poll::Pending => false, }, - StateProj::ServiceCall(fut) => match fut.as_mut().poll(cx) { + StateProj::ServiceCall(fut) => match fut.poll(cx) { Poll::Ready(Ok(res)) => { let (res, body) = res.into().replace_body(()); - let state = self.as_mut().send_response(res, body)?; - this = self.as_mut().project(); - this.state.set(state); + self.as_mut().send_response(res, body)?; continue; } Poll::Ready(Err(e)) => { let res: Response = e.into().into(); let (res, body) = res.replace_body(()); - Some(self.as_mut().send_response(res, body.into_body())?) + self.as_mut().send_response(res, body.into_body())?; + true } - Poll::Pending => None, + Poll::Pending => false, }, StateProj::SendPayload(mut stream) => { loop { @@ -454,11 +458,8 @@ where } }; - this = self.as_mut().project(); - - // set new state - if let Some(state) = state { - this.state.set(state); + // state is changed and continue when the state is not Empty + if state_change { if !self.state.is_empty() { continue; } @@ -483,39 +484,67 @@ where mut self: Pin<&mut Self>, req: Request, cx: &mut Context<'_>, - ) -> Result, DispatchError> { + ) -> Result<(), DispatchError> { // Handle `EXPECT: 100-Continue` header - let req = if req.head().expect() { - let mut task = Box::pin(self.as_mut().project().expect.call(req)); - match task.as_mut().poll(cx) { - Poll::Ready(Ok(req)) => { - self.as_mut().send_continue(); - req - } - Poll::Pending => return Ok(State::ExpectCall(task)), - Poll::Ready(Err(e)) => { - let e = e.into(); - let res: Response = e.into(); - let (res, body) = res.replace_body(()); - return self.send_response(res, body.into_body()); - } - } + if req.head().expect() { + // set dispatcher state so the future is pinned. + let task = self.as_mut().project().expect.call(req); + self.as_mut().project().state.set(State::ExpectCall(task)); } else { - req + // the same as above. + let task = self.as_mut().project().service.call(req); + self.as_mut().project().state.set(State::ServiceCall(task)); }; - // Call service - let mut task = Box::pin(self.as_mut().project().service.call(req)); - match task.as_mut().poll(cx) { - Poll::Ready(Ok(res)) => { - let (res, body) = res.into().replace_body(()); - self.send_response(res, body) - } - Poll::Pending => Ok(State::ServiceCall(task)), - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - let (res, body) = res.replace_body(()); - self.send_response(res, body.into_body()) + // eagerly poll the future for once(or twice if expect is resolved immediately). + loop { + match self.as_mut().project().state.project() { + StateProj::ExpectCall(fut) => { + match fut.poll(cx) { + // expect is resolved. continue loop and poll the service call branch. + Poll::Ready(Ok(req)) => { + self.as_mut().send_continue(); + let task = self.as_mut().project().service.call(req); + self.as_mut().project().state.set(State::ServiceCall(task)); + continue; + } + // future is pending. return Ok(()) to notify that a new state is + // set and the outer loop should be continue. + Poll::Pending => return Ok(()), + // future is error. send response and return a result. On success + // to notify the dispatcher a new state is set and the outer loop + // should be continue. + Poll::Ready(Err(e)) => { + let e = e.into(); + let res: Response = e.into(); + let (res, body) = res.replace_body(()); + return self.send_response(res, body.into_body()); + } + } + } + StateProj::ServiceCall(fut) => { + // return no matter the service call future's result. + return match fut.poll(cx) { + // future is resolved. send response and return a result. On success + // to notify the dispatcher a new state is set and the outer loop + // should be continue. + Poll::Ready(Ok(res)) => { + let (res, body) = res.into().replace_body(()); + self.send_response(res, body) + } + // see the comment on ExpectCall state branch's Pending. + Poll::Pending => Ok(()), + // see the comment on ExpectCall state branch's Ready(Err(e)). + Poll::Ready(Err(e)) => { + let res: Response = e.into().into(); + let (res, body) = res.replace_body(()); + self.send_response(res, body.into_body()) + } + }; + } + _ => unreachable!( + "State must be set to ServiceCall or ExceptCall in handle_request" + ), } } } @@ -566,9 +595,8 @@ where // handle request early if this.state.is_empty() { - let state = self.as_mut().handle_request(req, cx)?; + self.as_mut().handle_request(req, cx)?; this = self.as_mut().project(); - this.state.set(state); } else { this.messages.push_back(DispatcherMessage::Item(req)); } @@ -1004,7 +1032,7 @@ mod tests { lazy(|cx| { let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); - let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, ServiceConfig::default(), CloneableService::new(ok_service()), @@ -1015,15 +1043,17 @@ mod tests { None, ); - match Pin::new(&mut h1).poll(cx) { + futures_util::pin_mut!(h1); + + match h1.as_mut().poll(cx) { Poll::Pending => panic!(), Poll::Ready(res) => assert!(res.is_err()), } - if let DispatcherState::Normal(ref mut inner) = h1.inner { + if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() { assert!(inner.flags.contains(Flags::READ_DISCONNECT)); assert_eq!( - &inner.io.take().unwrap().write_buf[..26], + &inner.project().io.take().unwrap().write_buf[..26], b"HTTP/1.1 400 Bad Request\r\n" ); } @@ -1043,7 +1073,7 @@ mod tests { let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); - let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, cfg, CloneableService::new(echo_path_service()), @@ -1054,9 +1084,11 @@ mod tests { None, ); + futures_util::pin_mut!(h1); + assert!(matches!(&h1.inner, DispatcherState::Normal(_))); - match Pin::new(&mut h1).poll(cx) { + match h1.as_mut().poll(cx) { Poll::Pending => panic!("first poll should not be pending"), Poll::Ready(res) => assert!(res.is_ok()), } @@ -1064,8 +1096,8 @@ mod tests { // polls: initial => shutdown assert_eq!(h1.poll_count, 2); - if let DispatcherState::Normal(ref mut inner) = h1.inner { - let res = &mut inner.io.take().unwrap().write_buf[..]; + if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() { + let res = &mut inner.project().io.take().unwrap().write_buf[..]; stabilize_date_header(res); let exp = b"\ @@ -1096,7 +1128,7 @@ mod tests { let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); - let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, cfg, CloneableService::new(echo_path_service()), @@ -1107,9 +1139,11 @@ mod tests { None, ); + futures_util::pin_mut!(h1); + assert!(matches!(&h1.inner, DispatcherState::Normal(_))); - match Pin::new(&mut h1).poll(cx) { + match h1.as_mut().poll(cx) { Poll::Pending => panic!("first poll should not be pending"), Poll::Ready(res) => assert!(res.is_err()), } @@ -1117,8 +1151,8 @@ mod tests { // polls: initial => shutdown assert_eq!(h1.poll_count, 1); - if let DispatcherState::Normal(ref mut inner) = h1.inner { - let res = &mut inner.io.take().unwrap().write_buf[..]; + if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() { + let res = &mut inner.project().io.take().unwrap().write_buf[..]; stabilize_date_header(res); let exp = b"\ @@ -1144,7 +1178,7 @@ mod tests { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( buf.clone(), cfg, CloneableService::new(echo_payload_service()), @@ -1164,7 +1198,9 @@ mod tests { ", ); - assert!(Pin::new(&mut h1).poll(cx).is_pending()); + futures_util::pin_mut!(h1); + + assert!(h1.as_mut().poll(cx).is_pending()); assert!(matches!(&h1.inner, DispatcherState::Normal(_))); // polls: manual @@ -1181,7 +1217,7 @@ mod tests { } buf.extend_read_buf("12345"); - assert!(Pin::new(&mut h1).poll(cx).is_ready()); + assert!(h1.as_mut().poll(cx).is_ready()); // polls: manual manual shutdown assert_eq!(h1.poll_count, 3); @@ -1214,7 +1250,7 @@ mod tests { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( buf.clone(), cfg, CloneableService::new(echo_path_service()), @@ -1234,7 +1270,9 @@ mod tests { ", ); - assert!(Pin::new(&mut h1).poll(cx).is_ready()); + futures_util::pin_mut!(h1); + + assert!(h1.as_mut().poll(cx).is_ready()); assert!(matches!(&h1.inner, DispatcherState::Normal(_))); // polls: manual shutdown @@ -1272,7 +1310,7 @@ mod tests { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( buf.clone(), cfg, CloneableService::new(ok_service()), @@ -1292,7 +1330,9 @@ mod tests { ", ); - assert!(Pin::new(&mut h1).poll(cx).is_ready()); + futures_util::pin_mut!(h1); + + assert!(h1.as_mut().poll(cx).is_ready()); assert!(matches!(&h1.inner, DispatcherState::Upgrade(_))); // polls: manual shutdown From 404b5a7709b7aaac8703a7b9751262508e4d7ca9 Mon Sep 17 00:00:00 2001 From: Edoardo Morandi Date: Sat, 26 Dec 2020 04:36:15 +0100 Subject: [PATCH 56/81] Add optional support for hidden files/directories (#1811) --- actix-files/CHANGES.md | 3 +++ actix-files/src/files.rs | 11 +++++++++++ actix-files/src/path_buf.rs | 22 +++++++++++++++++++++- actix-files/src/service.rs | 10 ++++++---- 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index c4d56010f..afd4696e8 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2020-xx-xx +* Optionally support hidden files/directories. [#1811] + +[#1811]: https://github.com/actix/actix-web/pull/1811 ## 0.4.1 - 2020-11-24 diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index a99b4699e..d0cac6aa4 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -39,6 +39,7 @@ pub struct Files { mime_override: Option>, file_flags: named::Flags, guards: Option>, + hidden_files: bool, } impl fmt::Debug for Files { @@ -60,6 +61,7 @@ impl Clone for Files { path: self.path.clone(), mime_override: self.mime_override.clone(), guards: self.guards.clone(), + hidden_files: self.hidden_files, } } } @@ -103,6 +105,7 @@ impl Files { mime_override: None, file_flags: named::Flags::default(), guards: None, + hidden_files: false, } } @@ -213,6 +216,13 @@ impl Files { self } + + /// Enables serving hidden files and directories, allowing a leading dots in url fragments. + #[inline] + pub fn use_hidden_files(mut self) -> Self { + self.hidden_files = true; + self + } } impl HttpServiceFactory for Files { @@ -251,6 +261,7 @@ impl ServiceFactory for Files { mime_override: self.mime_override.clone(), file_flags: self.file_flags, guards: self.guards.clone(), + hidden_files: self.hidden_files, }; if let Some(ref default) = *self.default.borrow() { diff --git a/actix-files/src/path_buf.rs b/actix-files/src/path_buf.rs index 2f3ae84d4..dd8e5b503 100644 --- a/actix-files/src/path_buf.rs +++ b/actix-files/src/path_buf.rs @@ -15,12 +15,19 @@ impl FromStr for PathBufWrap { type Err = UriSegmentError; fn from_str(path: &str) -> Result { + Self::parse_path(path, false) + } +} + +impl PathBufWrap { + /// Parse a path, giving the choice of allowing hidden files to be considered valid segments. + pub fn parse_path(path: &str, hidden_files: bool) -> Result { let mut buf = PathBuf::new(); for segment in path.split('/') { if segment == ".." { buf.pop(); - } else if segment.starts_with('.') { + } else if !hidden_files && segment.starts_with('.') { return Err(UriSegmentError::BadStart('.')); } else if segment.starts_with('*') { return Err(UriSegmentError::BadStart('*')); @@ -96,4 +103,17 @@ mod tests { PathBuf::from_iter(vec!["seg2"]) ); } + + #[test] + fn test_parse_path() { + assert_eq!( + PathBufWrap::parse_path("/test/.tt", false).map(|t| t.0), + Err(UriSegmentError::BadStart('.')) + ); + + assert_eq!( + PathBufWrap::parse_path("/test/.tt", true).unwrap().0, + PathBuf::from_iter(vec!["test", ".tt"]) + ); + } } diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index cbf4c2d3b..dc4f2bd2c 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -31,6 +31,7 @@ pub struct FilesService { pub(crate) mime_override: Option>, pub(crate) file_flags: named::Flags, pub(crate) guards: Option>, + pub(crate) hidden_files: bool, } type FilesServiceFuture = Either< @@ -83,10 +84,11 @@ impl Service for FilesService { ))); } - let real_path: PathBufWrap = match req.match_info().path().parse() { - Ok(item) => item, - Err(e) => return Either::Left(ok(req.error_response(e))), - }; + let real_path = + match PathBufWrap::parse_path(req.match_info().path(), self.hidden_files) { + Ok(item) => item, + Err(e) => return Either::Left(ok(req.error_response(e))), + }; // full file path let path = match self.directory.join(&real_path).canonicalize() { From b373e1370d21b34ea5fdd79a97a1c15a44c03202 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 26 Dec 2020 04:05:45 +0000 Subject: [PATCH 57/81] prepare files 0.5.0 release --- actix-files/CHANGES.md | 3 +++ actix-files/Cargo.toml | 8 ++++---- actix-files/README.md | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index afd4696e8..86e5e6563 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2020-xx-xx + + +## 0.5.0 - 2020-12-26 * Optionally support hidden files/directories. [#1811] [#1811]: https://github.com/actix/actix-web/pull/1811 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index f7d32f8ec..b67abb1c1 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.4.1" +version = "0.5.0" authors = ["Nikolay Kim "] description = "Static file serving for Actix Web" readme = "README.md" @@ -23,13 +23,13 @@ bitflags = "1" bytes = "0.5.3" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } -derive_more = "0.99.2" +derive_more = "0.99.5" log = "0.4" mime = "0.3" mime_guess = "2.0.1" percent-encoding = "2.1" -v_htmlescape = "0.11" +v_htmlescape = "0.12" [dev-dependencies] actix-rt = "1.0.0" -actix-web = { version = "3.0.0", features = ["openssl"] } +actix-web = "3.0.0" diff --git a/actix-files/README.md b/actix-files/README.md index 685e5dbe5..2953b4458 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.4.1)](https://docs.rs/actix-files/0.4.1) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.5.0)](https://docs.rs/actix-files/0.5.0) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.4.1/status.svg)](https://deps.rs/crate/actix-files/0.4.1) +[![dependency status](https://deps.rs/crate/actix-files/0.5.0/status.svg)](https://deps.rs/crate/actix-files/0.5.0) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![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) From 1032f04deddecd8bdc3e95798382d51efb74a30b Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 26 Dec 2020 20:46:36 +0800 Subject: [PATCH 58/81] remove unused `actix_http::h1::OneRequest` (#1853) --- actix-http/src/h1/mod.rs | 2 +- actix-http/src/h1/service.rs | 112 ++--------------------------------- 2 files changed, 7 insertions(+), 107 deletions(-) diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index 0c85f076a..3d5dea5d6 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -17,7 +17,7 @@ pub use self::codec::Codec; pub use self::dispatcher::Dispatcher; pub use self::expect::ExpectHandler; pub use self::payload::Payload; -pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; +pub use self::service::{H1Service, H1ServiceHandler}; pub use self::upgrade::UpgradeHandler; pub use self::utils::SendResponse; diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 5008791c0..cbba0609c 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -9,12 +9,12 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; use futures_core::ready; -use futures_util::future::{ok, Ready}; +use futures_util::future::ready; use crate::body::MessageBody; use crate::cloneable::CloneableService; use crate::config::ServiceConfig; -use crate::error::{DispatchError, Error, ParseError}; +use crate::error::{DispatchError, Error}; use crate::helpers::DataFactory; use crate::request::Request; use crate::response::Response; @@ -22,7 +22,7 @@ use crate::{ConnectCallback, Extensions}; use super::codec::Codec; use super::dispatcher::Dispatcher; -use super::{ExpectHandler, Message, UpgradeHandler}; +use super::{ExpectHandler, UpgradeHandler}; /// `ServiceFactory` implementation for HTTP1 transport pub struct H1Service> { @@ -90,7 +90,7 @@ where > { pipeline_factory(|io: TcpStream| { let peer_addr = io.peer_addr().ok(); - ok((io, peer_addr)) + ready(Ok((io, peer_addr))) }) .and_then(self) } @@ -139,7 +139,7 @@ mod openssl { ) .and_then(|io: SslStream| { let peer_addr = io.get_ref().peer_addr().ok(); - ok((io, peer_addr)) + ready(Ok((io, peer_addr))) }) .and_then(self.map_err(TlsError::Service)) } @@ -189,7 +189,7 @@ mod rustls { ) .and_then(|io: TlsStream| { let peer_addr = io.get_ref().0.peer_addr().ok(); - ok((io, peer_addr)) + ready(Ok((io, peer_addr))) }) .and_then(self.map_err(TlsError::Service)) } @@ -500,103 +500,3 @@ where ) } } - -/// `ServiceFactory` implementation for `OneRequestService` service -#[derive(Default)] -pub struct OneRequest { - config: ServiceConfig, - _t: PhantomData, -} - -impl OneRequest -where - T: AsyncRead + AsyncWrite + Unpin, -{ - /// Create new `H1SimpleService` instance. - pub fn new() -> Self { - OneRequest { - config: ServiceConfig::default(), - _t: PhantomData, - } - } -} - -impl ServiceFactory for OneRequest -where - T: AsyncRead + AsyncWrite + Unpin, -{ - type Config = (); - type Request = T; - type Response = (Request, Framed); - type Error = ParseError; - type InitError = (); - type Service = OneRequestService; - type Future = Ready>; - - fn new_service(&self, _: ()) -> Self::Future { - ok(OneRequestService { - _t: PhantomData, - config: self.config.clone(), - }) - } -} - -/// `Service` implementation for HTTP1 transport. Reads one request and returns -/// request and framed object. -pub struct OneRequestService { - _t: PhantomData, - config: ServiceConfig, -} - -impl Service for OneRequestService -where - T: AsyncRead + AsyncWrite + Unpin, -{ - type Request = T; - type Response = (Request, Framed); - type Error = ParseError; - type Future = OneRequestServiceResponse; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - OneRequestServiceResponse { - framed: Some(Framed::new(req, Codec::new(self.config.clone()))), - } - } -} - -#[doc(hidden)] -#[pin_project::pin_project] -pub struct OneRequestServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, -{ - #[pin] - framed: Option>, -} - -impl Future for OneRequestServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, -{ - type Output = Result<(Request, Framed), ParseError>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - - match ready!(this.framed.as_pin_mut().unwrap().next_item(cx)) { - Some(Ok(req)) => match req { - Message::Item(req) => { - let mut this = self.as_mut().project(); - Poll::Ready(Ok((req, this.framed.take().unwrap()))) - } - Message::Chunk(_) => unreachable!("Something is wrong"), - }, - Some(Err(err)) => Poll::Ready(Err(err)), - None => Poll::Ready(Err(ParseError::Incomplete)), - } - } -} From cbda928a33ce69e5d83059ff459464ba2a60b19f Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Sat, 26 Dec 2020 16:46:19 -0500 Subject: [PATCH 59/81] Rename factory to handler (#1852) --- CHANGES.md | 4 +-- src/handler.rs | 93 ++++++++++++++++++++++++++----------------------- src/lib.rs | 2 +- src/resource.rs | 10 +++--- src/route.rs | 14 ++++---- src/web.rs | 10 +++--- 6 files changed, 69 insertions(+), 64 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ee9b9308d..da04c5aa3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,13 +3,13 @@ ## Unreleased - 2020-xx-xx ### Changed * Bumped `rand` to `0.8` +* Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] ### Fixed * added the actual parsing error to `test::read_body_json` [#1812] [#1812]: https://github.com/actix/actix-web/pull/1812 - - +[#1852]: https://github.com/actix/actix-web/pull/1852 ## 3.3.2 - 2020-12-01 ### Fixed diff --git a/src/handler.rs b/src/handler.rs index 3d0a2382e..b928939a5 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -14,20 +14,25 @@ use crate::request::HttpRequest; use crate::responder::Responder; use crate::service::{ServiceRequest, ServiceResponse}; -/// Async handler converter factory -pub trait Factory: Clone + 'static +/// A request handler is an async function that accepts zero or more parameters that can be +/// extracted from a request (ie, [`impl FromRequest`](crate::FromRequest)) and returns a type that can be converted into +/// an [`HttpResponse`](crate::HttpResponse) (ie, [`impl Responder`](crate::Responder)). +/// +/// If you got the error `the trait Handler<_, _, _> is not implemented`, then your function is not +/// a valid handler. See [Request Handlers](https://actix.rs/docs/handlers/) for more information. +pub trait Handler: Clone + 'static where - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { fn call(&self, param: T) -> R; } -impl Factory<(), R, O> for F +impl Handler<(), R> for F where F: Fn() -> R + Clone + 'static, - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { fn call(&self, _: ()) -> R { (self)() @@ -36,53 +41,53 @@ where #[doc(hidden)] /// Extract arguments from request, run factory function and make response. -pub struct Handler +pub struct HandlerService where - F: Factory, + F: Handler, T: FromRequest, - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { hnd: F, - _t: PhantomData<(T, R, O)>, + _t: PhantomData<(T, R)>, } -impl Handler +impl HandlerService where - F: Factory, + F: Handler, T: FromRequest, - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { pub fn new(hnd: F) -> Self { - Handler { + Self { hnd, _t: PhantomData, } } } -impl Clone for Handler +impl Clone for HandlerService where - F: Factory, + F: Handler, T: FromRequest, - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { fn clone(&self) -> Self { - Handler { + Self { hnd: self.hnd.clone(), _t: PhantomData, } } } -impl ServiceFactory for Handler +impl ServiceFactory for HandlerService where - F: Factory, + F: Handler, T: FromRequest, - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { type Request = ServiceRequest; type Response = ServiceResponse; @@ -97,18 +102,18 @@ where } } -// Handler is both it's ServiceFactory and Service Type. -impl Service for Handler +// Handler is both it's ServiceHandler and Service Type. +impl Service for HandlerService where - F: Factory, + F: Handler, T: FromRequest, - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = HandlerServiceFuture; + type Future = HandlerServiceFuture; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) @@ -123,24 +128,24 @@ where #[doc(hidden)] #[pin_project(project = HandlerProj)] -pub enum HandlerServiceFuture +pub enum HandlerServiceFuture where - F: Factory, + F: Handler, T: FromRequest, - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { Extract(#[pin] T::Future, Option, F), Handle(#[pin] R, Option), - Respond(#[pin] O::Future, Option), + Respond(#[pin] ::Future, Option), } -impl Future for HandlerServiceFuture +impl Future for HandlerServiceFuture where - F: Factory, + F: Handler, T: FromRequest, - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { // Error type in this future is a placeholder type. // all instances of error must be converted to ServiceResponse and return in Ok. @@ -181,10 +186,10 @@ where /// FromRequest trait impl for tuples macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { - impl Factory<($($T,)+), Res, O> for Func + impl Handler<($($T,)+), Res> for Func where Func: Fn($($T,)+) -> Res + Clone + 'static, - Res: Future, - O: Responder, + Res: Future, + Res::Output: Responder, { fn call(&self, param: ($($T,)+)) -> Res { (self)($(param.$n,)+) diff --git a/src/lib.rs b/src/lib.rs index b8346d966..8246c8286 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,7 +121,7 @@ pub mod dev { pub use crate::config::{AppConfig, AppService}; #[doc(hidden)] - pub use crate::handler::Factory; + pub use crate::handler::Handler; pub use crate::info::ConnectionInfo; pub use crate::rmap::ResourceMap; pub use crate::service::{ diff --git a/src/resource.rs b/src/resource.rs index dd9b23012..f6046d652 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -17,7 +17,7 @@ use crate::data::Data; use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}; use crate::extract::FromRequest; use crate::guard::Guard; -use crate::handler::Factory; +use crate::handler::Handler; use crate::responder::Responder; use crate::route::{CreateRouteService, Route, RouteService}; use crate::service::{ServiceRequest, ServiceResponse}; @@ -227,12 +227,12 @@ where /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } /// App::new().service(web::resource("/").route(web::route().to(index))); /// ``` - pub fn to(mut self, handler: F) -> Self + pub fn to(mut self, handler: F) -> Self where - F: Factory, + F: Handler, I: FromRequest + 'static, - R: Future + 'static, - U: Responder + 'static, + R: Future + 'static, + R::Output: Responder + 'static, { self.routes.push(Route::new().to(handler)); self diff --git a/src/route.rs b/src/route.rs index 439ae6c4a..00d93fce9 100644 --- a/src/route.rs +++ b/src/route.rs @@ -11,7 +11,7 @@ use futures_util::future::{ready, FutureExt, LocalBoxFuture}; use crate::extract::FromRequest; use crate::guard::{self, Guard}; -use crate::handler::{Factory, Handler}; +use crate::handler::{Handler, HandlerService}; use crate::responder::Responder; use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; @@ -51,7 +51,7 @@ impl Route { #[allow(clippy::new_without_default)] pub fn new() -> Route { Route { - service: Box::new(RouteNewService::new(Handler::new(|| { + service: Box::new(RouteNewService::new(HandlerService::new(|| { ready(HttpResponse::NotFound()) }))), guards: Rc::new(Vec::new()), @@ -219,14 +219,14 @@ impl Route { /// ); /// } /// ``` - pub fn to(mut self, handler: F) -> Self + pub fn to(mut self, handler: F) -> Self where - F: Factory, + F: Handler, T: FromRequest + 'static, - R: Future + 'static, - U: Responder + 'static, + R: Future + 'static, + R::Output: Responder + 'static, { - self.service = Box::new(RouteNewService::new(Handler::new(handler))); + self.service = Box::new(RouteNewService::new(HandlerService::new(handler))); self } } diff --git a/src/web.rs b/src/web.rs index bf2158917..85e5f2e7b 100644 --- a/src/web.rs +++ b/src/web.rs @@ -9,7 +9,7 @@ pub use futures_channel::oneshot::Canceled; use crate::error::BlockingError; use crate::extract::FromRequest; -use crate::handler::Factory; +use crate::handler::Handler; use crate::resource::Resource; use crate::responder::Responder; use crate::route::Route; @@ -244,12 +244,12 @@ pub fn method(method: Method) -> Route { /// web::to(index)) /// ); /// ``` -pub fn to(handler: F) -> Route +pub fn to(handler: F) -> Route where - F: Factory, + F: Handler, I: FromRequest + 'static, - R: Future + 'static, - U: Responder + 'static, + R: Future + 'static, + R::Output: Responder + 'static, { Route::new().to(handler) } From f9fcf56d5c525956572051303ab8bce793cb4456 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 28 Dec 2020 04:37:53 +0800 Subject: [PATCH 60/81] reduce branch in actix_http::h1::codec (#1854) --- actix-http/src/h1/codec.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index c9a62dc30..d5035df26 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -111,8 +111,8 @@ impl Decoder for Codec { 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 let Some(ref mut payload) = self.payload { + Ok(match payload.decode(src)? { Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), Some(PayloadItem::Eof) => { self.payload.take(); From 8c9ea43e232b55234fe24b04b023a7a7d5f18bd4 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 27 Dec 2020 20:53:19 +0000 Subject: [PATCH 61/81] address clippy warnings --- actix-files/CHANGES.md | 1 + actix-files/src/range.rs | 41 +++++++++++++++++++---------------- src/middleware/errhandlers.rs | 1 + tests/test_httpserver.rs | 1 + 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 86e5e6563..49768419b 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2020-xx-xx +* `HttpRange::parse` now has its own error type. ## 0.5.0 - 2020-12-26 diff --git a/actix-files/src/range.rs b/actix-files/src/range.rs index e891ca7ec..e420ce414 100644 --- a/actix-files/src/range.rs +++ b/actix-files/src/range.rs @@ -1,3 +1,5 @@ +use derive_more::{Display, Error}; + /// HTTP Range header representation. #[derive(Debug, Clone, Copy)] pub struct HttpRange { @@ -11,17 +13,21 @@ pub struct HttpRange { const PREFIX: &str = "bytes="; const PREFIX_LEN: usize = 6; +#[derive(Debug, Clone, Display, Error)] +#[display(fmt = "Parse HTTP Range failed")] +pub struct ParseRangeErr(#[error(not(source))] ()); + 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). - pub fn parse(header: &str, size: u64) -> Result, ()> { + pub fn parse(header: &str, size: u64) -> Result, ParseRangeErr> { if header.is_empty() { return Ok(Vec::new()); } if !header.starts_with(PREFIX) { - return Err(()); + return Err(ParseRangeErr(())); } let size_sig = size as i64; @@ -34,13 +40,14 @@ impl HttpRange { .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(); + let start_str = start_end_iter.next().ok_or(ParseRangeErr(()))?.trim(); + let end_str = start_end_iter.next().ok_or(ParseRangeErr(()))?.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 = end_str.parse().map_err(|_| ())?; + let mut length: i64 = + end_str.parse().map_err(|_| ParseRangeErr(()))?; if length > size_sig { length = size_sig; @@ -51,10 +58,10 @@ impl HttpRange { length: length as u64, })) } else { - let start: i64 = start_str.parse().map_err(|_| ())?; + let start: i64 = start_str.parse().map_err(|_| ParseRangeErr(()))?; if start < 0 { - return Err(()); + return Err(ParseRangeErr(())); } if start >= size_sig { no_overlap = true; @@ -65,10 +72,11 @@ impl HttpRange { // 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(|_| ())?; + let mut end: i64 = + end_str.parse().map_err(|_| ParseRangeErr(()))?; if start > end { - return Err(()); + return Err(ParseRangeErr(())); } if end >= size_sig { @@ -89,7 +97,7 @@ impl HttpRange { let ranges: Vec = all_ranges.into_iter().filter_map(|x| x).collect(); if no_overlap && ranges.is_empty() { - return Err(()); + return Err(ParseRangeErr(())); } Ok(ranges) @@ -333,8 +341,7 @@ mod tests { if expected.is_empty() { continue; } else { - assert!( - false, + panic!( "parse({}, {}) returned error {:?}", header, size, @@ -346,28 +353,24 @@ mod tests { let got = res.unwrap(); if got.len() != expected.len() { - assert!( - false, + panic!( "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, + panic!( "parseRange({}, {})[{}].start = {}, want {}", header, size, i, got[i].start, expected[i].start ) } if got[i].length != expected[i].length { - assert!( - false, + panic!( "parseRange({}, {})[{}].length = {}, want {}", header, size, i, got[i].length, expected[i].length ) diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index c0cb9594e..d2d3b0d8c 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -179,6 +179,7 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } + #[allow(clippy::unnecessary_wraps)] fn render_500_async( mut res: ServiceResponse, ) -> Result> { diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 118640aca..d164f4445 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -63,6 +63,7 @@ async fn test_start() { let _ = sys.stop(); } +#[allow(clippy::unnecessary_wraps)] #[cfg(feature = "openssl")] fn ssl_acceptor() -> std::io::Result { use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; From 093d3a6c59338045f46ee85f1a3ec3b028f97712 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 27 Dec 2020 23:23:30 +0000 Subject: [PATCH 62/81] remove deprecated on_connect methods (#1857) --- actix-http/CHANGES.md | 7 +++++++ actix-http/src/builder.rs | 23 ----------------------- actix-http/src/clinu/mod.rs | 0 actix-http/src/h1/dispatcher.rs | 18 ------------------ actix-http/src/h1/service.rs | 23 ----------------------- actix-http/src/h2/dispatcher.rs | 10 ---------- actix-http/src/h2/service.rs | 25 ------------------------- actix-http/src/helpers.rs | 14 -------------- actix-http/src/service.rs | 30 +----------------------------- actix-http/tests/test_openssl.rs | 2 -- actix-http/tests/test_server.rs | 2 -- 11 files changed, 8 insertions(+), 146 deletions(-) create mode 100644 actix-http/src/clinu/mod.rs diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 81577688d..c43246bb0 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,6 +4,13 @@ ### Changed * Bumped `rand` to `0.8` +### Removed +* Deprecated `on_connect` methods have been removed. Prefer the new + `on_connect_ext` technique. [#1857] + +[#1857]: https://github.com/actix/actix-web/pull/1857 + + ## 2.2.0 - 2020-11-25 ### Added * HttpResponse builders for 1xx status codes. [#1768] diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index b28c69761..df1b332c1 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -10,7 +10,6 @@ use crate::config::{KeepAlive, ServiceConfig}; use crate::error::Error; use crate::h1::{Codec, ExpectHandler, H1Service, UpgradeHandler}; use crate::h2::H2Service; -use crate::helpers::{Data, DataFactory}; use crate::request::Request; use crate::response::Response; use crate::service::HttpService; @@ -28,8 +27,6 @@ pub struct HttpServiceBuilder> { local_addr: Option, expect: X, upgrade: Option, - // DEPRECATED: in favor of on_connect_ext - on_connect: Option Box>>, on_connect_ext: Option>>, _t: PhantomData<(T, S)>, } @@ -51,7 +48,6 @@ where local_addr: None, expect: ExpectHandler, upgrade: None, - on_connect: None, on_connect_ext: None, _t: PhantomData, } @@ -141,7 +137,6 @@ where local_addr: self.local_addr, expect: expect.into_factory(), upgrade: self.upgrade, - on_connect: self.on_connect, on_connect_ext: self.on_connect_ext, _t: PhantomData, } @@ -171,26 +166,11 @@ where local_addr: self.local_addr, expect: self.expect, upgrade: Some(upgrade.into_factory()), - on_connect: self.on_connect, on_connect_ext: self.on_connect_ext, _t: PhantomData, } } - /// Set on-connect callback. - /// - /// Called once per connection. Return value of the call is stored in request extensions. - /// - /// *SOFT DEPRECATED*: Prefer the `on_connect_ext` style callback. - pub fn on_connect(mut self, f: F) -> Self - where - F: Fn(&T) -> I + 'static, - I: Clone + 'static, - { - self.on_connect = Some(Rc::new(move |io| Box::new(Data(f(io))))); - self - } - /// Sets the callback to be run on connection establishment. /// /// Has mutable access to a data container that will be merged into request extensions. @@ -224,7 +204,6 @@ where H1Service::with_config(cfg, service.into_factory()) .expect(self.expect) .upgrade(self.upgrade) - .on_connect(self.on_connect) .on_connect_ext(self.on_connect_ext) } @@ -247,7 +226,6 @@ where ); H2Service::with_config(cfg, service.into_factory()) - .on_connect(self.on_connect) .on_connect_ext(self.on_connect_ext) } @@ -272,7 +250,6 @@ where HttpService::with_config(cfg, service.into_factory()) .expect(self.expect) .upgrade(self.upgrade) - .on_connect(self.on_connect) .on_connect_ext(self.on_connect_ext) } } diff --git a/actix-http/src/clinu/mod.rs b/actix-http/src/clinu/mod.rs new file mode 100644 index 000000000..e69de29bb diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index ea8f91e0d..91e208aac 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -19,7 +19,6 @@ use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; use crate::error::{ParseError, PayloadError}; -use crate::helpers::DataFactory; use crate::httpmessage::HttpMessage; use crate::request::Request; use crate::response::Response; @@ -96,7 +95,6 @@ where service: CloneableService, expect: CloneableService, upgrade: Option>, - on_connect: Option>, on_connect_data: Extensions, flags: Flags, peer_addr: Option, @@ -184,7 +182,6 @@ where service: CloneableService, expect: CloneableService, upgrade: Option>, - on_connect: Option>, on_connect_data: Extensions, peer_addr: Option, ) -> Self { @@ -197,7 +194,6 @@ where service, expect, upgrade, - on_connect, on_connect_data, peer_addr, ) @@ -213,7 +209,6 @@ where service: CloneableService, expect: CloneableService, upgrade: Option>, - on_connect: Option>, on_connect_data: Extensions, peer_addr: Option, ) -> Self { @@ -246,7 +241,6 @@ where service, expect, upgrade, - on_connect, on_connect_data, flags, peer_addr, @@ -572,12 +566,6 @@ where let pl = this.codec.message_type(); req.head_mut().peer_addr = *this.peer_addr; - // DEPRECATED - // set on_connect data - if let Some(ref on_connect) = this.on_connect { - on_connect.set(&mut req.extensions_mut()); - } - // merge on_connect_ext data into request extensions req.extensions_mut().drain_from(this.on_connect_data); @@ -1038,7 +1026,6 @@ mod tests { CloneableService::new(ok_service()), CloneableService::new(ExpectHandler), None, - None, Extensions::new(), None, ); @@ -1079,7 +1066,6 @@ mod tests { CloneableService::new(echo_path_service()), CloneableService::new(ExpectHandler), None, - None, Extensions::new(), None, ); @@ -1134,7 +1120,6 @@ mod tests { CloneableService::new(echo_path_service()), CloneableService::new(ExpectHandler), None, - None, Extensions::new(), None, ); @@ -1184,7 +1169,6 @@ mod tests { CloneableService::new(echo_payload_service()), CloneableService::new(ExpectHandler), None, - None, Extensions::new(), None, ); @@ -1256,7 +1240,6 @@ mod tests { CloneableService::new(echo_path_service()), CloneableService::new(ExpectHandler), None, - None, Extensions::new(), None, ); @@ -1316,7 +1299,6 @@ mod tests { CloneableService::new(ok_service()), CloneableService::new(ExpectHandler), Some(CloneableService::new(UpgradeHandler(PhantomData))), - None, Extensions::new(), None, ); diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index cbba0609c..919a5d932 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -15,7 +15,6 @@ use crate::body::MessageBody; use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; -use crate::helpers::DataFactory; use crate::request::Request; use crate::response::Response; use crate::{ConnectCallback, Extensions}; @@ -30,7 +29,6 @@ pub struct H1Service> { cfg: ServiceConfig, expect: X, upgrade: Option, - on_connect: Option Box>>, on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -53,7 +51,6 @@ where srv: service.into_factory(), expect: ExpectHandler, upgrade: None, - on_connect: None, on_connect_ext: None, _t: PhantomData, } @@ -215,7 +212,6 @@ where cfg: self.cfg, srv: self.srv, upgrade: self.upgrade, - on_connect: self.on_connect, on_connect_ext: self.on_connect_ext, _t: PhantomData, } @@ -232,21 +228,11 @@ where cfg: self.cfg, srv: self.srv, expect: self.expect, - on_connect: self.on_connect, on_connect_ext: self.on_connect_ext, _t: PhantomData, } } - /// Set on connect callback. - pub(crate) fn on_connect( - mut self, - f: Option Box>>, - ) -> Self { - self.on_connect = f; - self - } - /// Set on connect callback. pub(crate) fn on_connect_ext(mut self, f: Option>>) -> Self { self.on_connect_ext = f; @@ -284,7 +270,6 @@ where fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())), expect: None, upgrade: None, - on_connect: self.on_connect.clone(), on_connect_ext: self.on_connect_ext.clone(), cfg: Some(self.cfg.clone()), _t: PhantomData, @@ -314,7 +299,6 @@ where fut_upg: Option, expect: Option, upgrade: Option, - on_connect: Option Box>>, on_connect_ext: Option>>, cfg: Option, _t: PhantomData<(T, B)>, @@ -371,7 +355,6 @@ where service, this.expect.take().unwrap(), this.upgrade.take(), - this.on_connect.clone(), this.on_connect_ext.clone(), ) })) @@ -383,7 +366,6 @@ pub struct H1ServiceHandler { srv: CloneableService, expect: CloneableService, upgrade: Option>, - on_connect: Option Box>>, on_connect_ext: Option>>, cfg: ServiceConfig, _t: PhantomData<(T, B)>, @@ -405,7 +387,6 @@ where srv: S, expect: X, upgrade: Option, - on_connect: Option Box>>, on_connect_ext: Option>>, ) -> H1ServiceHandler { H1ServiceHandler { @@ -413,7 +394,6 @@ where expect: CloneableService::new(expect), upgrade: upgrade.map(CloneableService::new), cfg, - on_connect, on_connect_ext, _t: PhantomData, } @@ -480,8 +460,6 @@ where } fn call(&mut self, (io, addr): Self::Request) -> Self::Future { - let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io)); - let mut connect_extensions = Extensions::new(); if let Some(ref handler) = self.on_connect_ext { // run on_connect_ext callback, populating connect extensions @@ -494,7 +472,6 @@ where self.srv.clone(), self.expect.clone(), self.upgrade.clone(), - deprecated_on_connect, connect_extensions, addr, ) diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 594339121..7a0be9492 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -18,7 +18,6 @@ use crate::body::{BodySize, MessageBody, ResponseBody}; use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; -use crate::helpers::DataFactory; use crate::httpmessage::HttpMessage; use crate::message::ResponseHead; use crate::payload::Payload; @@ -36,7 +35,6 @@ where { service: CloneableService, connection: Connection, - on_connect: Option>, on_connect_data: Extensions, config: ServiceConfig, peer_addr: Option, @@ -57,7 +55,6 @@ where pub(crate) fn new( service: CloneableService, connection: Connection, - on_connect: Option>, on_connect_data: Extensions, config: ServiceConfig, timeout: Option, @@ -84,7 +81,6 @@ where config, peer_addr, connection, - on_connect, on_connect_data, ka_expire, ka_timer, @@ -134,12 +130,6 @@ where head.headers = parts.headers.into(); head.peer_addr = this.peer_addr; - // DEPRECATED - // set on_connect data - if let Some(ref on_connect) = this.on_connect { - on_connect.set(&mut req.extensions_mut()); - } - // merge on_connect_ext data into request extensions req.extensions_mut().drain_from(&mut this.on_connect_data); diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 3103127b4..b1fb9a634 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -20,7 +20,6 @@ use crate::body::MessageBody; use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; -use crate::helpers::DataFactory; use crate::request::Request; use crate::response::Response; use crate::{ConnectCallback, Extensions}; @@ -31,7 +30,6 @@ use super::dispatcher::Dispatcher; pub struct H2Service { srv: S, cfg: ServiceConfig, - on_connect: Option Box>>, on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -51,23 +49,12 @@ where ) -> Self { H2Service { cfg, - on_connect: None, on_connect_ext: None, srv: service.into_factory(), _t: PhantomData, } } - /// Set on connect callback. - - pub(crate) fn on_connect( - mut self, - f: Option Box>>, - ) -> Self { - self.on_connect = f; - self - } - /// Set on connect callback. pub(crate) fn on_connect_ext(mut self, f: Option>>) -> Self { self.on_connect_ext = f; @@ -212,7 +199,6 @@ where H2ServiceResponse { fut: self.srv.new_service(()), cfg: Some(self.cfg.clone()), - on_connect: self.on_connect.clone(), on_connect_ext: self.on_connect_ext.clone(), _t: PhantomData, } @@ -225,7 +211,6 @@ pub struct H2ServiceResponse { #[pin] fut: S::Future, cfg: Option, - on_connect: Option Box>>, on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -248,7 +233,6 @@ where let this = self.as_mut().project(); H2ServiceHandler::new( this.cfg.take().unwrap(), - this.on_connect.clone(), this.on_connect_ext.clone(), service, ) @@ -260,7 +244,6 @@ where pub struct H2ServiceHandler { srv: CloneableService, cfg: ServiceConfig, - on_connect: Option Box>>, on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -275,13 +258,11 @@ where { fn new( cfg: ServiceConfig, - on_connect: Option Box>>, on_connect_ext: Option>>, srv: S, ) -> H2ServiceHandler { H2ServiceHandler { cfg, - on_connect, on_connect_ext, srv: CloneableService::new(srv), _t: PhantomData, @@ -312,8 +293,6 @@ where } fn call(&mut self, (io, addr): Self::Request) -> Self::Future { - let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io)); - let mut connect_extensions = Extensions::new(); if let Some(ref handler) = self.on_connect_ext { // run on_connect_ext callback, populating connect extensions @@ -325,7 +304,6 @@ where Some(self.srv.clone()), Some(self.cfg.clone()), addr, - deprecated_on_connect, Some(connect_extensions), server::handshake(io), ), @@ -343,7 +321,6 @@ where Option>, Option, Option, - Option>, Option, Handshake, ), @@ -379,7 +356,6 @@ where ref mut srv, ref mut config, ref peer_addr, - ref mut on_connect, ref mut on_connect_data, ref mut handshake, ) => match Pin::new(handshake).poll(cx) { @@ -387,7 +363,6 @@ where self.state = State::Incoming(Dispatcher::new( srv.take().unwrap(), conn, - on_connect.take(), on_connect_data.take().unwrap(), config.take().unwrap(), None, diff --git a/actix-http/src/helpers.rs b/actix-http/src/helpers.rs index ac0e0f118..13195f7db 100644 --- a/actix-http/src/helpers.rs +++ b/actix-http/src/helpers.rs @@ -3,8 +3,6 @@ use std::io; use bytes::{BufMut, BytesMut}; use http::Version; -use crate::extensions::Extensions; - const DIGITS_START: u8 = b'0'; pub(crate) fn write_status_line(version: Version, n: u16, bytes: &mut BytesMut) { @@ -56,18 +54,6 @@ impl<'a> io::Write for Writer<'a> { } } -pub(crate) trait DataFactory { - fn set(&self, ext: &mut Extensions); -} - -pub(crate) struct Data(pub(crate) T); - -impl DataFactory for Data { - fn set(&self, ext: &mut Extensions) { - ext.insert(self.0.clone()) - } -} - #[cfg(test)] mod tests { use std::str::from_utf8; diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 75745209c..527ed3833 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -17,7 +17,6 @@ use crate::builder::HttpServiceBuilder; use crate::cloneable::CloneableService; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, Error}; -use crate::helpers::DataFactory; use crate::request::Request; use crate::response::Response; use crate::{h1, h2::Dispatcher, ConnectCallback, Extensions, Protocol}; @@ -28,8 +27,6 @@ pub struct HttpService cfg: ServiceConfig, expect: X, upgrade: Option, - // DEPRECATED: in favor of on_connect_ext - on_connect: Option Box>>, on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -67,7 +64,6 @@ where srv: service.into_factory(), expect: h1::ExpectHandler, upgrade: None, - on_connect: None, on_connect_ext: None, _t: PhantomData, } @@ -83,7 +79,6 @@ where srv: service.into_factory(), expect: h1::ExpectHandler, upgrade: None, - on_connect: None, on_connect_ext: None, _t: PhantomData, } @@ -116,7 +111,6 @@ where cfg: self.cfg, srv: self.srv, upgrade: self.upgrade, - on_connect: self.on_connect, on_connect_ext: self.on_connect_ext, _t: PhantomData, } @@ -142,21 +136,11 @@ where cfg: self.cfg, srv: self.srv, expect: self.expect, - on_connect: self.on_connect, on_connect_ext: self.on_connect_ext, _t: PhantomData, } } - /// Set on connect callback. - pub(crate) fn on_connect( - mut self, - f: Option Box>>, - ) -> Self { - self.on_connect = f; - self - } - /// Set connect callback with mutable access to request data container. pub(crate) fn on_connect_ext(mut self, f: Option>>) -> Self { self.on_connect_ext = f; @@ -366,7 +350,6 @@ where fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())), expect: None, upgrade: None, - on_connect: self.on_connect.clone(), on_connect_ext: self.on_connect_ext.clone(), cfg: self.cfg.clone(), _t: PhantomData, @@ -391,7 +374,6 @@ pub struct HttpServiceResponse< fut_upg: Option, expect: Option, upgrade: Option, - on_connect: Option Box>>, on_connect_ext: Option>>, cfg: ServiceConfig, _t: PhantomData<(T, B)>, @@ -451,7 +433,6 @@ where service, this.expect.take().unwrap(), this.upgrade.take(), - this.on_connect.clone(), this.on_connect_ext.clone(), ) })) @@ -464,7 +445,6 @@ pub struct HttpServiceHandler { expect: CloneableService, upgrade: Option>, cfg: ServiceConfig, - on_connect: Option Box>>, on_connect_ext: Option>>, _t: PhantomData<(T, B, X)>, } @@ -486,12 +466,10 @@ where srv: S, expect: X, upgrade: Option, - on_connect: Option Box>>, on_connect_ext: Option>>, ) -> HttpServiceHandler { HttpServiceHandler { cfg, - on_connect, on_connect_ext, srv: CloneableService::new(srv), expect: CloneableService::new(expect), @@ -564,7 +542,6 @@ where fn call(&mut self, (io, proto, peer_addr): Self::Request) -> Self::Future { let mut connect_extensions = Extensions::new(); - let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io)); if let Some(ref handler) = self.on_connect_ext { handler(&io, &mut connect_extensions); } @@ -575,7 +552,6 @@ where server::handshake(io), self.cfg.clone(), self.srv.clone(), - deprecated_on_connect, connect_extensions, peer_addr, ))), @@ -588,7 +564,6 @@ where self.srv.clone(), self.expect.clone(), self.upgrade.clone(), - deprecated_on_connect, connect_extensions, peer_addr, )), @@ -617,7 +592,6 @@ where Handshake, ServiceConfig, CloneableService, - Option>, Extensions, Option, )>, @@ -694,12 +668,10 @@ where } else { panic!() }; - let (_, cfg, srv, on_connect, on_connect_data, peer_addr) = - data.take().unwrap(); + let (_, cfg, srv, on_connect_data, peer_addr) = data.take().unwrap(); self.set(State::H2(Dispatcher::new( srv, conn, - on_connect, on_connect_data, cfg, None, diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 05f01d240..6b80bad0a 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -410,10 +410,8 @@ async fn test_h2_service_error() { async fn test_h2_on_connect() { let srv = test_server(move || { HttpService::build() - .on_connect(|_| 10usize) .on_connect_ext(|_, data| data.insert(20isize)) .h2(|req: Request| { - assert!(req.extensions().contains::()); assert!(req.extensions().contains::()); ok::<_, ()>(Response::Ok().finish()) }) diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index de6368fda..44794e199 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -662,10 +662,8 @@ async fn test_h1_service_error() { async fn test_h1_on_connect() { let srv = test_server(|| { HttpService::build() - .on_connect(|_| 10usize) .on_connect_ext(|_, data| data.insert(20isize)) .h1(|req: Request| { - assert!(req.extensions().contains::()); assert!(req.extensions().contains::()); future::ok::<_, ()>(Response::Ok().finish()) }) From 2a2a20c3e740a47b1534662500e9d3c065bdfa79 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 28 Dec 2020 00:44:15 +0000 Subject: [PATCH 63/81] bump msrv to 1.46 (#1858) --- .github/workflows/linux.yml | 18 +++++++++-- .github/workflows/macos.yml | 12 ++++++++ .github/workflows/windows.yml | 12 ++++++++ CHANGES.md | 1 + README.md | 4 +-- actix-files/README.md | 4 +-- actix-http-test/README.md | 2 +- actix-http/README.md | 2 +- .../src/header/common/content_disposition.rs | 3 +- actix-http/src/lib.rs | 1 - actix-web-codegen/README.md | 4 +-- actix-web-codegen/tests/trybuild.rs | 30 +++++-------------- .../route-missing-method-fail-msrv.rs | 1 - .../route-missing-method-fail-msrv.stderr | 11 ------- awc/README.md | 2 +- src/lib.rs | 4 +-- 16 files changed, 59 insertions(+), 52 deletions(-) delete mode 120000 actix-web-codegen/tests/trybuild/route-missing-method-fail-msrv.rs delete mode 100644 actix-web-codegen/tests/trybuild/route-missing-method-fail-msrv.stderr diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a068070ff..53f22df63 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -13,7 +13,7 @@ jobs: fail-fast: false matrix: version: - - 1.42.0 # MSRV + - 1.46.0 # MSRV - stable - nightly @@ -30,6 +30,13 @@ jobs: profile: minimal override: true + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: + command: generate-lockfile + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.0.1 + - name: check build uses: actions-rs/cargo@v1 with: @@ -58,12 +65,17 @@ jobs: args: --package=awc --no-default-features --features=rustls -- --nocapture - name: Generate coverage file - if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request') + if: matrix.version == 'stable' && github.ref == 'refs/heads/master' run: | cargo install cargo-tarpaulin --vers "^0.13" cargo tarpaulin --out Xml - name: Upload to Codecov - if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request') + if: matrix.version == 'stable' && github.ref == 'refs/heads/master' uses: codecov/codecov-action@v1 with: file: cobertura.xml + + - name: Clear the cargo caches + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index dc8558ac1..6b5366faf 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -29,6 +29,13 @@ jobs: profile: minimal override: true + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: + command: generate-lockfile + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.0.1 + - name: check build uses: actions-rs/cargo@v1 with: @@ -42,3 +49,8 @@ jobs: args: --all --all-features --no-fail-fast -- --nocapture --skip=test_h2_content_length --skip=test_reading_deflate_encoding_large_random_rustls + + - name: Clear the cargo caches + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d53d50a61..d3de72a61 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -41,6 +41,13 @@ jobs: Get-ChildItem C:\vcpkg\installed\x64-windows\bin Get-ChildItem C:\vcpkg\installed\x64-windows\lib + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: + command: generate-lockfile + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.0.1 + - name: check build uses: actions-rs/cargo@v1 with: @@ -62,3 +69,8 @@ jobs: --skip=test_connection_force_close --skip=test_connection_server_close --skip=test_connection_wait_queue_force_close + + - name: Clear the cargo caches + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache diff --git a/CHANGES.md b/CHANGES.md index da04c5aa3..fa56acc17 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ ### Changed * Bumped `rand` to `0.8` * Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] +* MSRV is now 1.46.0. ### Fixed * added the actual parsing error to `test::read_body_json` [#1812] diff --git a/README.md b/README.md index b9f2b7594..62ee50243 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![Documentation](https://docs.rs/actix-web/badge.svg?version=3.3.2)](https://docs.rs/actix-web/3.3.2) -[![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) +[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![License](https://img.shields.io/crates/l/actix-web.svg) [![Dependency Status](https://deps.rs/crate/actix-web/3.3.2/status.svg)](https://deps.rs/crate/actix-web/3.3.2)
@@ -34,7 +34,7 @@ * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) * Includes an async [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) * Supports [Actix actor framework](https://github.com/actix/actix) -* Runs on stable Rust 1.42+ +* Runs on stable Rust 1.46+ ## Documentation diff --git a/actix-files/README.md b/actix-files/README.md index 2953b4458..463f20224 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.5.0)](https://docs.rs/actix-files/0.5.0) -[![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) +[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
[![dependency status](https://deps.rs/crate/actix-files/0.5.0/status.svg)](https://deps.rs/crate/actix-files/0.5.0) @@ -16,4 +16,4 @@ - [API Documentation](https://docs.rs/actix-files/) - [Example Project](https://github.com/actix/examples/tree/master/static_index) - [Chat on Gitter](https://gitter.im/actix/actix-web) -- Minimum supported Rust version: 1.42 or later +- Minimum supported Rust version: 1.46 or later diff --git a/actix-http-test/README.md b/actix-http-test/README.md index c847c8515..bca9a7976 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -12,4 +12,4 @@ - [API Documentation](https://docs.rs/actix-http-test) - [Chat on Gitter](https://gitter.im/actix/actix-web) -- Minimum Supported Rust Version (MSRV): 1.42.0 +- Minimum Supported Rust Version (MSRV): 1.46.0 diff --git a/actix-http/README.md b/actix-http/README.md index 9103cd184..9dfb85e24 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -12,7 +12,7 @@ - [API Documentation](https://docs.rs/actix-http) - [Chat on Gitter](https://gitter.im/actix/actix-web) -- Minimum Supported Rust Version (MSRV): 1.42.0 +- Minimum Supported Rust Version (MSRV): 1.46.0 ## Example diff --git a/actix-http/src/header/common/content_disposition.rs b/actix-http/src/header/common/content_disposition.rs index 826cfef63..4c512acbe 100644 --- a/actix-http/src/header/common/content_disposition.rs +++ b/actix-http/src/header/common/content_disposition.rs @@ -318,9 +318,8 @@ impl ContentDisposition { return Err(crate::error::ParseError::Header); } left = new_left; - if param_name.ends_with('*') { + if let Some(param_name) = param_name.strip_suffix('*') { // 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)?; diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 89d64fb77..94cc50a76 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -7,7 +7,6 @@ clippy::new_without_default, clippy::borrow_interior_mutable_const )] -#![allow(clippy::manual_strip)] // Allow this to keep MSRV(1.42). #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 283591e86..887502075 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -4,7 +4,7 @@ [![crates.io](https://meritbadge.herokuapp.com/actix-web-codegen)](https://crates.io/crates/actix-web-codegen) [![Documentation](https://docs.rs/actix-web-codegen/badge.svg)](https://docs.rs/actix-web-codegen/0.4.0/actix_web_codegen/) -[![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) +[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/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) @@ -14,7 +14,7 @@ - [API Documentation](https://docs.rs/actix-web-codegen) - [Chat on Gitter](https://gitter.im/actix/actix-web) - Cargo package: [actix-web-codegen](https://crates.io/crates/actix-web-codegen) -- Minimum supported Rust version: 1.42 or later. +- Minimum supported Rust version: 1.46 or later. ## Compile Testing Uses the [`trybuild`] crate. All compile fail tests should include a stderr file generated by `trybuild`. See the [workflow section](https://github.com/dtolnay/trybuild#workflow) of the trybuild docs for info on how to do this. diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index 6c7c58986..d2d8a38f5 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -6,31 +6,15 @@ fn compile_macros() { t.compile_fail("tests/trybuild/simple-fail.rs"); t.pass("tests/trybuild/route-ok.rs"); - - test_route_duplicate_unexpected_method(&t); - test_route_missing_method(&t) -} - -#[rustversion::stable(1.42)] -fn test_route_missing_method(t: &trybuild::TestCases) { - t.compile_fail("tests/trybuild/route-missing-method-fail-msrv.rs"); -} - -#[rustversion::not(stable(1.42))] -#[rustversion::not(nightly)] -fn test_route_missing_method(t: &trybuild::TestCases) { t.compile_fail("tests/trybuild/route-missing-method-fail.rs"); -} - -#[rustversion::nightly] -fn test_route_missing_method(_t: &trybuild::TestCases) {} - -// FIXME: Re-test them on nightly once rust-lang/rust#77993 is fixed. -#[rustversion::not(nightly)] -fn test_route_duplicate_unexpected_method(t: &trybuild::TestCases) { t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs"); t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs"); } -#[rustversion::nightly] -fn test_route_duplicate_unexpected_method(_t: &trybuild::TestCases) {} +// #[rustversion::not(nightly)] +// fn skip_on_nightly(t: &trybuild::TestCases) { +// +// } + +// #[rustversion::nightly] +// fn skip_on_nightly(_t: &trybuild::TestCases) {} diff --git a/actix-web-codegen/tests/trybuild/route-missing-method-fail-msrv.rs b/actix-web-codegen/tests/trybuild/route-missing-method-fail-msrv.rs deleted file mode 120000 index 70a5c0e33..000000000 --- a/actix-web-codegen/tests/trybuild/route-missing-method-fail-msrv.rs +++ /dev/null @@ -1 +0,0 @@ -route-missing-method-fail.rs \ No newline at end of file diff --git a/actix-web-codegen/tests/trybuild/route-missing-method-fail-msrv.stderr b/actix-web-codegen/tests/trybuild/route-missing-method-fail-msrv.stderr deleted file mode 100644 index d3e2b60ae..000000000 --- a/actix-web-codegen/tests/trybuild/route-missing-method-fail-msrv.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: The #[route(..)] macro requires at least one `method` attribute - --> $DIR/route-missing-method-fail-msrv.rs:3:1 - | -3 | #[route("/")] - | ^^^^^^^^^^^^^ - -error[E0425]: cannot find value `index` in this scope - --> $DIR/route-missing-method-fail-msrv.rs:12:49 - | -12 | let srv = test::start(|| App::new().service(index)); - | ^^^^^ not found in this scope diff --git a/awc/README.md b/awc/README.md index b97d4fa00..972a80140 100644 --- a/awc/README.md +++ b/awc/README.md @@ -13,7 +13,7 @@ - [API Documentation](https://docs.rs/awc) - [Example Project](https://github.com/actix/examples/tree/HEAD/awc_https) - [Chat on Gitter](https://gitter.im/actix/actix-web) -- Minimum Supported Rust Version (MSRV): 1.42.0 +- Minimum Supported Rust Version (MSRV): 1.46.0 ## Example ```rust diff --git a/src/lib.rs b/src/lib.rs index 8246c8286..88eae44bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,7 @@ //! * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) //! * Includes an async [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) //! * Supports [Actix actor framework](https://github.com/actix/actix) -//! * Runs on stable Rust 1.42+ +//! * Runs on stable Rust 1.46+ //! //! ## Crate Features //! @@ -65,7 +65,7 @@ //! * `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2` //! * `secure-cookies` - secure cookies support -#![deny(rust_2018_idioms)] +#![deny(rust_2018_idioms, nonstandard_style)] #![allow(clippy::needless_doctest_main, clippy::type_complexity)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] From 20b46cdaf907aeb7253b63458307b564209d6210 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Mon, 28 Dec 2020 16:04:02 -0500 Subject: [PATCH 64/81] format factory_tuple macro invocations (#1859) --- src/handler.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index b928939a5..d4b755e57 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -201,14 +201,14 @@ macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { mod m { use super::*; -factory_tuple!((0, A)); -factory_tuple!((0, A), (1, B)); -factory_tuple!((0, A), (1, B), (2, C)); -factory_tuple!((0, A), (1, B), (2, C), (3, D)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); + factory_tuple!((0, A)); + factory_tuple!((0, A), (1, B)); + factory_tuple!((0, A), (1, B), (2, C)); + factory_tuple!((0, A), (1, B), (2, C), (3, D)); + factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E)); + factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); + factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); + factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); + factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); + factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); } From 102bb8f9abf9fdc61d0c63b3ab183bd4e186ef47 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 29 Dec 2020 00:22:13 +0000 Subject: [PATCH 65/81] update dot dep graphs --- docs/graphs/net-only.dot | 6 +----- docs/graphs/web-focus.dot | 42 +++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/docs/graphs/net-only.dot b/docs/graphs/net-only.dot index 0eebf2a6f..9488f3fe7 100644 --- a/docs/graphs/net-only.dot +++ b/docs/graphs/net-only.dot @@ -2,12 +2,10 @@ digraph { subgraph cluster_net { label="actix/actix-net"; "actix-codec" - "actix-connect" "actix-macros" "actix-rt" "actix-server" "actix-service" - "actix-testing" "actix-threadpool" "actix-tls" "actix-tracing" @@ -17,9 +15,7 @@ digraph { "actix-utils" -> { "actix-service" "actix-rt" "actix-codec" } "actix-tracing" -> { "actix-service" } - "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" } - "actix-testing" -> { "actix-rt" "actix-macros" "actix-server" "actix-service" } + "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" } "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" } "actix-rt" -> { "actix-macros" "actix-threadpool" } - "actix-connect" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" } } diff --git a/docs/graphs/web-focus.dot b/docs/graphs/web-focus.dot index 17228fe62..55a82bb41 100644 --- a/docs/graphs/web-focus.dot +++ b/docs/graphs/web-focus.dot @@ -2,31 +2,29 @@ digraph { subgraph cluster_web { label="actix/actix-web" "awc" - "web" - "files" - "http" - "multipart" - "web-actors" - "codegen" - "http-test" + "actix-web" + "actix-files" + "actix-http" + "actix-multipart" + "actix-web-actors" + "actix-web-codegen" + "actix-http-test" } - "web" -> { "codec" "service" "utils" "router" "rt" "server" "testing" "macros" "threadpool" "tls" "codegen" "http" "awc" } - "awc" -> { "codec" "service" "http" "rt" } - "web-actors" -> { "actix" "web" "http" "codec" } - "multipart" -> { "web" "service" "utils" } - "http" -> { "service" "codec" "connect" "utils" "rt" "threadpool" } - "http" -> { "actix" "tls" }[color=blue] // optional - "files" -> { "web" } - "http-test" -> { "service" "codec" "connect" "utils" "rt" "server" "testing" "awc" } + "actix-web" -> { "actix-codec" "actix-service" "actix-utils" "actix-router" "actix-rt" "actix-server" "macros" "threadpool" "actix-tls" "actix-web-codegen" "actix-http" "awc" } + "awc" -> { "actix-codec" "actix-service" "actix-http" "actix-rt" } + "actix-web-actors" -> { "actix" "actix-web" "actix-http" "actix-codec" } + "actix-multipart" -> { "actix-web" "actix-service" "actix-utils" } + "actix-http" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "threadpool" } + "actix-http" -> { "actix" "actix-tls" }[color=blue] // optional + "actix-files" -> { "actix-web" } + "actix-http-test" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "actix-server" "awc" } // net - "utils" -> { "service" "rt" "codec" } - "tracing" -> { "service" } - "tls" -> { "service" "codec" "utils" } - "testing" -> { "rt" "macros" "server" "service" } - "server" -> { "service" "rt" "codec" "utils" } - "rt" -> { "macros" "threadpool" } - "connect" -> { "service" "codec" "utils" "rt" } + "actix-utils" -> { "actix-service" "actix-rt" "actix-codec" } + "actix-tracing" -> { "actix-service" } + "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" } + "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" } + "actix-rt" -> { "macros" "threadpool" } } From 522c9a5ea6eb45c1350a20b4d34d62908f736a78 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 31 Dec 2020 03:24:18 +0000 Subject: [PATCH 66/81] update CoC text --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 62ee50243..956bb3741 100644 --- a/README.md +++ b/README.md @@ -107,5 +107,5 @@ at your option. ## Code of Conduct -Contribution to the actix-web crate is organized under the terms of the Contributor Covenant, the -maintainers of Actix web, promises to intervene to uphold that code of conduct. +Contribution to the actix-web repo is organized under the terms of the Contributor Covenant. +The Actix team promises to intervene to uphold that code of conduct. From 3beb4cf2da01440c2b6454558dba70274305dd18 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 2 Jan 2021 07:18:25 +0800 Subject: [PATCH 67/81] replace tinyvec with smallvec (#1866) --- Cargo.toml | 2 +- src/app_service.rs | 2 -- src/request.rs | 18 ++++++++++++------ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6ed327f56..e33ff844b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,7 +107,7 @@ time = { version = "0.2.7", default-features = false, features = ["std"] } url = "2.1" open-ssl = { package = "openssl", version = "0.10", optional = true } rust-tls = { package = "rustls", version = "0.18.0", optional = true } -tinyvec = { version = "1", features = ["alloc"] } +smallvec = "1.6" [dev-dependencies] actix = "0.10.0" diff --git a/src/app_service.rs b/src/app_service.rs index e5f8dd9cf..4452778df 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -10,7 +10,6 @@ use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{fn_service, Service, ServiceFactory}; use futures_util::future::{join_all, ok, FutureExt, LocalBoxFuture}; -use tinyvec::tiny_vec; use crate::config::{AppConfig, AppService}; use crate::data::{DataFactory, FnDataFactory}; @@ -246,7 +245,6 @@ where inner.path.reset(); inner.head = head; inner.payload = payload; - inner.app_data = tiny_vec![self.data.clone()]; req } else { HttpRequest::new( diff --git a/src/request.rs b/src/request.rs index bd4bbbf58..432134cd7 100644 --- a/src/request.rs +++ b/src/request.rs @@ -6,7 +6,7 @@ use actix_http::http::{HeaderMap, Method, Uri, Version}; use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; use actix_router::{Path, Url}; use futures_util::future::{ok, Ready}; -use tinyvec::TinyVec; +use smallvec::SmallVec; use crate::config::AppConfig; use crate::error::UrlGenerationError; @@ -22,7 +22,7 @@ pub(crate) struct HttpRequestInner { pub(crate) head: Message, pub(crate) path: Path, pub(crate) payload: Payload, - pub(crate) app_data: TinyVec<[Rc; 4]>, + pub(crate) app_data: SmallVec<[Rc; 4]>, rmap: Rc, config: AppConfig, pool: &'static HttpRequestPool, @@ -39,7 +39,7 @@ impl HttpRequest { app_data: Rc, pool: &'static HttpRequestPool, ) -> HttpRequest { - let mut data = TinyVec::<[Rc; 4]>::new(); + let mut data = SmallVec::<[Rc; 4]>::new(); data.push(app_data); HttpRequest(Rc::new(HttpRequestInner { @@ -277,10 +277,16 @@ impl HttpMessage for HttpRequest { impl Drop for HttpRequest { fn drop(&mut self) { // if possible, contribute to current worker's HttpRequest allocation pool - if Rc::strong_count(&self.0) == 1 { - let v = &mut self.0.pool.0.borrow_mut(); + + // This relies on no Weak exists anywhere.(There is none) + if let Some(inner) = Rc::get_mut(&mut self.0) { + let v = &mut inner.pool.0.borrow_mut(); if v.len() < 128 { - self.extensions_mut().clear(); + // clear additional app_data and keep the root one for reuse. + inner.app_data.truncate(1); + // inner is borrowed mut here. get head's Extension mutably + // to reduce borrow check + inner.head.extensions.get_mut().clear(); v.push(self.0.clone()); } } From a1b00b2cd07362fa2e219c3256d4c0ca2eabb6b4 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jan 2021 00:12:18 +0000 Subject: [PATCH 68/81] change unreleased year --- CHANGES.md | 3 ++- actix-files/CHANGES.md | 2 +- actix-http-test/CHANGES.md | 2 +- actix-http/CHANGES.md | 3 ++- actix-multipart/CHANGES.md | 2 +- actix-web-actors/CHANGES.md | 3 ++- actix-web-codegen/CHANGES.md | 2 +- awc/CHANGES.md | 2 +- 8 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fa56acc17..2e886e0f5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2020-xx-xx +## Unreleased - 2021-xx-xx ### Changed * Bumped `rand` to `0.8` * Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] @@ -12,6 +12,7 @@ [#1812]: https://github.com/actix/actix-web/pull/1812 [#1852]: https://github.com/actix/actix-web/pull/1852 + ## 3.3.2 - 2020-12-01 ### Fixed * Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762] diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 49768419b..f992e5fdb 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2020-xx-xx +## Unreleased - 2021-xx-xx * `HttpRange::parse` now has its own error type. diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 835b75ddc..535766373 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2020-xx-xx +## Unreleased - 2021-xx-xx ## 2.1.0 - 2020-11-25 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c43246bb0..1d0cc3d31 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,9 +1,10 @@ # Changes -## Unreleased - 2020-xx-xx +## Unreleased - 2021-xx-xx ### Changed * Bumped `rand` to `0.8` + ### Removed * Deprecated `on_connect` methods have been removed. Prefer the new `on_connect_ext` technique. [#1857] diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 446ca5ad2..0d13d5015 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2020-xx-xx +## Unreleased - 2021-xx-xx * Fix multipart consuming payload before header checks #1513 diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 9df0df159..0d830fe64 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,8 +1,9 @@ # Changes -## Unreleased - 2020-xx-xx +## Unreleased - 2021-xx-xx * Upgrade `pin-project` to `1.0`. + ## 3.0.0 - 2020-09-11 * No significant changes from `3.0.0-beta.2`. diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 1ab51f924..a7675d9dd 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2020-xx-xx +## Unreleased - 2021-xx-xx ## 0.4.0 - 2020-09-20 diff --git a/awc/CHANGES.md b/awc/CHANGES.md index e4f801bbe..ee795fccb 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2020-xx-xx +## Unreleased - 2021-xx-xx ### Changed * Bumped `rand` to `0.8` From ad608aa64ef56c048c0b691ebe379224747a6902 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 3 Jan 2021 03:40:31 +0800 Subject: [PATCH 69/81] optimize Resource and Scope service call (#1867) --- src/resource.rs | 21 ++++++++++----------- src/scope.rs | 13 +++++++------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/resource.rs b/src/resource.rs index f6046d652..29a7daa78 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -11,7 +11,7 @@ use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform, }; -use futures_util::future::{ok, Either, LocalBoxFuture, Ready}; +use futures_core::future::LocalBoxFuture; use crate::data::Data; use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}; @@ -524,10 +524,7 @@ impl Service for ResourceService { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Either< - Ready>, - LocalBoxFuture<'static, Result>, - >; + type Future = LocalBoxFuture<'static, Result>; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) @@ -539,20 +536,22 @@ impl Service for ResourceService { if let Some(ref data) = self.data { req.add_data_container(data.clone()); } - return Either::Right(route.call(req)); + return route.call(req); } } if let Some(ref mut default) = self.default { if let Some(ref data) = self.data { req.add_data_container(data.clone()); } - Either::Right(default.call(req)) + default.call(req) } else { let req = req.into_parts().0; - Either::Left(ok(ServiceResponse::new( - req, - Response::MethodNotAllowed().finish(), - ))) + Box::pin(async { + Ok(ServiceResponse::new( + req, + Response::MethodNotAllowed().finish(), + )) + }) } } } diff --git a/src/scope.rs b/src/scope.rs index 681d142be..ce8d94159 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -11,7 +11,7 @@ use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform, }; -use futures_util::future::{ok, Either, LocalBoxFuture, Ready}; +use futures_core::future::LocalBoxFuture; use crate::config::ServiceConfig; use crate::data::Data; @@ -28,7 +28,6 @@ use crate::service::{ type Guards = Vec>; type HttpService = BoxService; type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; -type BoxedResponse = LocalBoxFuture<'static, Result>; /// Resources scope. /// @@ -606,7 +605,7 @@ impl Service for ScopeService { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Either>>; + type Future = LocalBoxFuture<'static, Result>; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) @@ -628,15 +627,17 @@ impl Service for ScopeService { if let Some(ref data) = self.data { req.add_data_container(data.clone()); } - Either::Left(srv.call(req)) + srv.call(req) } else if let Some(ref mut default) = self.default { if let Some(ref data) = self.data { req.add_data_container(data.clone()); } - Either::Left(default.call(req)) + default.call(req) } else { let req = req.into_parts().0; - Either::Right(ok(ServiceResponse::new(req, Response::NotFound().finish()))) + Box::pin(async { + Ok(ServiceResponse::new(req, Response::NotFound().finish())) + }) } } } From 1f202d40e48120b413e191df36431b45a9b0fccd Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 3 Jan 2021 16:53:01 +0000 Subject: [PATCH 70/81] optimize write_camel_case in h1 encoder (#1868) --- actix-http/Cargo.toml | 2 +- actix-http/benches/content-length.rs | 291 ------------------------- actix-http/benches/write-camel-case.rs | 89 ++++++++ actix-http/src/h1/encoder.rs | 39 ++-- 4 files changed, 110 insertions(+), 311 deletions(-) delete mode 100644 actix-http/benches/content-length.rs create mode 100644 actix-http/benches/write-camel-case.rs diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 7cf344487..50690537a 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -97,7 +97,7 @@ open-ssl = { version="0.10", package = "openssl" } rust-tls = { version="0.18", package = "rustls" } [[bench]] -name = "content-length" +name = "write-camel-case" harness = false [[bench]] diff --git a/actix-http/benches/content-length.rs b/actix-http/benches/content-length.rs deleted file mode 100644 index 18a55a33b..000000000 --- a/actix-http/benches/content-length.rs +++ /dev/null @@ -1,291 +0,0 @@ -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; - -use bytes::BytesMut; - -// benchmark sending all requests at the same time -fn bench_write_content_length(c: &mut Criterion) { - let mut group = c.benchmark_group("write_content_length"); - - let sizes = [ - 0, 1, 11, 83, 101, 653, 1001, 6323, 10001, 56329, 100001, 123456, 98724245, - 4294967202, - ]; - - for i in sizes.iter() { - group.bench_with_input(BenchmarkId::new("Original (unsafe)", i), i, |b, &i| { - b.iter(|| { - let mut b = BytesMut::with_capacity(35); - _original::write_content_length(i, &mut b) - }) - }); - - group.bench_with_input(BenchmarkId::new("New (safe)", i), i, |b, &i| { - b.iter(|| { - let mut b = BytesMut::with_capacity(35); - _new::write_content_length(i, &mut b) - }) - }); - - group.bench_with_input(BenchmarkId::new("itoa", i), i, |b, &i| { - b.iter(|| { - let mut b = BytesMut::with_capacity(35); - _itoa::write_content_length(i, &mut b) - }) - }); - } - - group.finish(); -} - -criterion_group!(benches, bench_write_content_length); -criterion_main!(benches); - -mod _itoa { - use bytes::{BufMut, BytesMut}; - - pub fn write_content_length(n: usize, bytes: &mut BytesMut) { - if n == 0 { - bytes.put_slice(b"\r\ncontent-length: 0\r\n"); - return; - } - - let mut buf = itoa::Buffer::new(); - - bytes.put_slice(b"\r\ncontent-length: "); - bytes.put_slice(buf.format(n).as_bytes()); - bytes.put_slice(b"\r\n"); - } -} - -mod _new { - use bytes::{BufMut, BytesMut}; - - const DIGITS_START: u8 = b'0'; - - /// NOTE: bytes object has to contain enough space - pub fn write_content_length(n: usize, bytes: &mut BytesMut) { - if n == 0 { - bytes.put_slice(b"\r\ncontent-length: 0\r\n"); - return; - } - - bytes.put_slice(b"\r\ncontent-length: "); - - if n < 10 { - bytes.put_u8(DIGITS_START + (n as u8)); - } else if n < 100 { - let n = n as u8; - - let d10 = n / 10; - let d1 = n % 10; - - bytes.put_u8(DIGITS_START + d10); - bytes.put_u8(DIGITS_START + d1); - } else if n < 1000 { - let n = n as u16; - - let d100 = (n / 100) as u8; - let d10 = ((n / 10) % 10) as u8; - let d1 = (n % 10) as u8; - - bytes.put_u8(DIGITS_START + d100); - bytes.put_u8(DIGITS_START + d10); - bytes.put_u8(DIGITS_START + d1); - } else if n < 10_000 { - let n = n as u16; - - let d1000 = (n / 1000) as u8; - let d100 = ((n / 100) % 10) as u8; - let d10 = ((n / 10) % 10) as u8; - let d1 = (n % 10) as u8; - - bytes.put_u8(DIGITS_START + d1000); - bytes.put_u8(DIGITS_START + d100); - bytes.put_u8(DIGITS_START + d10); - bytes.put_u8(DIGITS_START + d1); - } else if n < 100_000 { - let n = n as u32; - - let d10000 = (n / 10000) as u8; - let d1000 = ((n / 1000) % 10) as u8; - let d100 = ((n / 100) % 10) as u8; - let d10 = ((n / 10) % 10) as u8; - let d1 = (n % 10) as u8; - - bytes.put_u8(DIGITS_START + d10000); - bytes.put_u8(DIGITS_START + d1000); - bytes.put_u8(DIGITS_START + d100); - bytes.put_u8(DIGITS_START + d10); - bytes.put_u8(DIGITS_START + d1); - } else if n < 1_000_000 { - let n = n as u32; - - let d100000 = (n / 100000) as u8; - let d10000 = ((n / 10000) % 10) as u8; - let d1000 = ((n / 1000) % 10) as u8; - let d100 = ((n / 100) % 10) as u8; - let d10 = ((n / 10) % 10) as u8; - let d1 = (n % 10) as u8; - - bytes.put_u8(DIGITS_START + d100000); - bytes.put_u8(DIGITS_START + d10000); - bytes.put_u8(DIGITS_START + d1000); - bytes.put_u8(DIGITS_START + d100); - bytes.put_u8(DIGITS_START + d10); - bytes.put_u8(DIGITS_START + d1); - } else { - write_usize(n, bytes); - } - - bytes.put_slice(b"\r\n"); - } - - fn write_usize(n: usize, bytes: &mut BytesMut) { - let mut n = n; - - // 20 chars is max length of a usize (2^64) - // digits will be added to the buffer from lsd to msd - let mut buf = BytesMut::with_capacity(20); - - while n > 9 { - // "pop" the least-significant digit - let lsd = (n % 10) as u8; - - // remove the lsd from n - n = n / 10; - - buf.put_u8(DIGITS_START + lsd); - } - - // put msd to result buffer - bytes.put_u8(DIGITS_START + (n as u8)); - - // put, in reverse (msd to lsd), remaining digits to buffer - for i in (0..buf.len()).rev() { - bytes.put_u8(buf[i]); - } - } -} - -mod _original { - use std::{mem, ptr, slice}; - - use bytes::{BufMut, BytesMut}; - - const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\ - 2021222324252627282930313233343536373839\ - 4041424344454647484950515253545556575859\ - 6061626364656667686970717273747576777879\ - 8081828384858687888990919293949596979899"; - - /// NOTE: bytes object has to contain enough space - pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) { - if n < 10 { - let mut buf: [u8; 21] = [ - b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', - b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'\r', b'\n', - ]; - buf[18] = (n as u8) + b'0'; - bytes.put_slice(&buf); - } else if n < 100 { - let mut buf: [u8; 22] = [ - b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', - b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'0', b'\r', b'\n', - ]; - let d1 = n << 1; - unsafe { - ptr::copy_nonoverlapping( - DEC_DIGITS_LUT.as_ptr().add(d1), - buf.as_mut_ptr().offset(18), - 2, - ); - } - bytes.put_slice(&buf); - } else if n < 1000 { - let mut buf: [u8; 23] = [ - b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', - b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'0', b'0', b'\r', - b'\n', - ]; - // decode 2 more chars, if > 2 chars - let d1 = (n % 100) << 1; - n /= 100; - unsafe { - ptr::copy_nonoverlapping( - DEC_DIGITS_LUT.as_ptr().add(d1), - buf.as_mut_ptr().offset(19), - 2, - ) - }; - - // decode last 1 - buf[18] = (n as u8) + b'0'; - - bytes.put_slice(&buf); - } else { - bytes.put_slice(b"\r\ncontent-length: "); - convert_usize(n, bytes); - } - } - - pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { - let mut curr: isize = 39; - let mut buf: [u8; 41] = unsafe { mem::MaybeUninit::uninit().assume_init() }; - buf[39] = b'\r'; - buf[40] = b'\n'; - let buf_ptr = buf.as_mut_ptr(); - let lut_ptr = DEC_DIGITS_LUT.as_ptr(); - - // eagerly decode 4 characters at a time - while n >= 10_000 { - let rem = (n % 10_000) as isize; - n /= 10_000; - - let d1 = (rem / 100) << 1; - let d2 = (rem % 100) << 1; - curr -= 4; - unsafe { - ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); - ptr::copy_nonoverlapping( - lut_ptr.offset(d2), - buf_ptr.offset(curr + 2), - 2, - ); - } - } - - // if we reach here numbers are <= 9999, so at most 4 chars long - let mut n = n as isize; // possibly reduce 64bit math - - // decode 2 more chars, if > 2 chars - if n >= 100 { - let d1 = (n % 100) << 1; - n /= 100; - curr -= 2; - unsafe { - ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); - } - } - - // decode last 1 or 2 chars - if n < 10 { - curr -= 1; - unsafe { - *buf_ptr.offset(curr) = (n as u8) + b'0'; - } - } else { - let d1 = n << 1; - curr -= 2; - unsafe { - ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); - } - } - - unsafe { - bytes.extend_from_slice(slice::from_raw_parts( - buf_ptr.offset(curr), - 41 - curr as usize, - )); - } - } -} diff --git a/actix-http/benches/write-camel-case.rs b/actix-http/benches/write-camel-case.rs new file mode 100644 index 000000000..fa4930eb9 --- /dev/null +++ b/actix-http/benches/write-camel-case.rs @@ -0,0 +1,89 @@ +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; + +fn bench_write_camel_case(c: &mut Criterion) { + let mut group = c.benchmark_group("write_camel_case"); + + let names = ["connection", "Transfer-Encoding", "transfer-encoding"]; + + for &i in &names { + let bts = i.as_bytes(); + + group.bench_with_input(BenchmarkId::new("Original", i), bts, |b, bts| { + b.iter(|| { + let mut buf = black_box([0; 24]); + _original::write_camel_case(black_box(bts), &mut buf) + }); + }); + + group.bench_with_input(BenchmarkId::new("New", i), bts, |b, bts| { + b.iter(|| { + let mut buf = black_box([0; 24]); + _new::write_camel_case(black_box(bts), &mut buf) + }); + }); + } + + group.finish(); +} + +criterion_group!(benches, bench_write_camel_case); +criterion_main!(benches); + +mod _new { + pub fn write_camel_case(value: &[u8], buffer: &mut [u8]) { + // first copy entire (potentially wrong) slice to output + buffer[..value.len()].copy_from_slice(value); + + let mut iter = value.iter(); + + // first character should be uppercase + if let Some(c @ b'a'..=b'z') = iter.next() { + buffer[0] = c & 0b1101_1111; + } + + // track 1 ahead of the current position since that's the location being assigned to + let mut index = 2; + + // remaining characters after hyphens should also be uppercase + while let Some(&c) = iter.next() { + if c == b'-' { + // advance iter by one and uppercase if needed + if let Some(c @ b'a'..=b'z') = iter.next() { + buffer[index] = c & 0b1101_1111; + } + } + + index += 1; + } + } +} + +mod _original { + pub fn write_camel_case(value: &[u8], buffer: &mut [u8]) { + let mut index = 0; + let key = value; + let mut key_iter = key.iter(); + + if let Some(c) = key_iter.next() { + if *c >= b'a' && *c <= b'z' { + buffer[index] = *c ^ b' '; + index += 1; + } + } else { + return; + } + + while let Some(c) = key_iter.next() { + buffer[index] = *c; + index += 1; + if *c == b'-' { + if let Some(c) = key_iter.next() { + if *c >= b'a' && *c <= b'z' { + buffer[index] = *c ^ b' '; + index += 1; + } + } + } + } + } +} diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index b7648eff1..2ec4899a7 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -532,30 +532,29 @@ unsafe fn write_data(value: &[u8], buf: *mut u8, len: usize) { } fn write_camel_case(value: &[u8], buffer: &mut [u8]) { - let mut index = 0; - let key = value; - let mut key_iter = key.iter(); + // first copy entire (potentially wrong) slice to output + buffer[..value.len()].copy_from_slice(value); - if let Some(c) = key_iter.next() { - if *c >= b'a' && *c <= b'z' { - buffer[index] = *c ^ b' '; - index += 1; - } - } else { - return; + let mut iter = value.iter(); + + // first character should be uppercase + if let Some(c @ b'a'..=b'z') = iter.next() { + buffer[0] = c & 0b1101_1111; } - while let Some(c) = key_iter.next() { - buffer[index] = *c; - index += 1; - if *c == b'-' { - if let Some(c) = key_iter.next() { - if *c >= b'a' && *c <= b'z' { - buffer[index] = *c ^ b' '; - index += 1; - } + // track 1 ahead of the current position since that's the location being assigned to + let mut index = 2; + + // remaining characters after hyphens should also be uppercase + while let Some(&c) = iter.next() { + if c == b'-' { + // advance iter by one and uppercase if needed + if let Some(c @ b'a'..=b'z') = iter.next() { + buffer[index] = c & 0b1101_1111; } } + + index += 1; } } @@ -604,6 +603,8 @@ mod tests { ); let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); + eprintln!("{}", &data); + assert!(data.contains("Content-Length: 0\r\n")); assert!(data.contains("Connection: close\r\n")); assert!(data.contains("Content-Type: plain/text\r\n")); From 32de9f8840a39d42f070a1a87b26ed3d32a80fb5 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 4 Jan 2021 07:47:04 +0800 Subject: [PATCH 71/81] Tokio 1.0 (#1813) Co-authored-by: Rob Ede --- CHANGES.md | 7 +- Cargo.toml | 32 ++-- actix-files/CHANGES.md | 3 + actix-files/Cargo.toml | 6 +- actix-files/src/files.rs | 9 +- actix-files/src/service.rs | 3 +- actix-http-test/CHANGES.md | 3 + actix-http-test/Cargo.toml | 19 ++- actix-http-test/src/lib.rs | 15 +- actix-http/CHANGES.md | 14 +- actix-http/Cargo.toml | 36 ++--- actix-http/src/builder.rs | 48 +++--- actix-http/src/client/connection.rs | 34 +--- actix-http/src/client/connector.rs | 87 +++++----- actix-http/src/client/error.rs | 30 ++-- actix-http/src/client/h1proto.rs | 17 +- actix-http/src/client/pool.rs | 32 ++-- actix-http/src/cloneable.rs | 11 +- actix-http/src/config.rs | 12 +- actix-http/src/error.rs | 11 +- actix-http/src/h1/dispatcher.rs | 108 +++++++------ actix-http/src/h1/encoder.rs | 6 +- actix-http/src/h1/expect.rs | 8 +- actix-http/src/h1/service.rs | 107 +++++++------ actix-http/src/h1/upgrade.rs | 15 +- actix-http/src/h2/dispatcher.rs | 14 +- actix-http/src/h2/service.rs | 81 +++++----- actix-http/src/header/shared/httpdate.rs | 3 +- actix-http/src/request.rs | 8 +- actix-http/src/service.rs | 193 ++++++++++++----------- actix-http/src/test.rs | 22 ++- actix-http/src/ws/dispatcher.rs | 10 +- actix-http/tests/test_client.rs | 7 +- actix-http/tests/test_openssl.rs | 14 +- actix-http/tests/test_server.rs | 4 +- actix-http/tests/test_ws.rs | 8 +- actix-multipart/CHANGES.md | 3 + actix-multipart/Cargo.toml | 10 +- actix-web-actors/CHANGES.md | 4 +- actix-web-actors/Cargo.toml | 17 +- actix-web-actors/src/context.rs | 2 +- actix-web-actors/src/ws.rs | 2 +- actix-web-codegen/Cargo.toml | 4 +- actix-web-codegen/tests/test_macro.rs | 12 +- awc/CHANGES.md | 6 +- awc/Cargo.toml | 24 +-- awc/src/builder.rs | 2 +- awc/src/connect.rs | 17 +- awc/src/sender.rs | 7 +- awc/tests/test_client.rs | 8 +- awc/tests/test_connector.rs | 2 +- awc/tests/test_rustls_client.rs | 2 +- awc/tests/test_ssl_client.rs | 2 +- benches/server.rs | 14 +- benches/service.rs | 25 +-- src/app.rs | 21 +-- src/app_service.rs | 33 ++-- src/config.rs | 4 +- src/handler.rs | 10 +- src/middleware/compress.rs | 16 +- src/middleware/condition.rs | 18 +-- src/middleware/defaultheaders.rs | 14 +- src/middleware/errhandlers.rs | 12 +- src/middleware/logger.rs | 16 +- src/middleware/normalize.rs | 12 +- src/resource.rs | 38 +++-- src/route.rs | 38 ++--- src/scope.rs | 30 ++-- src/server.rs | 31 ++-- src/service.rs | 6 +- src/test.rs | 54 +++---- src/types/json.rs | 2 +- src/web.rs | 1 - tests/test_httpserver.rs | 51 +++--- tests/test_server.rs | 7 +- 75 files changed, 788 insertions(+), 826 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2e886e0f5..32f444ec1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,10 +2,15 @@ ## Unreleased - 2021-xx-xx ### Changed -* Bumped `rand` to `0.8` +* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +* Bumped `rand` to `0.8`. +* Update `rust-tls` to `0.19`. [#1813] * Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] * MSRV is now 1.46.0. +[#1813]: https://github.com/actix/actix-web/pull/1813 + + ### Fixed * added the actual parsing error to `test::read_body_json` [#1812] diff --git a/Cargo.toml b/Cargo.toml index e33ff844b..165004447 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,10 +47,10 @@ compress = ["actix-http/compress", "awc/compress"] secure-cookies = ["actix-http/secure-cookies"] # openssl -openssl = ["actix-tls/openssl", "awc/openssl", "open-ssl"] +openssl = ["actix-tls/accept", "actix-tls/openssl", "awc/openssl", "open-ssl"] # rustls -rustls = ["actix-tls/rustls", "awc/rustls", "rust-tls"] +rustls = ["actix-tls/accept", "actix-tls/rustls", "awc/rustls", "rust-tls"] [[example]] name = "basic" @@ -73,27 +73,25 @@ name = "client" required-features = ["rustls"] [dependencies] -actix-codec = "0.3.0" -actix-service = "1.0.6" -actix-utils = "2.0.0" -actix-router = "0.2.4" -actix-rt = "1.1.1" -actix-server = "1.0.0" -actix-testing = "1.0.0" +actix-codec = "0.4.0-beta.1" actix-macros = "0.1.0" +actix-router = "0.2.4" +actix-rt = "2.0.0-beta.1" +actix-server = "2.0.0-beta.2" +actix-service = "2.0.0-beta.2" +actix-utils = "3.0.0-beta.1" actix-threadpool = "0.3.1" -actix-tls = "2.0.0" +actix-tls = { version = "3.0.0-beta.2", default-features = false, optional = true } actix-web-codegen = "0.4.0" actix-http = "2.2.0" awc = { version = "2.0.3", default-features = false } -bytes = "0.5.3" +bytes = "1" derive_more = "0.99.5" encoding_rs = "0.8" -futures-channel = { version = "0.3.5", default-features = false } -futures-core = { version = "0.3.5", default-features = false } -futures-util = { version = "0.3.5", default-features = false } +futures-core = { version = "0.3.7", default-features = false } +futures-util = { version = "0.3.7", default-features = false } fxhash = "0.2.1" log = "0.4" mime = "0.3" @@ -106,12 +104,12 @@ serde_urlencoded = "0.7" time = { version = "0.2.7", default-features = false, features = ["std"] } url = "2.1" open-ssl = { package = "openssl", version = "0.10", optional = true } -rust-tls = { package = "rustls", version = "0.18.0", optional = true } +rust-tls = { package = "rustls", version = "0.19.0", optional = true } smallvec = "1.6" [dev-dependencies] -actix = "0.10.0" -actix-http = { version = "2.1.0", features = ["actors"] } +actix = "0.11.0-beta.1" +actix-http = { version = "2.2.0", features = ["actors"] } rand = "0.8" env_logger = "0.8" serde_derive = "1.0" diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index f992e5fdb..6dcf4f66f 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -2,6 +2,9 @@ ## Unreleased - 2021-xx-xx * `HttpRange::parse` now has its own error type. +* Update `bytes` to `1.0`. [#1813] + +[#1813]: https://github.com/actix/actix-web/pull/1813 ## 0.5.0 - 2020-12-26 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index b67abb1c1..17e1a4888 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -18,9 +18,9 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "3.0.0", default-features = false } -actix-service = "1.0.6" +actix-service = "2.0.0-beta.2" bitflags = "1" -bytes = "0.5.3" +bytes = "1" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } derive_more = "0.99.5" @@ -31,5 +31,5 @@ percent-encoding = "2.1" v_htmlescape = "0.12" [dev-dependencies] -actix-rt = "1.0.0" +actix-rt = "2.0.0-beta.1" actix-web = "3.0.0" diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index d0cac6aa4..98dd26880 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, fmt, io, path::PathBuf, rc::Rc}; -use actix_service::{boxed, IntoServiceFactory, ServiceFactory}; +use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt}; use actix_web::{ dev::{ AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse, @@ -201,10 +201,10 @@ impl Files { /// Sets default handler which is used when no matched file could be found. pub fn default_handler(mut self, f: F) -> Self where - F: IntoServiceFactory, + F: IntoServiceFactory, U: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, > + 'static, @@ -241,8 +241,7 @@ impl HttpServiceFactory for Files { } } -impl ServiceFactory for Files { - type Request = ServiceRequest; +impl ServiceFactory for Files { type Response = ServiceResponse; type Error = Error; type Config = (); diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index dc4f2bd2c..1e3d64a0d 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -57,8 +57,7 @@ impl fmt::Debug for FilesService { } } -impl Service for FilesService { - type Request = ServiceRequest; +impl Service for FilesService { type Response = ServiceResponse; type Error = Error; type Future = FilesServiceFuture; diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 535766373..6ed6a0603 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* Update `bytes` to `1.0`. [#1813] + +[#1813]: https://github.com/actix/actix-web/pull/1813 ## 2.1.0 - 2020-11-25 diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 8b23bef1c..910fbab73 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -29,19 +29,18 @@ default = [] openssl = ["open-ssl", "awc/openssl"] [dependencies] -actix-service = "1.0.6" -actix-codec = "0.3.0" -actix-connect = "2.0.0" -actix-utils = "2.0.0" -actix-rt = "1.1.1" -actix-server = "1.0.0" -actix-testing = "1.0.0" +actix-service = "2.0.0-beta.2" +actix-codec = "0.4.0-beta.1" +actix-tls = "3.0.0-beta.2" +actix-utils = "3.0.0-beta.1" +actix-rt = "2.0.0-beta.1" +actix-server = "2.0.0-beta.2" awc = "2.0.0" base64 = "0.13" -bytes = "0.5.3" -futures-core = { version = "0.3.5", default-features = false } -http = "0.2.0" +bytes = "1" +futures-core = { version = "0.3.7", default-features = false } +http = "0.2.2" log = "0.4" socket2 = "0.3" serde = "1.0" diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 3ab3f8a0d..4fd74d6eb 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -16,8 +16,6 @@ use futures_core::stream::Stream; use http::Method; use socket2::{Domain, Protocol, Socket, Type}; -pub use actix_testing::*; - /// Start test server /// /// `TestServer` is very simple test server that simplify process of writing @@ -65,13 +63,16 @@ pub async fn test_server_with_addr>( let sys = System::new("actix-test-server"); let local_addr = tcp.local_addr().unwrap(); - Server::build() + let srv = Server::build() .listen("test", tcp, factory)? .workers(1) - .disable_signals() - .start(); + .disable_signals(); + + sys.block_on(async { + srv.start(); + tx.send((System::current(), local_addr)).unwrap(); + }); - tx.send((System::current(), local_addr)).unwrap(); sys.run() }); @@ -105,7 +106,7 @@ pub async fn test_server_with_addr>( Client::builder().connector(connector).finish() }; - actix_connect::start_default_resolver().await.unwrap(); + actix_tls::connect::start_default_resolver().await.unwrap(); TestServer { addr, diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 1d0cc3d31..147285ddf 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,13 +2,25 @@ ## Unreleased - 2021-xx-xx ### Changed -* Bumped `rand` to `0.8` +* Bumped `rand` to `0.8`. +* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +* Update `bytes` to `1.0`. [#1813] +* Update `h2` to `0.3`. [#1813] + + +[#1813]: https://github.com/actix/actix-web/pull/1813 ### Removed * Deprecated `on_connect` methods have been removed. Prefer the new `on_connect_ext` technique. [#1857] +* Remove `ResponseError` impl for `actix::actors::resolver::ResolverError` + due to deprecate of resolver actor. [#1813] +* Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. + due to the removal of this type from `tokio-openssl` crate. openssl handshake + error would return as `ConnectError::SslError`. [#1813] +[#1813]: https://github.com/actix/actix-web/pull/1813 [#1857]: https://github.com/actix/actix-web/pull/1857 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 50690537a..e98bcf76d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -25,10 +25,10 @@ path = "src/lib.rs" default = [] # openssl -openssl = ["actix-tls/openssl", "actix-connect/openssl"] +openssl = ["actix-tls/openssl"] # rustls support -rustls = ["actix-tls/rustls", "actix-connect/rustls"] +rustls = ["actix-tls/rustls"] # enable compressison support compress = ["flate2", "brotli2"] @@ -40,29 +40,28 @@ secure-cookies = ["cookie/secure"] actors = ["actix"] [dependencies] -actix-service = "1.0.6" -actix-codec = "0.3.0" -actix-connect = "2.0.0" -actix-utils = "2.0.0" -actix-rt = "1.0.0" +actix-service = "2.0.0-beta.2" +actix-codec = "0.4.0-beta.1" +actix-utils = "3.0.0-beta.1" +actix-rt = "2.0.0-beta.1" actix-threadpool = "0.3.1" -actix-tls = { version = "2.0.0", optional = true } -actix = { version = "0.10.0", optional = true } +actix-tls = "3.0.0-beta.2" +actix = { version = "0.11.0-beta.1", optional = true } base64 = "0.13" bitflags = "1.2" -bytes = "0.5.3" +bytes = "1" cookie = { version = "0.14.1", features = ["percent-encode"] } copyless = "0.1.4" derive_more = "0.99.2" either = "1.5.3" encoding_rs = "0.8" -futures-channel = { version = "0.3.5", default-features = false } -futures-core = { version = "0.3.5", default-features = false } -futures-util = { version = "0.3.5", default-features = false } +futures-channel = { version = "0.3.7", default-features = false } +futures-core = { version = "0.3.7", default-features = false } +futures-util = { version = "0.3.7", default-features = false, features = ["sink"] } fxhash = "0.2.1" -h2 = "0.2.1" -http = "0.2.0" +h2 = "0.3.0" +http = "0.2.2" httparse = "1.3" indexmap = "1.3" itoa = "0.4" @@ -86,15 +85,14 @@ brotli2 = { version="0.3.2", optional = true } flate2 = { version = "1.0.13", optional = true } [dev-dependencies] -actix-server = "1.0.1" -actix-connect = { version = "2.0.0", features = ["openssl"] } +actix-server = "2.0.0-beta.2" actix-http-test = { version = "2.0.0", features = ["openssl"] } -actix-tls = { version = "2.0.0", features = ["openssl"] } +actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] } criterion = "0.3" env_logger = "0.7" serde_derive = "1.0" open-ssl = { version="0.10", package = "openssl" } -rust-tls = { version="0.18", package = "rustls" } +rust-tls = { version="0.19", package = "rustls" } [[bench]] name = "write-camel-case" diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index df1b332c1..ecb4327df 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -19,7 +19,7 @@ use crate::{ConnectCallback, Extensions}; /// /// This type can be used to construct an instance of [`HttpService`] through a /// builder-like pattern. -pub struct HttpServiceBuilder> { +pub struct HttpServiceBuilder { keep_alive: KeepAlive, client_timeout: u64, client_disconnect: u64, @@ -28,15 +28,15 @@ pub struct HttpServiceBuilder> { expect: X, upgrade: Option, on_connect_ext: Option>>, - _t: PhantomData<(T, S)>, + _t: PhantomData, } -impl HttpServiceBuilder> +impl HttpServiceBuilder where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, - ::Future: 'static, + >::Future: 'static, { /// Create instance of `ServiceConfigBuilder` pub fn new() -> Self { @@ -56,18 +56,18 @@ where impl HttpServiceBuilder where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, - ::Future: 'static, - X: ServiceFactory, + >::Future: 'static, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - ::Future: 'static, - U: ServiceFactory), Response = ()>, + >::Future: 'static, + U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, - ::Future: 'static, + )>>::Future: 'static, { /// Set server keep-alive setting. /// @@ -123,11 +123,11 @@ where /// request will be forwarded to main service. pub fn expect(self, expect: F) -> HttpServiceBuilder where - F: IntoServiceFactory, - X1: ServiceFactory, + F: IntoServiceFactory, + X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, - ::Future: 'static, + >::Future: 'static, { HttpServiceBuilder { keep_alive: self.keep_alive, @@ -148,15 +148,11 @@ where /// and this service get called with original request and framed object. pub fn upgrade(self, upgrade: F) -> HttpServiceBuilder where - F: IntoServiceFactory, - U1: ServiceFactory< - Config = (), - Request = (Request, Framed), - Response = (), - >, + F: IntoServiceFactory)>, + U1: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U1::Error: fmt::Display, U1::InitError: fmt::Debug, - ::Future: 'static, + )>>::Future: 'static, { HttpServiceBuilder { keep_alive: self.keep_alive, @@ -188,7 +184,7 @@ where pub fn h1(self, service: F) -> H1Service where B: MessageBody, - F: IntoServiceFactory, + F: IntoServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, @@ -211,11 +207,11 @@ where pub fn h2(self, service: F) -> H2Service where B: MessageBody + 'static, - F: IntoServiceFactory, + F: IntoServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, { let cfg = ServiceConfig::new( self.keep_alive, @@ -233,11 +229,11 @@ where pub fn finish(self, service: F) -> HttpService where B: MessageBody + 'static, - F: IntoServiceFactory, + F: IntoServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, { let cfg = ServiceConfig::new( self.keep_alive, diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index ec86dabb0..d22f2c7ac 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -1,10 +1,10 @@ use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; -use std::{fmt, io, mem, time}; +use std::{fmt, io, time}; -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use bytes::{Buf, Bytes}; +use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; +use bytes::Bytes; use futures_util::future::{err, Either, FutureExt, LocalBoxFuture, Ready}; use h2::client::SendRequest; use pin_project::pin_project; @@ -223,23 +223,13 @@ where fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { + buf: &mut ReadBuf<'_>, + ) -> Poll> { match self.project() { EitherIoProj::A(val) => val.poll_read(cx, buf), EitherIoProj::B(val) => val.poll_read(cx, buf), } } - - unsafe fn prepare_uninitialized_buffer( - &self, - buf: &mut [mem::MaybeUninit], - ) -> bool { - match self { - EitherIo::A(ref val) => val.prepare_uninitialized_buffer(buf), - EitherIo::B(ref val) => val.prepare_uninitialized_buffer(buf), - } - } } impl AsyncWrite for EitherIo @@ -274,18 +264,4 @@ where EitherIoProj::B(val) => val.poll_shutdown(cx), } } - - fn poll_write_buf( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut U, - ) -> Poll> - where - Self: Sized, - { - match self.project() { - EitherIoProj::A(val) => val.poll_write_buf(cx, buf), - EitherIoProj::B(val) => val.poll_write_buf(cx, buf), - } - } } diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index e1aed6382..b638336f7 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -3,11 +3,11 @@ use std::marker::PhantomData; use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_connect::{ +use actix_rt::net::TcpStream; +use actix_service::{apply_fn, Service, ServiceExt}; +use actix_tls::connect::{ default_connector, Connect as TcpConnect, Connection as TcpConnection, }; -use actix_rt::net::TcpStream; -use actix_service::{apply_fn, Service}; use actix_utils::timeout::{TimeoutError, TimeoutService}; use http::Uri; @@ -18,10 +18,10 @@ use super::pool::{ConnectionPool, Protocol}; use super::Connect; #[cfg(feature = "openssl")] -use actix_connect::ssl::openssl::SslConnector as OpensslConnector; +use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector; #[cfg(feature = "rustls")] -use actix_connect::ssl::rustls::ClientConfig; +use actix_tls::connect::ssl::rustls::ClientConfig; #[cfg(feature = "rustls")] use std::sync::Arc; @@ -62,9 +62,9 @@ impl Connector<(), ()> { #[allow(clippy::new_ret_no_self, clippy::let_unit_value)] pub fn new() -> Connector< impl Service< - Request = TcpConnect, + TcpConnect, Response = TcpConnection, - Error = actix_connect::ConnectError, + Error = actix_tls::connect::ConnectError, > + Clone, TcpStream, > { @@ -79,7 +79,7 @@ impl Connector<(), ()> { // Build Ssl connector with openssl, based on supplied alpn protocols #[cfg(feature = "openssl")] fn build_ssl(protocols: Vec>) -> SslConnector { - use actix_connect::ssl::openssl::SslMethod; + use actix_tls::connect::ssl::openssl::SslMethod; use bytes::{BufMut, BytesMut}; let mut alpn = BytesMut::with_capacity(20); @@ -102,7 +102,7 @@ impl Connector<(), ()> { config.set_protocols(&protocols); config .root_store - .add_server_trust_anchors(&actix_tls::rustls::TLS_SERVER_ROOTS); + .add_server_trust_anchors(&actix_tls::accept::rustls::TLS_SERVER_ROOTS); SslConnector::Rustls(Arc::new(config)) } @@ -117,9 +117,9 @@ impl Connector { where U1: AsyncRead + AsyncWrite + Unpin + fmt::Debug, T1: Service< - Request = TcpConnect, + TcpConnect, Response = TcpConnection, - Error = actix_connect::ConnectError, + Error = actix_tls::connect::ConnectError, > + Clone, { Connector { @@ -135,9 +135,9 @@ impl Connector where U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, T: Service< - Request = TcpConnect, + TcpConnect, Response = TcpConnection, - Error = actix_connect::ConnectError, + Error = actix_tls::connect::ConnectError, > + Clone + 'static, { @@ -241,8 +241,8 @@ where /// its combinator chain. pub fn finish( self, - ) -> impl Service - + Clone { + ) -> impl Service + Clone + { #[cfg(not(any(feature = "openssl", feature = "rustls")))] { let connector = TimeoutService::new( @@ -268,11 +268,11 @@ where #[cfg(any(feature = "openssl", feature = "rustls"))] { const H2: &[u8] = b"h2"; - #[cfg(feature = "openssl")] - use actix_connect::ssl::openssl::OpensslConnector; - #[cfg(feature = "rustls")] - use actix_connect::ssl::rustls::{RustlsConnector, Session}; use actix_service::{boxed::service, pipeline}; + #[cfg(feature = "openssl")] + use actix_tls::connect::ssl::openssl::OpensslConnector; + #[cfg(feature = "rustls")] + use actix_tls::connect::ssl::rustls::{RustlsConnector, Session}; let ssl_service = TimeoutService::new( self.config.timeout, @@ -363,8 +363,7 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, + T: Service + 'static, { pub(crate) tcp_pool: ConnectionPool, } @@ -372,8 +371,7 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, + T: Service + 'static, { fn clone(&self) -> Self { InnerConnector { @@ -382,17 +380,15 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, + T: Service + 'static, { - type Request = Connect; type Response = IoConnection; type Error = ConnectError; type Future = Either< - as Service>::Future, + as Service>::Future, Ready, ConnectError>>, >; @@ -428,8 +424,8 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T1: Service, - T2: Service, + T1: Service, + T2: Service, { pub(crate) tcp_pool: ConnectionPool, pub(crate) ssl_pool: ConnectionPool, @@ -439,10 +435,8 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T1: Service - + 'static, - T2: Service - + 'static, + T1: Service + 'static, + T2: Service + 'static, { fn clone(&self) -> Self { InnerConnector { @@ -452,16 +446,13 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T1: Service - + 'static, - T2: Service - + 'static, + T1: Service + 'static, + T2: Service + 'static, { - type Request = Connect; type Response = EitherConnection; type Error = ConnectError; type Future = Either< @@ -491,18 +482,16 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, + T: Service + 'static, { #[pin] - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } impl Future for InnerConnectorResponseA where - T: Service - + 'static, + T: Service + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, { @@ -520,18 +509,16 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, + T: Service + 'static, { #[pin] - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } impl Future for InnerConnectorResponseB where - T: Service - + 'static, + T: Service + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, { diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index ba697bca4..a5f1b2e8e 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -1,10 +1,10 @@ use std::io; -use actix_connect::resolver::ResolveError; +use actix_tls::connect::resolver::ResolveError; use derive_more::{Display, From}; #[cfg(feature = "openssl")] -use actix_connect::ssl::openssl::{HandshakeError, SslError}; +use actix_tls::accept::openssl::SslError; use crate::error::{Error, ParseError, ResponseError}; use crate::http::{Error as HttpError, StatusCode}; @@ -21,11 +21,6 @@ pub enum ConnectError { #[display(fmt = "{}", _0)] SslError(SslError), - /// SSL Handshake error - #[cfg(feature = "openssl")] - #[display(fmt = "{}", _0)] - SslHandshakeError(String), - /// Failed to resolve the hostname #[display(fmt = "Failed resolving hostname: {}", _0)] Resolver(ResolveError), @@ -57,25 +52,18 @@ pub enum ConnectError { impl std::error::Error for ConnectError {} -impl From for ConnectError { - fn from(err: actix_connect::ConnectError) -> ConnectError { +impl From for ConnectError { + fn from(err: actix_tls::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::Unresolved => ConnectError::Unresolved, - actix_connect::ConnectError::Io(e) => ConnectError::Io(e), + actix_tls::connect::ConnectError::Resolver(e) => ConnectError::Resolver(e), + actix_tls::connect::ConnectError::NoRecords => ConnectError::NoRecords, + actix_tls::connect::ConnectError::InvalidInput => panic!(), + actix_tls::connect::ConnectError::Unresolved => ConnectError::Unresolved, + actix_tls::connect::ConnectError::Io(e) => ConnectError::Io(e), } } } -#[cfg(feature = "openssl")] -impl From> for ConnectError { - fn from(err: HandshakeError) -> ConnectError { - ConnectError::SslHandshakeError(format!("{:?}", err)) - } -} - #[derive(Debug, Display, From)] pub enum InvalidUrl { #[display(fmt = "Missing url scheme")] diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 06cc05404..754c53968 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -1,10 +1,10 @@ use std::io::Write; use std::pin::Pin; use std::task::{Context, Poll}; -use std::{io, mem, time}; +use std::{io, time}; -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use bytes::buf::BufMutExt; +use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; +use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; use futures_core::Stream; use futures_util::future::poll_fn; @@ -204,18 +204,11 @@ where } impl AsyncRead for H1Connection { - unsafe fn prepare_uninitialized_buffer( - &self, - buf: &mut [mem::MaybeUninit], - ) -> bool { - self.io.as_ref().unwrap().prepare_uninitialized_buffer(buf) - } - fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { + buf: &mut ReadBuf<'_>, + ) -> Poll> { Pin::new(&mut self.io.as_mut().unwrap()).poll_read(cx, buf) } } diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index a8687dbeb..f9973a850 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -6,8 +6,8 @@ use std::rc::Rc; use std::task::{Context, Poll}; use std::time::{Duration, Instant}; -use actix_codec::{AsyncRead, AsyncWrite}; -use actix_rt::time::{delay_for, Delay}; +use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; +use actix_rt::time::{sleep, Sleep}; use actix_service::Service; use actix_utils::task::LocalWaker; use bytes::Bytes; @@ -50,8 +50,7 @@ pub(crate) struct ConnectionPool(Rc>, Rc ConnectionPool where Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, + T: Service + 'static, { pub(crate) fn new(connector: T, config: ConnectorConfig) -> Self { let connector_rc = Rc::new(RefCell::new(connector)); @@ -90,13 +89,11 @@ impl Drop for ConnectionPool { } } -impl Service for ConnectionPool +impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, + T: Service + 'static, { - type Request = Connect; type Response = IoConnection; type Error = ConnectError; type Future = LocalBoxFuture<'static, Result, ConnectError>>; @@ -334,10 +331,11 @@ where } else { let mut io = conn.io; let mut buf = [0; 2]; + let mut read_buf = ReadBuf::new(&mut buf); if let ConnectionType::H1(ref mut s) = io { - match Pin::new(s).poll_read(cx, &mut buf) { + match Pin::new(s).poll_read(cx, &mut read_buf) { Poll::Pending => (), - Poll::Ready(Ok(n)) if n > 0 => { + Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => { if let Some(timeout) = self.config.disconnect_timeout { if let ConnectionType::H1(io) = io { actix_rt::spawn(CloseConnection::new( @@ -387,9 +385,11 @@ where } } +#[pin_project::pin_project] struct CloseConnection { io: T, - timeout: Delay, + #[pin] + timeout: Sleep, } impl CloseConnection @@ -399,7 +399,7 @@ where fn new(io: T, timeout: Duration) -> Self { CloseConnection { io, - timeout: delay_for(timeout), + timeout: sleep(timeout), } } } @@ -411,11 +411,11 @@ where type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { - let this = self.get_mut(); + let this = self.project(); - match Pin::new(&mut this.timeout).poll(cx) { + match this.timeout.poll(cx) { Poll::Ready(_) => Poll::Ready(()), - Poll::Pending => match Pin::new(&mut this.io).poll_shutdown(cx) { + Poll::Pending => match Pin::new(this.io).poll_shutdown(cx) { Poll::Ready(_) => Poll::Ready(()), Poll::Pending => Poll::Pending, }, @@ -435,7 +435,7 @@ where impl Future for ConnectorPoolSupport where Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service, + T: Service, T::Future: 'static, { type Output = (); diff --git a/actix-http/src/cloneable.rs b/actix-http/src/cloneable.rs index 0e77c455c..5f0b1ea28 100644 --- a/actix-http/src/cloneable.rs +++ b/actix-http/src/cloneable.rs @@ -10,22 +10,21 @@ use actix_service::Service; /// CloneableService might panic with some creative use of thread local storage. /// See https://github.com/actix/actix-web/issues/1295 for example #[doc(hidden)] -pub(crate) struct CloneableService(Rc>); +pub(crate) struct CloneableService(Rc>); -impl CloneableService { +impl CloneableService { pub(crate) fn new(service: T) -> Self { Self(Rc::new(RefCell::new(service))) } } -impl Clone for CloneableService { +impl Clone for CloneableService { fn clone(&self) -> Self { Self(self.0.clone()) } } -impl Service for CloneableService { - type Request = T::Request; +impl, Req> Service for CloneableService { type Response = T::Response; type Error = T::Error; type Future = T::Future; @@ -34,7 +33,7 @@ impl Service for CloneableService { self.0.borrow_mut().poll_ready(cx) } - fn call(&mut self, req: T::Request) -> Self::Future { + fn call(&mut self, req: Req) -> Self::Future { self.0.borrow_mut().call(req) } } diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index b314d4c99..1cd7e4aea 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use std::time::Duration; use std::{fmt, net}; -use actix_rt::time::{delay_for, delay_until, Delay, Instant}; +use actix_rt::time::{sleep, sleep_until, Instant, Sleep}; use bytes::BytesMut; use futures_util::{future, FutureExt}; use time::OffsetDateTime; @@ -121,10 +121,10 @@ impl ServiceConfig { #[inline] /// Client timeout for first request. - pub fn client_timer(&self) -> Option { + pub fn client_timer(&self) -> Option { let delay_time = self.0.client_timeout; if delay_time != 0 { - Some(delay_until( + Some(sleep_until( self.0.timer.now() + Duration::from_millis(delay_time), )) } else { @@ -154,9 +154,9 @@ impl ServiceConfig { #[inline] /// Return keep-alive timer delay is configured. - pub fn keep_alive_timer(&self) -> Option { + pub fn keep_alive_timer(&self) -> Option { if let Some(ka) = self.0.keep_alive { - Some(delay_until(self.0.timer.now() + ka)) + Some(sleep_until(self.0.timer.now() + ka)) } else { None } @@ -266,7 +266,7 @@ impl DateService { // periodic date update let s = self.clone(); - actix_rt::spawn(delay_for(Duration::from_millis(500)).then(move |_| { + actix_rt::spawn(sleep(Duration::from_millis(500)).then(move |_| { s.0.reset(); future::ready(()) })); diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 0ebd4c05c..03e5467c5 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -178,11 +178,7 @@ impl ResponseError for FormError {} #[cfg(feature = "openssl")] /// `InternalServerError` for `openssl::ssl::Error` -impl ResponseError for actix_connect::ssl::openssl::SslError {} - -#[cfg(feature = "openssl")] -/// `InternalServerError` for `openssl::ssl::HandshakeError` -impl ResponseError for actix_tls::openssl::HandshakeError {} +impl ResponseError for actix_tls::accept::openssl::SslError {} /// Return `BAD_REQUEST` for `de::value::Error` impl ResponseError for DeError { @@ -956,11 +952,6 @@ where /// This is supported on feature=`actors` only impl ResponseError for actix::MailboxError {} -#[cfg(feature = "actors")] -/// `InternalServerError` for `actix::ResolverError` -/// This is supported on feature=`actors` only -impl ResponseError for actix::actors::resolver::ResolverError {} - #[cfg(test)] mod tests { use super::*; diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 91e208aac..41caea902 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -8,7 +8,7 @@ use std::{ }; use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts}; -use actix_rt::time::{delay_until, Delay, Instant}; +use actix_rt::time::{sleep_until, Instant, Sleep}; use actix_service::Service; use bitflags::bitflags; use bytes::{Buf, BytesMut}; @@ -51,12 +51,12 @@ bitflags! { /// Dispatcher for HTTP/1.1 protocol pub struct Dispatcher where - S: Service, + S: Service, S::Error: Into, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { #[pin] @@ -69,12 +69,12 @@ where #[pin_project(project = DispatcherStateProj)] enum DispatcherState where - S: Service, + S: Service, S::Error: Into, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { Normal(#[pin] InnerDispatcher), @@ -84,12 +84,12 @@ where #[pin_project(project = InnerDispatcherProj)] struct InnerDispatcher where - S: Service, + S: Service, S::Error: Into, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { service: CloneableService, @@ -106,7 +106,8 @@ where messages: VecDeque, ka_expire: Instant, - ka_timer: Option, + #[pin] + ka_timer: Option, io: Option, read_buf: BytesMut, @@ -123,8 +124,8 @@ enum DispatcherMessage { #[pin_project(project = StateProj)] enum State where - S: Service, - X: Service, + S: Service, + X: Service, B: MessageBody, { None, @@ -135,8 +136,8 @@ where impl State where - S: Service, - X: Service, + S: Service, + X: Service, B: MessageBody, { fn is_empty(&self) -> bool { @@ -166,13 +167,13 @@ impl PartialEq for PollResponse { impl Dispatcher where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into, S::Response: Into>, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { /// Create HTTP/1 dispatcher. @@ -205,7 +206,7 @@ where codec: Codec, config: ServiceConfig, read_buf: BytesMut, - timeout: Option, + timeout: Option, service: CloneableService, expect: CloneableService, upgrade: Option>, @@ -257,13 +258,13 @@ where impl InnerDispatcher where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into, S::Response: Into>, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { fn can_read(&self, cx: &mut Context<'_>) -> bool { @@ -660,7 +661,7 @@ where // shutdown timeout if this.flags.contains(Flags::SHUTDOWN) { if let Some(interval) = this.codec.config().client_disconnect_timer() { - *this.ka_timer = Some(delay_until(interval)); + this.ka_timer.set(Some(sleep_until(interval))); } else { this.flags.insert(Flags::READ_DISCONNECT); if let Some(mut payload) = this.payload.take() { @@ -673,12 +674,14 @@ where } } - match Pin::new(&mut this.ka_timer.as_mut().unwrap()).poll(cx) { + match this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx) { Poll::Ready(()) => { // if we get timeout during shutdown, drop connection if this.flags.contains(Flags::SHUTDOWN) { return Err(DispatchError::DisconnectTimeout); - } else if this.ka_timer.as_mut().unwrap().deadline() >= *this.ka_expire { + } else if this.ka_timer.as_mut().as_pin_mut().unwrap().deadline() + >= *this.ka_expire + { // check for any outstanding tasks if this.state.is_empty() && this.write_buf.is_empty() { if this.flags.contains(Flags::STARTED) { @@ -689,9 +692,15 @@ where if let Some(deadline) = this.codec.config().client_disconnect_timer() { - if let Some(mut timer) = this.ka_timer.as_mut() { + if let Some(timer) = this.ka_timer.as_mut().as_pin_mut() + { timer.reset(deadline); - let _ = Pin::new(&mut timer).poll(cx); + let _ = this + .ka_timer + .as_mut() + .as_pin_mut() + .unwrap() + .poll(cx); } } else { // no shutdown timeout, drop socket @@ -716,14 +725,15 @@ where } else if let Some(deadline) = this.codec.config().keep_alive_expire() { - if let Some(mut timer) = this.ka_timer.as_mut() { + if let Some(timer) = this.ka_timer.as_mut().as_pin_mut() { timer.reset(deadline); - let _ = Pin::new(&mut timer).poll(cx); + let _ = + this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx); } } - } else if let Some(mut timer) = this.ka_timer.as_mut() { + } else if let Some(timer) = this.ka_timer.as_mut().as_pin_mut() { timer.reset(*this.ka_expire); - let _ = Pin::new(&mut timer).poll(cx); + let _ = this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx); } } Poll::Pending => (), @@ -736,13 +746,13 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into, S::Response: Into>, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { type Output = Result<(), DispatchError>; @@ -951,12 +961,12 @@ fn read( where T: AsyncRead + Unpin, { - Pin::new(io).poll_read_buf(cx, buf) + actix_codec::poll_read_buf(Pin::new(io), cx, buf) } #[cfg(test)] mod tests { - use std::{marker::PhantomData, str}; + use std::str; use actix_service::fn_service; use futures_util::future::{lazy, ready}; @@ -985,21 +995,19 @@ mod tests { } } - fn ok_service() -> impl Service - { + fn ok_service() -> impl Service { fn_service(|_req: Request| ready(Ok::<_, Error>(Response::Ok().finish()))) } - fn echo_path_service( - ) -> impl Service { + fn echo_path_service() -> impl Service { fn_service(|req: Request| { let path = req.path().as_bytes(); ready(Ok::<_, Error>(Response::Ok().body(Body::from_slice(path)))) }) } - fn echo_payload_service( - ) -> impl Service { + fn echo_payload_service() -> impl Service + { fn_service(|mut req: Request| { Box::pin(async move { use futures_util::stream::StreamExt as _; @@ -1007,7 +1015,7 @@ mod tests { let mut pl = req.take_payload(); let mut body = BytesMut::new(); while let Some(chunk) = pl.next().await { - body.extend_from_slice(chunk.unwrap().bytes()) + body.extend_from_slice(chunk.unwrap().chunk()) } Ok::<_, Error>(Response::Ok().body(body)) @@ -1020,7 +1028,7 @@ mod tests { lazy(|cx| { let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, ServiceConfig::default(), CloneableService::new(ok_service()), @@ -1060,7 +1068,7 @@ mod tests { let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, cfg, CloneableService::new(echo_path_service()), @@ -1114,7 +1122,7 @@ mod tests { let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, cfg, CloneableService::new(echo_path_service()), @@ -1163,7 +1171,7 @@ mod tests { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf.clone(), cfg, CloneableService::new(echo_payload_service()), @@ -1234,7 +1242,7 @@ mod tests { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf.clone(), cfg, CloneableService::new(echo_path_service()), @@ -1293,12 +1301,12 @@ mod tests { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf.clone(), cfg, CloneableService::new(ok_service()), CloneableService::new(ExpectHandler), - Some(CloneableService::new(UpgradeHandler(PhantomData))), + Some(CloneableService::new(UpgradeHandler)), Extensions::new(), None, ); diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 2ec4899a7..4fadbb518 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -135,7 +135,7 @@ pub(crate) trait MessageType: Sized { let mut has_date = false; - let mut buf = dst.bytes_mut().as_mut_ptr() as *mut u8; + let mut buf = dst.chunk_mut().as_mut_ptr() as *mut u8; let mut remaining = dst.capacity() - dst.len(); // tracks bytes written since last buffer resize @@ -177,7 +177,7 @@ pub(crate) trait MessageType: Sized { // re-assign buf raw pointer since it's possible that the buffer was // reallocated and/or resized - buf = dst.bytes_mut().as_mut_ptr() as *mut u8; + buf = dst.chunk_mut().as_mut_ptr() as *mut u8; } // SAFETY: on each write, it is enough to ensure that the advancement of the @@ -224,7 +224,7 @@ pub(crate) trait MessageType: Sized { // re-assign buf raw pointer since it's possible that the buffer was // reallocated and/or resized - buf = dst.bytes_mut().as_mut_ptr() as *mut u8; + buf = dst.chunk_mut().as_mut_ptr() as *mut u8; } // SAFETY: on each write, it is enough to ensure that the advancement of diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index b89c7ff74..c3e4ccdaa 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -8,11 +8,10 @@ use crate::request::Request; pub struct ExpectHandler; -impl ServiceFactory for ExpectHandler { - type Config = (); - type Request = Request; +impl ServiceFactory for ExpectHandler { type Response = Request; type Error = Error; + type Config = (); type Service = ExpectHandler; type InitError = Error; type Future = Ready>; @@ -22,8 +21,7 @@ impl ServiceFactory for ExpectHandler { } } -impl Service for ExpectHandler { - type Request = Request; +impl Service for ExpectHandler { type Response = Request; type Error = Error; type Future = Ready>; diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 919a5d932..67f1127c7 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -24,25 +24,25 @@ use super::dispatcher::Dispatcher; use super::{ExpectHandler, UpgradeHandler}; /// `ServiceFactory` implementation for HTTP1 transport -pub struct H1Service> { +pub struct H1Service { srv: S, cfg: ServiceConfig, expect: X, upgrade: Option, on_connect_ext: Option>>, - _t: PhantomData<(T, B)>, + _t: PhantomData, } impl H1Service where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, { /// Create new `HttpService` instance with config. - pub(crate) fn with_config>( + pub(crate) fn with_config>( cfg: ServiceConfig, service: F, ) -> Self { @@ -59,19 +59,15 @@ where impl H1Service where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - U: ServiceFactory< - Config = (), - Request = (Request, Framed), - Response = (), - >, + U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { @@ -79,8 +75,8 @@ where pub fn tcp( self, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), Error = DispatchError, InitError = (), @@ -97,22 +93,23 @@ where mod openssl { use super::*; - use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream}; - use actix_tls::{openssl::HandshakeError, TlsError}; + use actix_service::ServiceFactoryExt; + use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream}; + use actix_tls::accept::TlsError; impl H1Service, S, B, X, U> where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory< + (Request, Framed, Codec>), Config = (), - Request = (Request, Framed, Codec>), Response = (), >, U::Error: fmt::Display + Into, @@ -123,10 +120,10 @@ mod openssl { self, acceptor: SslAcceptor, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), - Error = TlsError, DispatchError>, + Error = TlsError, InitError = (), > { pipeline_factory( @@ -146,23 +143,24 @@ mod openssl { #[cfg(feature = "rustls")] mod rustls { use super::*; - use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream}; - use actix_tls::TlsError; + use actix_service::ServiceFactoryExt; + use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream}; + use actix_tls::accept::TlsError; use std::{fmt, io}; impl H1Service, S, B, X, U> where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory< + (Request, Framed, Codec>), Config = (), - Request = (Request, Framed, Codec>), Response = (), >, U::Error: fmt::Display + Into, @@ -173,8 +171,8 @@ mod rustls { self, config: ServerConfig, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), Error = TlsError, InitError = (), @@ -195,7 +193,7 @@ mod rustls { impl H1Service where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, @@ -203,7 +201,7 @@ where { pub fn expect(self, expect: X1) -> H1Service where - X1: ServiceFactory, + X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, { @@ -219,7 +217,7 @@ where pub fn upgrade(self, upgrade: Option) -> H1Service where - U1: ServiceFactory), Response = ()>, + U1: ServiceFactory<(Request, Framed), Response = ()>, U1::Error: fmt::Display, U1::InitError: fmt::Debug, { @@ -240,27 +238,27 @@ where } } -impl ServiceFactory for H1Service +impl ServiceFactory<(T, Option)> + for H1Service where T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, + S: ServiceFactory, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, B: MessageBody, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - U: ServiceFactory), Response = ()>, + U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { - type Config = (); - type Request = (T, Option); type Response = (); type Error = DispatchError; - type InitError = (); + type Config = (); type Service = H1ServiceHandler; + type InitError = (); type Future = H1ServiceResponse; fn new_service(&self, _: ()) -> Self::Future { @@ -281,13 +279,13 @@ where #[pin_project::pin_project] pub struct H1ServiceResponse where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - U: ServiceFactory), Response = ()>, + U: ServiceFactory<(Request, Framed), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, { @@ -307,15 +305,15 @@ where impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, + S: ServiceFactory, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, B: MessageBody, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - U: ServiceFactory), Response = ()>, + U: ServiceFactory<(Request, Framed), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, { @@ -362,24 +360,29 @@ where } /// `Service` implementation for HTTP/1 transport -pub struct H1ServiceHandler { +pub struct H1ServiceHandler +where + S: Service, + X: Service, + U: Service<(Request, Framed)>, +{ srv: CloneableService, expect: CloneableService, upgrade: Option>, on_connect_ext: Option>>, cfg: ServiceConfig, - _t: PhantomData<(T, B)>, + _t: PhantomData, } impl H1ServiceHandler where - S: Service, + S: Service, S::Error: Into, S::Response: Into>, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { fn new( @@ -400,19 +403,19 @@ where } } -impl Service for H1ServiceHandler +impl Service<(T, Option)> + for H1ServiceHandler where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into, S::Response: Into>, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display + Into, { - type Request = (T, Option); type Response = (); type Error = DispatchError; type Future = Dispatcher; @@ -459,7 +462,7 @@ where } } - fn call(&mut self, (io, addr): Self::Request) -> Self::Future { + fn call(&mut self, (io, addr): (T, Option)) -> Self::Future { let mut connect_extensions = Extensions::new(); if let Some(ref handler) = self.on_connect_ext { // run on_connect_ext callback, populating connect extensions diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs index 8615f27a8..007aff1bf 100644 --- a/actix-http/src/h1/upgrade.rs +++ b/actix-http/src/h1/upgrade.rs @@ -1,4 +1,3 @@ -use std::marker::PhantomData; use std::task::{Context, Poll}; use actix_codec::Framed; @@ -9,14 +8,13 @@ use crate::error::Error; use crate::h1::Codec; use crate::request::Request; -pub struct UpgradeHandler(pub(crate) PhantomData); +pub struct UpgradeHandler; -impl ServiceFactory for UpgradeHandler { - type Config = (); - type Request = (Request, Framed); +impl ServiceFactory<(Request, Framed)> for UpgradeHandler { type Response = (); type Error = Error; - type Service = UpgradeHandler; + type Config = (); + type Service = UpgradeHandler; type InitError = Error; type Future = Ready>; @@ -25,8 +23,7 @@ impl ServiceFactory for UpgradeHandler { } } -impl Service for UpgradeHandler { - type Request = (Request, Framed); +impl Service<(Request, Framed)> for UpgradeHandler { type Response = (); type Error = Error; type Future = Ready>; @@ -35,7 +32,7 @@ impl Service for UpgradeHandler { Poll::Ready(Ok(())) } - fn call(&mut self, _: Self::Request) -> Self::Future { + fn call(&mut self, _: (Request, Framed)) -> Self::Future { ready(Ok(())) } } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 7a0be9492..4aeda942a 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -6,7 +6,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_rt::time::{Delay, Instant}; +use actix_rt::time::{Instant, Sleep}; use actix_service::Service; use bytes::{Bytes, BytesMut}; use h2::server::{Connection, SendResponse}; @@ -29,9 +29,11 @@ const CHUNK_SIZE: usize = 16_384; /// Dispatcher for HTTP/2 protocol #[pin_project::pin_project] -pub struct Dispatcher, B: MessageBody> +pub struct Dispatcher where T: AsyncRead + AsyncWrite + Unpin, + S: Service, + B: MessageBody, { service: CloneableService, connection: Connection, @@ -39,14 +41,14 @@ where config: ServiceConfig, peer_addr: Option, ka_expire: Instant, - ka_timer: Option, + ka_timer: Option, _t: PhantomData, } impl Dispatcher where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into, // S::Future: 'static, S::Response: Into>, @@ -57,7 +59,7 @@ where connection: Connection, on_connect_data: Extensions, config: ServiceConfig, - timeout: Option, + timeout: Option, peer_addr: Option, ) -> Self { // let keepalive = config.keep_alive_enabled(); @@ -92,7 +94,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index b1fb9a634..719f3622c 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -36,14 +36,14 @@ pub struct H2Service { impl H2Service where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { /// Create new `HttpService` instance with config. - pub(crate) fn with_config>( + pub(crate) fn with_config>( cfg: ServiceConfig, service: F, ) -> Self { @@ -64,18 +64,18 @@ where impl H2Service where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { /// Create simple tcp based service pub fn tcp( self, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), Error = DispatchError, InitError = S::InitError, @@ -92,18 +92,18 @@ where #[cfg(feature = "openssl")] mod openssl { - use actix_service::{fn_factory, fn_service}; - use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream}; - use actix_tls::{openssl::HandshakeError, TlsError}; + use actix_service::{fn_factory, fn_service, ServiceFactoryExt}; + use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream}; + use actix_tls::accept::TlsError; use super::*; impl H2Service, S, B> where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { /// Create ssl based service @@ -111,10 +111,10 @@ mod openssl { self, acceptor: SslAcceptor, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), - Error = TlsError, DispatchError>, + Error = TlsError, InitError = S::InitError, > { pipeline_factory( @@ -136,16 +136,17 @@ mod openssl { #[cfg(feature = "rustls")] mod rustls { use super::*; - use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream}; - use actix_tls::TlsError; + use actix_service::ServiceFactoryExt; + use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream}; + use actix_tls::accept::TlsError; use std::io; impl H2Service, S, B> where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { /// Create openssl based service @@ -153,8 +154,8 @@ mod rustls { self, mut config: ServerConfig, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), Error = TlsError, InitError = S::InitError, @@ -178,21 +179,20 @@ mod rustls { } } -impl ServiceFactory for H2Service +impl ServiceFactory<(T, Option)> for H2Service where T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { - type Config = (); - type Request = (T, Option); type Response = (); type Error = DispatchError; - type InitError = S::InitError; + type Config = (); type Service = H2ServiceHandler; + type InitError = S::InitError; type Future = H2ServiceResponse; fn new_service(&self, _: ()) -> Self::Future { @@ -207,21 +207,24 @@ where #[doc(hidden)] #[pin_project::pin_project] -pub struct H2ServiceResponse { +pub struct H2ServiceResponse +where + S: ServiceFactory, +{ #[pin] fut: S::Future, cfg: Option, on_connect_ext: Option>>, - _t: PhantomData<(T, B)>, + _t: PhantomData, } impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { type Output = Result, S::InitError>; @@ -241,16 +244,19 @@ where } /// `Service` implementation for http/2 transport -pub struct H2ServiceHandler { +pub struct H2ServiceHandler +where + S: Service, +{ srv: CloneableService, cfg: ServiceConfig, on_connect_ext: Option>>, - _t: PhantomData<(T, B)>, + _t: PhantomData, } impl H2ServiceHandler where - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, @@ -270,16 +276,15 @@ where } } -impl Service for H2ServiceHandler +impl Service<(T, Option)> for H2ServiceHandler where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, { - type Request = (T, Option); type Response = (); type Error = DispatchError; type Future = H2ServiceHandlerResponse; @@ -292,7 +297,7 @@ where }) } - fn call(&mut self, (io, addr): Self::Request) -> Self::Future { + fn call(&mut self, (io, addr): (T, Option)) -> Self::Future { let mut connect_extensions = Extensions::new(); if let Some(ref handler) = self.on_connect_ext { // run on_connect_ext callback, populating connect extensions @@ -311,7 +316,7 @@ where } } -enum State, B: MessageBody> +enum State, B: MessageBody> where T: AsyncRead + AsyncWrite + Unpin, S::Future: 'static, @@ -329,7 +334,7 @@ where pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, @@ -341,7 +346,7 @@ where impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, diff --git a/actix-http/src/header/shared/httpdate.rs b/actix-http/src/header/shared/httpdate.rs index 81caf6d53..d6b9d8001 100644 --- a/actix-http/src/header/shared/httpdate.rs +++ b/actix-http/src/header/shared/httpdate.rs @@ -3,7 +3,8 @@ use std::io::Write; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; -use bytes::{buf::BufMutExt, BytesMut}; +use bytes::buf::BufMut; +use bytes::BytesMut; use http::header::{HeaderValue, InvalidHeaderValue}; use time::{offset, OffsetDateTime, PrimitiveDateTime}; diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 64e302441..0bc84a44e 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -23,6 +23,10 @@ impl

HttpMessage for Request

{ &self.head().headers } + fn take_payload(&mut self) -> Payload

{ + std::mem::replace(&mut self.payload, Payload::None) + } + /// Request extensions #[inline] fn extensions(&self) -> Ref<'_, Extensions> { @@ -34,10 +38,6 @@ impl

HttpMessage for Request

{ fn extensions_mut(&self) -> RefMut<'_, Extensions> { self.head.extensions_mut() } - - fn take_payload(&mut self) -> Payload

{ - std::mem::replace(&mut self.payload, Payload::None) - } } impl From> for Request { diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 527ed3833..f23115cd5 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -22,22 +22,22 @@ use crate::response::Response; use crate::{h1, h2::Dispatcher, ConnectCallback, Extensions, Protocol}; /// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol. -pub struct HttpService> { +pub struct HttpService { srv: S, cfg: ServiceConfig, expect: X, upgrade: Option, on_connect_ext: Option>>, - _t: PhantomData<(T, B)>, + _t: PhantomData, } impl HttpService where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { /// Create builder for `HttpService` instance. @@ -48,15 +48,15 @@ where impl HttpService where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, 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, false, None); HttpService { @@ -70,7 +70,7 @@ where } /// Create new `HttpService` instance with config. - pub(crate) fn with_config>( + pub(crate) fn with_config>( cfg: ServiceConfig, service: F, ) -> Self { @@ -87,11 +87,11 @@ where impl HttpService where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody, { /// Provide service for `EXPECT: 100-Continue` support. @@ -101,10 +101,10 @@ where /// request will be forwarded to main service. pub fn expect(self, expect: X1) -> HttpService where - X1: ServiceFactory, + X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, - ::Future: 'static, + >::Future: 'static, { HttpService { expect, @@ -122,14 +122,10 @@ where /// and this service get called with original request and framed object. pub fn upgrade(self, upgrade: Option) -> HttpService where - U1: ServiceFactory< - Config = (), - Request = (Request, Framed), - Response = (), - >, + U1: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U1::Error: fmt::Display, U1::InitError: fmt::Debug, - ::Future: 'static, + )>>::Future: 'static, { HttpService { upgrade, @@ -150,31 +146,31 @@ where impl HttpService where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - ::Future: 'static, + >::Future: 'static, U: ServiceFactory< + (Request, Framed), Config = (), - Request = (Request, Framed), Response = (), >, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - ::Future: 'static, + )>>::Future: 'static, { /// Create simple tcp stream service pub fn tcp( self, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), Error = DispatchError, InitError = (), @@ -190,39 +186,40 @@ where #[cfg(feature = "openssl")] mod openssl { use super::*; - use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream}; - use actix_tls::{openssl::HandshakeError, TlsError}; + use actix_service::ServiceFactoryExt; + use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream}; + use actix_tls::accept::TlsError; impl HttpService, S, B, X, U> where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - ::Future: 'static, + >::Future: 'static, U: ServiceFactory< + (Request, Framed, h1::Codec>), Config = (), - Request = (Request, Framed, h1::Codec>), Response = (), >, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - ::Future: 'static, + , h1::Codec>)>>::Future: 'static, { /// Create openssl based service pub fn openssl( self, acceptor: SslAcceptor, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), - Error = TlsError, DispatchError>, + Error = TlsError, InitError = (), > { pipeline_factory( @@ -250,39 +247,42 @@ mod openssl { #[cfg(feature = "rustls")] mod rustls { - use super::*; - use actix_tls::rustls::{Acceptor, ServerConfig, Session, TlsStream}; - use actix_tls::TlsError; use std::io; + use actix_tls::accept::rustls::{Acceptor, ServerConfig, Session, TlsStream}; + use actix_tls::accept::TlsError; + + use super::*; + use actix_service::ServiceFactoryExt; + impl HttpService, S, B, X, U> where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - ::Future: 'static, + >::Future: 'static, U: ServiceFactory< + (Request, Framed, h1::Codec>), Config = (), - Request = (Request, Framed, h1::Codec>), Response = (), >, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - ::Future: 'static, + , h1::Codec>)>>::Future: 'static, { /// Create openssl based service pub fn rustls( self, mut config: ServerConfig, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), Error = TlsError, InitError = (), @@ -313,34 +313,30 @@ mod rustls { } } -impl ServiceFactory for HttpService +impl ServiceFactory<(T, Protocol, Option)> + for HttpService where T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - ::Future: 'static, - U: ServiceFactory< - Config = (), - Request = (Request, Framed), - Response = (), - >, + >::Future: 'static, + U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - ::Future: 'static, + )>>::Future: 'static, { - type Config = (); - type Request = (T, Protocol, Option); type Response = (); type Error = DispatchError; - type InitError = (); + type Config = (); type Service = HttpServiceHandler; + type InitError = (); type Future = HttpServiceResponse; fn new_service(&self, _: ()) -> Self::Future { @@ -359,13 +355,12 @@ where #[doc(hidden)] #[pin_project] -pub struct HttpServiceResponse< - T, - S: ServiceFactory, - B, - X: ServiceFactory, - U: ServiceFactory, -> { +pub struct HttpServiceResponse +where + S: ServiceFactory, + X: ServiceFactory, + U: ServiceFactory<(Request, Framed)>, +{ #[pin] fut: S::Future, #[pin] @@ -382,20 +377,20 @@ pub struct HttpServiceResponse< impl Future for HttpServiceResponse where T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - ::Future: 'static, - U: ServiceFactory), Response = ()>, + >::Future: 'static, + U: ServiceFactory<(Request, Framed), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, - ::Future: 'static, + )>>::Future: 'static, { type Output = Result, ()>; @@ -440,25 +435,30 @@ where } /// `Service` implementation for http transport -pub struct HttpServiceHandler { +pub struct HttpServiceHandler +where + S: Service, + X: Service, + U: Service<(Request, Framed)>, +{ srv: CloneableService, expect: CloneableService, upgrade: Option>, cfg: ServiceConfig, on_connect_ext: Option>>, - _t: PhantomData<(T, B, X)>, + _t: PhantomData, } impl HttpServiceHandler where - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { fn new( @@ -479,20 +479,20 @@ where } } -impl Service for HttpServiceHandler +impl Service<(T, Protocol, Option)> + for HttpServiceHandler where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display + Into, { - type Request = (T, Protocol, Option); type Response = (); type Error = DispatchError; type Future = HttpServiceHandlerResponse; @@ -539,7 +539,10 @@ where } } - fn call(&mut self, (io, proto, peer_addr): Self::Request) -> Self::Future { + fn call( + &mut self, + (io, proto, peer_addr): (T, Protocol, Option), + ) -> Self::Future { let mut connect_extensions = Extensions::new(); if let Some(ref handler) = self.on_connect_ext { @@ -575,14 +578,14 @@ where #[pin_project(project = StateProj)] enum State where - S: Service, + S: Service, S::Future: 'static, S::Error: Into, T: AsyncRead + AsyncWrite + Unpin, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { H1(#[pin] h1::Dispatcher), @@ -602,14 +605,14 @@ where pub struct HttpServiceHandlerResponse where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { #[pin] @@ -619,14 +622,14 @@ where impl Future for HttpServiceHandlerResponse where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { type Output = Result<(), DispatchError>; @@ -639,13 +642,13 @@ where impl State where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into + 'static, S::Response: Into> + 'static, B: MessageBody + 'static, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { fn poll( diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 4512e72c2..3f08bb7ee 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -10,7 +10,7 @@ use std::{ task::{Context, Poll}, }; -use actix_codec::{AsyncRead, AsyncWrite}; +use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use bytes::{Bytes, BytesMut}; use http::header::{self, HeaderName, HeaderValue}; use http::{Error as HttpError, Method, Uri, Version}; @@ -251,9 +251,11 @@ impl AsyncRead for TestBuffer { fn poll_read( self: Pin<&mut Self>, _: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - Poll::Ready(self.get_mut().read(buf)) + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let dst = buf.initialize_unfilled(); + let res = self.get_mut().read(dst).map(|n| buf.advance(n)); + Poll::Ready(res) } } @@ -356,11 +358,15 @@ impl AsyncRead for TestSeqBuffer { fn poll_read( self: Pin<&mut Self>, _: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - let r = self.get_mut().read(buf); + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let dst = buf.initialize_unfilled(); + let r = self.get_mut().read(dst); match r { - Ok(n) => Poll::Ready(Ok(n)), + Ok(n) => { + buf.advance(n); + Poll::Ready(Ok(())) + } Err(err) if err.kind() == io::ErrorKind::WouldBlock => Poll::Pending, Err(err) => Poll::Ready(Err(err)), } diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs index b114217a0..7be7cf637 100644 --- a/actix-http/src/ws/dispatcher.rs +++ b/actix-http/src/ws/dispatcher.rs @@ -11,7 +11,7 @@ use super::{Codec, Frame, Message}; #[pin_project::pin_project] pub struct Dispatcher where - S: Service + 'static, + S: Service + 'static, T: AsyncRead + AsyncWrite, { #[pin] @@ -21,17 +21,17 @@ where impl Dispatcher 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 { Dispatcher { inner: InnerDispatcher::new(Framed::new(io, Codec::new()), service), } } - pub fn with>(framed: Framed, service: F) -> Self { + pub fn with>(framed: Framed, service: F) -> Self { Dispatcher { inner: InnerDispatcher::new(framed, service), } @@ -41,7 +41,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 07104decc..f78636b9a 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -1,9 +1,8 @@ -use actix_service::ServiceFactory; -use bytes::Bytes; -use futures_util::future::{self, ok}; - use actix_http::{http, HttpService, Request, Response}; use actix_http_test::test_server; +use actix_service::ServiceFactoryExt; +use bytes::Bytes; +use futures_util::future::{self, ok}; 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/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 6b80bad0a..bb4732281 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -1,19 +1,17 @@ #![cfg(feature = "openssl")] use std::io; -use actix_http_test::test_server; -use actix_service::{fn_service, ServiceFactory}; - -use bytes::{Bytes, BytesMut}; -use futures_util::future::{err, ok, ready}; -use futures_util::stream::{once, Stream, StreamExt}; -use open_ssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; - use actix_http::error::{ErrorBadRequest, PayloadError}; use actix_http::http::header::{self, HeaderName, HeaderValue}; use actix_http::http::{Method, StatusCode, Version}; use actix_http::httpmessage::HttpMessage; use actix_http::{body, Error, HttpService, Request, Response}; +use actix_http_test::test_server; +use actix_service::{fn_service, ServiceFactoryExt}; +use bytes::{Bytes, BytesMut}; +use futures_util::future::{err, ok, ready}; +use futures_util::stream::{once, Stream, StreamExt}; +use open_ssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; async fn load_body(stream: S) -> Result where diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 44794e199..fa1aeb695 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -3,7 +3,7 @@ use std::time::Duration; use std::{net, thread}; use actix_http_test::test_server; -use actix_rt::time::delay_for; +use actix_rt::time::sleep; use actix_service::fn_service; use bytes::Bytes; use futures_util::future::{self, err, ok, ready, FutureExt}; @@ -88,7 +88,7 @@ async fn test_expect_continue_h1() { let srv = test_server(|| { HttpService::build() .expect(fn_service(|req: Request| { - delay_for(Duration::from_millis(20)).then(move |_| { + sleep(Duration::from_millis(20)).then(move |_| { if req.head().uri.query() == Some("yes=") { ok(req) } else { diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 5d86605f4..e31f2745c 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -36,11 +36,10 @@ impl Clone for WsService { } } -impl Service for WsService +impl Service<(Request, Framed)> for WsService where T: AsyncRead + AsyncWrite + Unpin + 'static, { - type Request = (Request, Framed); type Response = (); type Error = Error; type Future = Pin>>>; @@ -50,7 +49,10 @@ where Poll::Ready(Ok(())) } - fn call(&mut self, (req, mut framed): Self::Request) -> Self::Future { + fn call( + &mut self, + (req, mut framed): (Request, Framed), + ) -> Self::Future { let fut = async move { let res = ws::handshake(req.head()).unwrap().message_body(()); diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 0d13d5015..4c6f01d29 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -2,6 +2,9 @@ ## Unreleased - 2021-xx-xx * Fix multipart consuming payload before header checks #1513 +* Update `bytes` to `1.0`. [#1813] + +[#1813]: https://github.com/actix/actix-web/pull/1813 ## 3.0.0 - 2020-09-11 diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index e2e9dbf14..ed572a700 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -17,16 +17,16 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "3.0.0", default-features = false } -actix-service = "1.0.6" -actix-utils = "2.0.0" -bytes = "0.5.3" +actix-utils = "3.0.0-beta.1" + +bytes = "1" derive_more = "0.99.2" httparse = "1.3" -futures-util = { version = "0.3.5", default-features = false } +futures-util = { version = "0.3.7", default-features = false } log = "0.4" mime = "0.3" twoway = "0.2" [dev-dependencies] -actix-rt = "1.0.0" +actix-rt = "2.0.0-beta.1" actix-http = "2.0.0" diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 0d830fe64..e47f09135 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,8 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx -* Upgrade `pin-project` to `1.0`. +* Update `pin-project` to `1.0`. +* Update `bytes` to `1.0`. [#1813] +[#1813]: https://github.com/actix/actix-web/pull/1813 ## 3.0.0 - 2020-09-11 * No significant changes from `3.0.0-beta.2`. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 920940c40..28b9d6fa2 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,16 +16,17 @@ name = "actix_web_actors" path = "src/lib.rs" [dependencies] -actix = "0.10.0" -actix-web = { version = "3.0.0", default-features = false } +actix = "0.11.0-beta.1" +actix-codec = "0.4.0-beta.1" actix-http = "2.0.0" -actix-codec = "0.3.0" -bytes = "0.5.2" -futures-channel = { version = "0.3.5", default-features = false } -futures-core = { version = "0.3.5", default-features = false } +actix-web = { version = "3.0.0", default-features = false } + +bytes = "1" +futures-core = { version = "0.3.7", default-features = false } pin-project = "1.0.0" +tokio = { version = "1", features = ["sync"] } [dev-dependencies] -actix-rt = "1.1.1" +actix-rt = "2.0.0-beta.1" env_logger = "0.7" -futures-util = { version = "0.3.5", default-features = false } +futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs index 0839a4288..2dd93c727 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -12,8 +12,8 @@ use actix::{ }; use actix_web::error::Error; use bytes::Bytes; -use futures_channel::oneshot::Sender; use futures_core::Stream; +use tokio::sync::oneshot::Sender; /// Execution context for http actors pub struct HttpContext diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 8fd03f6a1..9dd7bf500 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -24,8 +24,8 @@ use actix_web::error::{Error, PayloadError}; use actix_web::http::{header, Method, StatusCode}; use actix_web::{HttpRequest, HttpResponse}; use bytes::{Bytes, BytesMut}; -use futures_channel::oneshot::Sender; use futures_core::Stream; +use tokio::sync::oneshot::Sender; /// Do websocket handshake and start ws actor. pub fn start(actor: A, req: &HttpRequest, stream: T) -> Result diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index fd99a8376..3fc4ae1be 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -19,8 +19,8 @@ syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" [dev-dependencies] -actix-rt = "1.1.1" +actix-rt = "2.0.0-beta.1" actix-web = "3.0.0" -futures-util = { version = "0.3.5", default-features = false } +futures-util = { version = "0.3.7", default-features = false } trybuild = "1" rustversion = "1" diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index dd2bccd7f..389d09c82 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -88,17 +88,16 @@ async fn route_test() -> impl Responder { pub struct ChangeStatusCode; -impl Transform for ChangeStatusCode +impl Transform for ChangeStatusCode where - S: Service, Error = Error>, + S: Service, Error = Error>, S::Future: 'static, B: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type InitError = (); type Transform = ChangeStatusCodeMiddleware; + type InitError = (); type Future = future::Ready>; fn new_transform(&self, service: S) -> Self::Future { @@ -110,13 +109,12 @@ pub struct ChangeStatusCodeMiddleware { service: S, } -impl Service for ChangeStatusCodeMiddleware +impl Service for ChangeStatusCodeMiddleware where - S: Service, Error = Error>, + S: Service, Error = Error>, S::Future: 'static, B: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; #[allow(clippy::type_complexity)] diff --git a/awc/CHANGES.md b/awc/CHANGES.md index ee795fccb..45a38259c 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -2,7 +2,11 @@ ## Unreleased - 2021-xx-xx ### Changed -* Bumped `rand` to `0.8` +* Update `rand` to `0.8` +* Update `bytes` to `1.0`. [#1813] +* Update `rust-tls` to `0.19`. [#1813] + +[#1813]: https://github.com/actix/actix-web/pull/1813 ## 2.0.3 - 2020-11-29 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 2e92526d2..b80f1ba6b 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -37,16 +37,16 @@ rustls = ["rust-tls", "actix-http/rustls"] compress = ["actix-http/compress"] [dependencies] -actix-codec = "0.3.0" -actix-service = "1.0.6" +actix-codec = "0.4.0-beta.1" +actix-service = "2.0.0-beta.2" actix-http = "2.2.0" -actix-rt = "1.0.0" +actix-rt = "2.0.0-beta.1" base64 = "0.13" -bytes = "0.5.3" +bytes = "1" cfg-if = "1.0" derive_more = "0.99.2" -futures-core = { version = "0.3.5", default-features = false } +futures-core = { version = "0.3.7", default-features = false } log =" 0.4" mime = "0.3" percent-encoding = "2.1" @@ -55,18 +55,20 @@ serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" open-ssl = { version = "0.10", package = "openssl", optional = true } -rust-tls = { version = "0.18.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } +rust-tls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } [dev-dependencies] -actix-connect = { version = "2.0.0", features = ["openssl"] } +# TODO: actix is temporary added as dev dep for actix-macro reason. +# Can be removed when it does not impact tests. +actix = "0.11.0-beta.1" actix-web = { version = "3.0.0", features = ["openssl"] } actix-http = { version = "2.0.0", features = ["openssl"] } actix-http-test = { version = "2.0.0", features = ["openssl"] } -actix-utils = "2.0.0" -actix-server = "1.0.0" -actix-tls = { version = "2.0.0", features = ["openssl", "rustls"] } +actix-utils = "3.0.0-beta.1" +actix-server = "2.0.0-beta.2" +actix-tls = { version = "3.0.0-beta.2", features = ["openssl", "rustls"] } brotli2 = "0.3.2" flate2 = "1.0.13" -futures-util = { version = "0.3.5", default-features = false } +futures-util = { version = "0.3.7", default-features = false } env_logger = "0.7" webpki = "0.21" diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 7cd659c38..6be0112d8 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -51,7 +51,7 @@ impl ClientBuilder { /// Use custom connector service. pub fn connector(mut self, connector: T) -> Self where - T: Service + 'static, + T: Service + 'static, T::Response: Connection, ::Future: 'static, T::Future: 'static, diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 7fbe1543a..8ee239f76 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -2,9 +2,9 @@ use std::future::Future; use std::pin::Pin; use std::rc::Rc; use std::task::{Context, Poll}; -use std::{fmt, io, mem, net}; +use std::{fmt, io, net}; -use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; use actix_http::body::Body; use actix_http::client::{ Connect as ClientConnect, ConnectError, Connection, SendRequestError, @@ -70,7 +70,7 @@ pub(crate) trait Connect { impl Connect for ConnectorWrapper where - T: Service, + T: Service, T::Response: Connection, ::Io: 'static, ::Future: 'static, @@ -221,18 +221,11 @@ impl fmt::Debug for BoxedSocket { } impl AsyncRead for BoxedSocket { - unsafe fn prepare_uninitialized_buffer( - &self, - buf: &mut [mem::MaybeUninit], - ) -> bool { - self.0.as_read().prepare_uninitialized_buffer(buf) - } - fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { + buf: &mut ReadBuf<'_>, + ) -> Poll> { Pin::new(self.get_mut().0.as_read_mut()).poll_read(cx, buf) } } diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 0bcdf4307..b5ff61da5 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use std::task::{Context, Poll}; use std::time::Duration; -use actix_rt::time::{delay_for, Delay}; +use actix_rt::time::{sleep, Sleep}; use bytes::Bytes; use derive_more::From; use futures_core::Stream; @@ -56,7 +56,8 @@ impl Into for PrepForSendingError { pub enum SendClientRequest { Fut( Pin>>>, - Option, + // FIXME: use a pinned Sleep instead of box. + Option>>, bool, ), Err(Option), @@ -68,7 +69,7 @@ impl SendClientRequest { response_decompress: bool, timeout: Option, ) -> SendClientRequest { - let delay = timeout.map(delay_for); + let delay = timeout.map(|d| Box::pin(sleep(d))); SendClientRequest::Fut(send, delay, response_decompress) } } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 0024c6652..1b7413312 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -108,14 +108,14 @@ async fn test_form() { async fn test_timeout() { let srv = test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { - actix_rt::time::delay_for(Duration::from_millis(200)).await; + actix_rt::time::sleep(Duration::from_millis(200)).await; Ok::<_, Error>(HttpResponse::Ok().body(STR)) }))) }); let connector = awc::Connector::new() - .connector(actix_connect::new_connector( - actix_connect::start_default_resolver().await.unwrap(), + .connector(actix_tls::connect::new_connector( + actix_tls::connect::start_default_resolver().await.unwrap(), )) .timeout(Duration::from_secs(15)) .finish(); @@ -136,7 +136,7 @@ async fn test_timeout() { async fn test_timeout_override() { let srv = test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { - actix_rt::time::delay_for(Duration::from_millis(200)).await; + actix_rt::time::sleep(Duration::from_millis(200)).await; Ok::<_, Error>(HttpResponse::Ok().body(STR)) }))) }); diff --git a/awc/tests/test_connector.rs b/awc/tests/test_connector.rs index 888f7a900..e500801c4 100644 --- a/awc/tests/test_connector.rs +++ b/awc/tests/test_connector.rs @@ -1,7 +1,7 @@ #![cfg(feature = "openssl")] use actix_http::HttpService; use actix_http_test::test_server; -use actix_service::{map_config, ServiceFactory}; +use actix_service::{map_config, ServiceFactoryExt}; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; use open_ssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index 0df6b154c..3fa76d4a9 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use actix_http::HttpService; use actix_http_test::test_server; -use actix_service::{map_config, pipeline_factory, ServiceFactory}; +use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; use futures_util::future::ok; diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index eced5f14b..de1514042 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use actix_http::HttpService; use actix_http_test::test_server; -use actix_service::{map_config, pipeline_factory, ServiceFactory}; +use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; use futures_util::future::ok; diff --git a/benches/server.rs b/benches/server.rs index 041d0fa57..117b6136e 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -29,18 +29,22 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ fn bench_async_burst(c: &mut Criterion) { // We are using System here, since Runtime requires preinitialized tokio // Maybe add to actix_rt docs - let mut rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new("test"); - let srv = test::start(|| { - App::new() - .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) + let srv = rt.block_on(async { + test::start(|| { + App::new().service( + web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), + ) + }) }); let url = srv.url("/"); c.bench_function("get_body_async_burst", move |b| { b.iter_custom(|iters| { - let client = Client::new().get(url.clone()).freeze().unwrap(); + let client = + rt.block_on(async { Client::new().get(url.clone()).freeze().unwrap() }); let start = std::time::Instant::now(); // benchmark body diff --git a/benches/service.rs b/benches/service.rs index 8adbc8a0c..8ca6cbe28 100644 --- a/benches/service.rs +++ b/benches/service.rs @@ -23,10 +23,9 @@ use actix_web::test::{init_service, ok_service, TestRequest}; /// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us] pub fn bench_async_service(c: &mut Criterion, srv: S, name: &str) where - S: Service - + 'static, + S: Service + 'static, { - let mut rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new("test"); let srv = Rc::new(RefCell::new(srv)); let req = TestRequest::default().to_srv_request(); @@ -41,14 +40,15 @@ where b.iter_custom(|iters| { let srv = srv.clone(); // exclude request generation, it appears it takes significant time vs call (3us vs 1us) - let reqs: Vec<_> = (0..iters) + let futs = (0..iters) .map(|_| TestRequest::default().to_srv_request()) - .collect(); + .map(|req| srv.borrow_mut().call(req)); + let start = std::time::Instant::now(); // benchmark body rt.block_on(async move { - for req in reqs { - srv.borrow_mut().call(req).await.unwrap(); + for fut in futs { + fut.await.unwrap(); } }); let elapsed = start.elapsed(); @@ -67,7 +67,7 @@ async fn index(req: ServiceRequest) -> Result { // Sample results on MacBook Pro '14 // time: [2.0724 us 2.1345 us 2.2074 us] fn async_web_service(c: &mut Criterion) { - let mut rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new("test"); let srv = Rc::new(RefCell::new(rt.block_on(init_service( App::new().service(web::service("/").finish(index)), )))); @@ -83,13 +83,14 @@ fn async_web_service(c: &mut Criterion) { c.bench_function("async_web_service_direct", move |b| { b.iter_custom(|iters| { let srv = srv.clone(); - let reqs = (0..iters).map(|_| TestRequest::get().uri("/").to_request()); - + let futs = (0..iters) + .map(|_| TestRequest::get().uri("/").to_request()) + .map(|req| srv.borrow_mut().call(req)); let start = std::time::Instant::now(); // benchmark body rt.block_on(async move { - for req in reqs { - srv.borrow_mut().call(req).await.unwrap(); + for fut in futs { + fut.await.unwrap(); } }); let elapsed = start.elapsed(); diff --git a/src/app.rs b/src/app.rs index 8dd86f7ec..d41d692ee 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,10 +5,11 @@ use std::marker::PhantomData; use std::rc::Rc; use actix_http::body::{Body, MessageBody}; -use actix_http::Extensions; +use actix_http::{Extensions, Request}; use actix_service::boxed::{self, BoxServiceFactory}; use actix_service::{ - apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, Transform, + apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, + Transform, }; use futures_util::future::FutureExt; @@ -63,8 +64,8 @@ impl App where B: MessageBody, T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -268,10 +269,10 @@ where /// ``` pub fn default_service(mut self, f: F) -> Self where - F: IntoServiceFactory, + F: IntoServiceFactory, U: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, > + 'static, @@ -353,8 +354,8 @@ where mw: M, ) -> App< impl ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -364,7 +365,7 @@ where where M: Transform< T::Service, - Request = ServiceRequest, + ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -420,8 +421,8 @@ where mw: F, ) -> App< impl ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -447,12 +448,12 @@ where } } -impl IntoServiceFactory> for App +impl IntoServiceFactory, Request> for App where B: MessageBody, T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), diff --git a/src/app_service.rs b/src/app_service.rs index 4452778df..f02bb831a 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -29,8 +29,8 @@ type BoxResponse = LocalBoxFuture<'static, Result>; pub struct AppInit where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -46,22 +46,21 @@ where pub(crate) external: RefCell>, } -impl ServiceFactory for AppInit +impl ServiceFactory for AppInit where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), >, { - type Config = AppConfig; - type Request = Request; type Response = ServiceResponse; type Error = T::Error; - type InitError = T::InitError; + type Config = AppConfig; type Service = AppInitService; + type InitError = T::InitError; type Future = AppInitResult; fn new_service(&self, config: AppConfig) -> Self::Future { @@ -132,7 +131,7 @@ where #[pin_project::pin_project] pub struct AppInitResult where - T: ServiceFactory, + T: ServiceFactory, { #[pin] endpoint_fut: T::Future, @@ -155,8 +154,8 @@ where impl Future for AppInitResult where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -214,7 +213,7 @@ where /// Service to convert `Request` to a `ServiceRequest` pub struct AppInitService where - T: Service, Error = Error>, + T: Service, Error = Error>, { service: T, rmap: Rc, @@ -223,11 +222,10 @@ where pool: &'static HttpRequestPool, } -impl Service for AppInitService +impl Service for AppInitService where - T: Service, Error = Error>, + T: Service, Error = Error>, { - type Request = Request; type Response = ServiceResponse; type Error = T::Error; type Future = T::Future; @@ -263,7 +261,7 @@ where impl Drop for AppInitService where - T: Service, Error = Error>, + T: Service, Error = Error>, { fn drop(&mut self) { self.pool.clear(); @@ -275,9 +273,8 @@ pub struct AppRoutingFactory { default: Rc, } -impl ServiceFactory for AppRoutingFactory { +impl ServiceFactory for AppRoutingFactory { type Config = (); - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); @@ -386,8 +383,7 @@ pub struct AppRouting { default: Option, } -impl Service for AppRouting { - type Request = ServiceRequest; +impl Service for AppRouting { type Response = ServiceResponse; type Error = Error; type Future = BoxResponse; @@ -434,9 +430,8 @@ impl AppEntry { } } -impl ServiceFactory for AppEntry { +impl ServiceFactory for AppEntry { type Config = (); - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); diff --git a/src/config.rs b/src/config.rs index 01959daa1..4ec36952a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -105,10 +105,10 @@ impl AppService { factory: F, nested: Option>, ) where - F: IntoServiceFactory, + F: IntoServiceFactory, S: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), diff --git a/src/handler.rs b/src/handler.rs index d4b755e57..14e8cb40b 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -82,14 +82,13 @@ where } } -impl ServiceFactory for HandlerService +impl ServiceFactory for HandlerService where F: Handler, T: FromRequest, R: Future, R::Output: Responder, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Config = (); @@ -102,15 +101,14 @@ where } } -// Handler is both it's ServiceHandler and Service Type. -impl Service for HandlerService +// HandlerService is both it's ServiceFactory and Service Type. +impl Service for HandlerService where F: Handler, T: FromRequest, R: Future, R::Output: Responder, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = HandlerServiceFuture; @@ -119,7 +117,7 @@ where Poll::Ready(Ok(())) } - fn call(&mut self, req: Self::Request) -> Self::Future { + fn call(&mut self, req: ServiceRequest) -> Self::Future { let (req, mut payload) = req.into_parts(); let fut = T::from_request(&req, &mut payload); HandlerServiceFuture::Extract(fut, Some(req), self.hnd.clone()) diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 7575d7455..faff5003a 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -51,16 +51,15 @@ impl Default for Compress { } } -impl Transform for Compress +impl Transform for Compress where B: MessageBody, - S: Service, Error = Error>, + S: Service, Error = Error>, { - type Request = ServiceRequest; type Response = ServiceResponse>; type Error = Error; - type InitError = (); type Transform = CompressMiddleware; + type InitError = (); type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { @@ -76,12 +75,11 @@ pub struct CompressMiddleware { encoding: ContentEncoding, } -impl Service for CompressMiddleware +impl Service for CompressMiddleware where B: MessageBody, - S: Service, Error = Error>, + S: Service, Error = Error>, { - type Request = ServiceRequest; type Response = ServiceResponse>; type Error = Error; type Future = CompressResponse; @@ -115,7 +113,7 @@ where #[pin_project] pub struct CompressResponse where - S: Service, + S: Service, B: MessageBody, { #[pin] @@ -127,7 +125,7 @@ where impl Future for CompressResponse where B: MessageBody, - S: Service, Error = Error>, + S: Service, Error = Error>, { type Output = Result>, Error>; diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index 9061c7458..87323e325 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -31,19 +31,18 @@ impl Condition { } } -impl Transform for Condition +impl Transform for Condition where - S: Service + 'static, - T: Transform, + S: Service + 'static, + T: Transform, T::Future: 'static, T::InitError: 'static, T::Transform: 'static, { - type Request = S::Request; type Response = S::Response; type Error = S::Error; - type InitError = T::InitError; type Transform = ConditionMiddleware; + type InitError = T::InitError; type Future = LocalBoxFuture<'static, Result>; fn new_transform(&self, service: S) -> Self::Future { @@ -66,12 +65,11 @@ pub enum ConditionMiddleware { Disable(D), } -impl Service for ConditionMiddleware +impl Service for ConditionMiddleware where - E: Service, - D: Service, + E: Service, + D: Service, { - type Request = E::Request; type Response = E::Response; type Error = E::Error; type Future = Either; @@ -84,7 +82,7 @@ where } } - fn call(&mut self, req: E::Request) -> Self::Future { + fn call(&mut self, req: Req) -> Self::Future { use ConditionMiddleware::*; match self { Enable(service) => Either::Left(service.call(req)), diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index a6f1a4336..d648ad70f 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -93,12 +93,11 @@ impl DefaultHeaders { } } -impl Transform for DefaultHeaders +impl Transform for DefaultHeaders where - S: Service, Error = Error>, + S: Service, Error = Error>, S::Future: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Transform = DefaultHeadersMiddleware; @@ -118,12 +117,11 @@ pub struct DefaultHeadersMiddleware { inner: Rc, } -impl Service for DefaultHeadersMiddleware +impl Service for DefaultHeadersMiddleware where - S: Service, Error = Error>, + S: Service, Error = Error>, S::Future: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = DefaultHeaderFuture; @@ -145,7 +143,7 @@ where } #[pin_project::pin_project] -pub struct DefaultHeaderFuture { +pub struct DefaultHeaderFuture, B> { #[pin] fut: S::Future, inner: Rc, @@ -154,7 +152,7 @@ pub struct DefaultHeaderFuture { impl Future for DefaultHeaderFuture where - S: Service, Error = Error>, + S: Service, Error = Error>, { type Output = ::Output; diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index d2d3b0d8c..9e78bb7d0 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -81,17 +81,16 @@ impl ErrorHandlers { } } -impl Transform for ErrorHandlers +impl Transform for ErrorHandlers where - S: Service, Error = Error>, + S: Service, Error = Error>, S::Future: 'static, B: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type InitError = (); type Transform = ErrorHandlersMiddleware; + type InitError = (); type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { @@ -108,13 +107,12 @@ pub struct ErrorHandlersMiddleware { handlers: Rc>>>, } -impl Service for ErrorHandlersMiddleware +impl Service for ErrorHandlersMiddleware where - S: Service, Error = Error>, + S: Service, Error = Error>, S::Future: 'static, B: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = LocalBoxFuture<'static, Result>; diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 563cb6c32..2a543f66f 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -179,12 +179,11 @@ impl Default for Logger { } } -impl Transform for Logger +impl Transform for Logger where - S: Service, Error = Error>, + S: Service, Error = Error>, B: MessageBody, { - type Request = ServiceRequest; type Response = ServiceResponse>; type Error = Error; type InitError = (); @@ -216,12 +215,11 @@ pub struct LoggerMiddleware { service: S, } -impl Service for LoggerMiddleware +impl Service for LoggerMiddleware where - S: Service, Error = Error>, + S: Service, Error = Error>, B: MessageBody, { - type Request = ServiceRequest; type Response = ServiceResponse>; type Error = Error; type Future = LoggerResponse; @@ -262,19 +260,19 @@ where pub struct LoggerResponse where B: MessageBody, - S: Service, + S: Service, { #[pin] fut: S::Future, time: OffsetDateTime, format: Option, - _t: PhantomData<(B,)>, + _t: PhantomData, } impl Future for LoggerResponse where B: MessageBody, - S: Service, Error = Error>, + S: Service, Error = Error>, { type Output = Result>, Error>; diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index ad9f51079..4109364bf 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -91,16 +91,15 @@ impl NormalizePath { } } -impl Transform for NormalizePath +impl Transform for NormalizePath where - S: Service, Error = Error>, + S: Service, Error = Error>, S::Future: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type InitError = (); type Transform = NormalizePathNormalization; + type InitError = (); type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { @@ -119,12 +118,11 @@ pub struct NormalizePathNormalization { trailing_slash_behavior: TrailingSlash, } -impl Service for NormalizePathNormalization +impl Service for NormalizePathNormalization where - S: Service, Error = Error>, + S: Service, Error = Error>, S::Future: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = S::Future; diff --git a/src/resource.rs b/src/resource.rs index 29a7daa78..7d53ef936 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -9,7 +9,8 @@ use actix_http::{Error, Extensions, Response}; use actix_router::IntoPattern; use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ - apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform, + apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, + ServiceFactoryExt, Transform, }; use futures_core::future::LocalBoxFuture; @@ -78,8 +79,8 @@ impl Resource { impl Resource where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -250,8 +251,8 @@ where mw: M, ) -> Resource< impl ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -260,7 +261,7 @@ where where M: Transform< T::Service, - Request = ServiceRequest, + ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -317,8 +318,8 @@ where mw: F, ) -> Resource< impl ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -345,10 +346,10 @@ where /// default handler from `App` or `Scope`. pub fn default_service(mut self, f: F) -> Self where - F: IntoServiceFactory, + F: IntoServiceFactory, U: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, > + 'static, @@ -368,8 +369,8 @@ where impl HttpServiceFactory for Resource where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -398,11 +399,11 @@ where } } -impl IntoServiceFactory for Resource +impl IntoServiceFactory for Resource where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -425,13 +426,12 @@ pub struct ResourceFactory { default: Rc>>>, } -impl ServiceFactory for ResourceFactory { - type Config = (); - type Request = ServiceRequest; +impl ServiceFactory for ResourceFactory { type Response = ServiceResponse; type Error = Error; - type InitError = (); + type Config = (); type Service = ResourceService; + type InitError = (); type Future = CreateResourceService; fn new_service(&self, _: ()) -> Self::Future { @@ -520,8 +520,7 @@ pub struct ResourceService { default: Option, } -impl Service for ResourceService { - type Request = ServiceRequest; +impl Service for ResourceService { type Response = ServiceResponse; type Error = Error; type Future = LocalBoxFuture<'static, Result>; @@ -567,9 +566,8 @@ impl ResourceEndpoint { } } -impl ServiceFactory for ResourceEndpoint { +impl ServiceFactory for ResourceEndpoint { type Config = (); - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); @@ -585,7 +583,7 @@ impl ServiceFactory for ResourceEndpoint { mod tests { use std::time::Duration; - use actix_rt::time::delay_for; + use actix_rt::time::sleep; use actix_service::Service; use futures_util::future::ok; @@ -653,7 +651,7 @@ mod tests { async fn test_to() { let mut srv = init_service(App::new().service(web::resource("/test").to(|| async { - delay_for(Duration::from_millis(100)).await; + sleep(Duration::from_millis(100)).await; Ok::<_, Error>(HttpResponse::Ok()) }))) .await; diff --git a/src/route.rs b/src/route.rs index 00d93fce9..8a3d1da9f 100644 --- a/src/route.rs +++ b/src/route.rs @@ -18,7 +18,7 @@ use crate::HttpResponse; type BoxedRouteService = Box< dyn Service< - Request = ServiceRequest, + ServiceRequest, Response = ServiceResponse, Error = Error, Future = LocalBoxFuture<'static, Result>, @@ -27,8 +27,8 @@ type BoxedRouteService = Box< type BoxedRouteNewService = Box< dyn ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -63,9 +63,8 @@ impl Route { } } -impl ServiceFactory for Route { +impl ServiceFactory for Route { type Config = (); - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); @@ -117,8 +116,7 @@ impl RouteService { } } -impl Service for RouteService { - type Request = ServiceRequest; +impl Service for RouteService { type Response = ServiceResponse; type Error = Error; type Future = LocalBoxFuture<'static, Result>; @@ -233,7 +231,7 @@ impl Route { struct RouteNewService where - T: ServiceFactory, + T: ServiceFactory, { service: T, } @@ -241,33 +239,32 @@ where impl RouteNewService where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, >, T::Future: 'static, T::Service: 'static, - ::Future: 'static, + >::Future: 'static, { pub fn new(service: T) -> Self { RouteNewService { service } } } -impl ServiceFactory for RouteNewService +impl ServiceFactory for RouteNewService where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, >, T::Future: 'static, T::Service: 'static, - ::Future: 'static, + >::Future: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Config = (); @@ -289,16 +286,15 @@ where } } -struct RouteServiceWrapper { +struct RouteServiceWrapper> { service: T, } -impl Service for RouteServiceWrapper +impl Service for RouteServiceWrapper where T::Future: 'static, - T: Service, + T: Service, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = LocalBoxFuture<'static, Result>; @@ -316,7 +312,7 @@ where mod tests { use std::time::Duration; - use actix_rt::time::delay_for; + use actix_rt::time::sleep; use bytes::Bytes; use serde_derive::Serialize; @@ -340,16 +336,16 @@ mod tests { Err::(error::ErrorBadRequest("err")) })) .route(web::post().to(|| async { - delay_for(Duration::from_millis(100)).await; + sleep(Duration::from_millis(100)).await; Ok::<_, ()>(HttpResponse::Created()) })) .route(web::delete().to(|| async { - delay_for(Duration::from_millis(100)).await; + sleep(Duration::from_millis(100)).await; Err::(error::ErrorBadRequest("err")) })), ) .service(web::resource("/json").route(web::get().to(|| async { - delay_for(Duration::from_millis(25)).await; + sleep(Duration::from_millis(25)).await; web::Json(MyObject { name: "test".to_string(), }) diff --git a/src/scope.rs b/src/scope.rs index ce8d94159..419e572aa 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -9,7 +9,8 @@ use actix_http::{Extensions, Response}; use actix_router::{ResourceDef, ResourceInfo, Router}; use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ - apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform, + apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, + ServiceFactoryExt, Transform, }; use futures_core::future::LocalBoxFuture; @@ -88,8 +89,8 @@ impl Scope { impl Scope where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -284,10 +285,10 @@ where /// If default resource is not registered, app's default resource is being used. pub fn default_service(mut self, f: F) -> Self where - F: IntoServiceFactory, + F: IntoServiceFactory, U: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, > + 'static, @@ -317,8 +318,8 @@ where mw: M, ) -> Scope< impl ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -327,7 +328,7 @@ where where M: Transform< T::Service, - Request = ServiceRequest, + ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -382,8 +383,8 @@ where mw: F, ) -> Scope< impl ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -409,8 +410,8 @@ where impl HttpServiceFactory for Scope where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -480,9 +481,8 @@ pub struct ScopeFactory { default: Rc>>>, } -impl ServiceFactory for ScopeFactory { +impl ServiceFactory for ScopeFactory { type Config = (); - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); @@ -601,8 +601,7 @@ pub struct ScopeService { _ready: Option<(ServiceRequest, ResourceInfo)>, } -impl Service for ScopeService { - type Request = ServiceRequest; +impl Service for ScopeService { type Response = ServiceResponse; type Error = Error; type Future = LocalBoxFuture<'static, Result>; @@ -653,13 +652,12 @@ impl ScopeEndpoint { } } -impl ServiceFactory for ScopeEndpoint { - type Config = (); - type Request = ServiceRequest; +impl ServiceFactory for ScopeEndpoint { type Response = ServiceResponse; type Error = Error; - type InitError = (); + type Config = (); type Service = ScopeService; + type InitError = (); type Future = ScopeFactoryResponse; fn new_service(&self, _: ()) -> Self::Future { diff --git a/src/server.rs b/src/server.rs index be97e8a0d..fc80cbed8 100644 --- a/src/server.rs +++ b/src/server.rs @@ -20,9 +20,9 @@ use actix_service::pipeline_factory; use futures_util::future::ok; #[cfg(feature = "openssl")] -use actix_tls::openssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; +use actix_tls::accept::openssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; #[cfg(feature = "rustls")] -use actix_tls::rustls::ServerConfig as RustlsServerConfig; +use actix_tls::accept::rustls::ServerConfig as RustlsServerConfig; use crate::config::AppConfig; @@ -58,8 +58,8 @@ struct Config { pub struct HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory, + I: IntoServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, @@ -67,7 +67,7 @@ where { pub(super) factory: F, config: Arc>, - backlog: i32, + backlog: u32, sockets: Vec, builder: ServerBuilder, on_connect_fn: Option>, @@ -77,12 +77,13 @@ where impl HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory, + I: IntoServiceFactory, + S: ServiceFactory + 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, + S::Service: 'static, B: MessageBody + 'static, { /// Create new http server with application factory @@ -147,7 +148,7 @@ where /// 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, backlog: i32) -> Self { + pub fn backlog(mut self, backlog: u32) -> Self { self.backlog = backlog; self.builder = self.builder.backlog(backlog); self @@ -170,8 +171,10 @@ where /// limit the global TLS CPU usage. /// /// By default max connections is set to a 256. + #[allow(unused_variables)] pub fn max_connection_rate(self, num: usize) -> Self { - actix_tls::max_concurrent_tls_connect(num); + #[cfg(any(feature = "rustls", feature = "openssl"))] + actix_tls::accept::max_concurrent_tls_connect(num); self } @@ -603,8 +606,8 @@ where impl HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory, + I: IntoServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, @@ -639,7 +642,7 @@ where fn create_tcp_listener( addr: net::SocketAddr, - backlog: i32, + backlog: u32, ) -> io::Result { use socket2::{Domain, Protocol, Socket, Type}; let domain = match addr { @@ -649,6 +652,8 @@ fn create_tcp_listener( let socket = Socket::new(domain, Type::stream(), Some(Protocol::tcp()))?; socket.set_reuse_address(true)?; socket.bind(&addr.into())?; + // clamp backlog to max u32 that fits in i32 range + let backlog = backlog.min(i32::MAX as u32) as i32; socket.listen(backlog)?; Ok(socket.into_tcp_listener()) } diff --git a/src/service.rs b/src/service.rs index 189ba5554..85bc6123d 100644 --- a/src/service.rs +++ b/src/service.rs @@ -486,10 +486,10 @@ impl WebService { /// Set a service factory implementation and generate web service. pub fn finish(self, service: F) -> impl HttpServiceFactory where - F: IntoServiceFactory, + F: IntoServiceFactory, T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -514,8 +514,8 @@ struct WebServiceImpl { impl HttpServiceFactory for WebServiceImpl where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), diff --git a/src/test.rs b/src/test.rs index cff6c3e51..a76bae6a6 100644 --- a/src/test.rs +++ b/src/test.rs @@ -11,7 +11,7 @@ use actix_http::http::{Error as HttpError, Method, StatusCode, Uri, Version}; use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{cookie::Cookie, ws, Extensions, HttpService, Request}; use actix_router::{Path, ResourceDef, Url}; -use actix_rt::{time::delay_for, System}; +use actix_rt::{time::sleep, System}; use actix_service::{ map_config, IntoService, IntoServiceFactory, Service, ServiceFactory, }; @@ -37,16 +37,14 @@ use crate::{Error, HttpRequest, HttpResponse}; /// Create service that always responds with `HttpResponse::Ok()` pub fn ok_service( -) -> impl Service, Error = Error> -{ +) -> impl Service, Error = Error> { default_service(StatusCode::OK) } /// Create service that responds with response with specified status code pub fn default_service( status_code: StatusCode, -) -> impl Service, Error = Error> -{ +) -> impl Service, Error = Error> { (move |req: ServiceRequest| { ok(req.into_response(HttpResponse::build(status_code).finish())) }) @@ -77,12 +75,12 @@ pub fn default_service( /// ``` pub async fn init_service( app: R, -) -> impl Service, Error = E> +) -> impl Service, Error = E> where - R: IntoServiceFactory, + R: IntoServiceFactory, S: ServiceFactory< + Request, Config = AppConfig, - Request = Request, Response = ServiceResponse, Error = E, >, @@ -96,15 +94,12 @@ where /// Fallible version of init_service that allows testing data factory errors. pub(crate) async fn try_init_service( app: R, -) -> Result< - impl Service, Error = E>, - S::InitError, -> +) -> Result, Error = E>, S::InitError> where - R: IntoServiceFactory, + R: IntoServiceFactory, S: ServiceFactory< + Request, Config = AppConfig, - Request = Request, Response = ServiceResponse, Error = E, >, @@ -138,7 +133,7 @@ where /// ``` pub async fn call_service(app: &mut S, req: R) -> S::Response where - S: Service, Error = E>, + S: Service, Error = E>, E: std::fmt::Debug, { app.call(req).await.unwrap() @@ -171,7 +166,7 @@ where /// ``` pub async fn read_response(app: &mut S, req: Request) -> Bytes where - S: Service, Error = Error>, + S: Service, Error = Error>, B: MessageBody + Unpin, { let mut resp = app @@ -321,7 +316,7 @@ where /// ``` pub async fn read_response_json(app: &mut S, req: Request) -> T where - S: Service, Error = Error>, + S: Service, Error = Error>, B: MessageBody + Unpin, T: DeserializeOwned, { @@ -602,7 +597,7 @@ impl TestRequest { /// Complete request creation, calls service and waits for response future completion. pub async fn send_request(self, app: &mut S) -> S::Response where - S: Service, Error = E>, + S: Service, Error = E>, E: std::fmt::Debug, { let req = self.to_request(); @@ -639,12 +634,12 @@ impl TestRequest { pub fn start(factory: F) -> TestServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory + 'static, + I: IntoServiceFactory, + S: ServiceFactory + 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { start_with(TestServerConfig::default(), factory) @@ -678,12 +673,12 @@ where pub fn start_with(cfg: TestServerConfig, factory: F) -> TestServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory + 'static, + I: IntoServiceFactory, + S: ServiceFactory + 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { let (tx, rx) = mpsc::channel(); @@ -788,10 +783,13 @@ where }), }, } - .unwrap() - .start(); + .unwrap(); + + sys.block_on(async { + let srv = srv.start(); + tx.send((System::current(), srv, local_addr)).unwrap(); + }); - tx.send((System::current(), srv, local_addr)).unwrap(); sys.run() }); @@ -1022,7 +1020,7 @@ impl TestServer { pub async fn stop(self) { self.server.stop(true).await; self.system.stop(); - delay_for(time::Duration::from_millis(100)).await; + sleep(time::Duration::from_millis(100)).await; } } diff --git a/src/types/json.rs b/src/types/json.rs index dc0870a6e..74138ca56 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -367,7 +367,7 @@ where let json = if let Ok(Some(mime)) = req.mime_type() { mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) - || ctype.as_ref().map_or(false, |predicate| predicate(mime)) + || ctype.map_or(false, |predicate| predicate(mime)) } else { false }; diff --git a/src/web.rs b/src/web.rs index 85e5f2e7b..39dfc450a 100644 --- a/src/web.rs +++ b/src/web.rs @@ -5,7 +5,6 @@ use std::future::Future; pub use actix_http::Response as HttpResponse; pub use bytes::{Buf, BufMut, Bytes, BytesMut}; -pub use futures_channel::oneshot::Canceled; use crate::error::BlockingError; use crate::extract::FromRequest; diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index d164f4445..5eca14931 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -15,26 +15,30 @@ async fn test_start() { thread::spawn(move || { let sys = actix_rt::System::new("test"); - let srv = HttpServer::new(|| { - App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok().body("test"))), - ) - }) - .workers(1) - .backlog(1) - .max_connections(10) - .max_connection_rate(10) - .keep_alive(10) - .client_timeout(5000) - .client_shutdown(0) - .server_hostname("localhost") - .system_exit() - .disable_signals() - .bind(format!("{}", addr)) - .unwrap() - .run(); + sys.block_on(async { + let srv = HttpServer::new(|| { + App::new().service( + web::resource("/") + .route(web::to(|| HttpResponse::Ok().body("test"))), + ) + }) + .workers(1) + .backlog(1) + .max_connections(10) + .max_connection_rate(10) + .keep_alive(10) + .client_timeout(5000) + .client_shutdown(0) + .server_hostname("localhost") + .system_exit() + .disable_signals() + .bind(format!("{}", addr)) + .unwrap() + .run(); + + let _ = tx.send((srv, actix_rt::System::current())); + }); - let _ = tx.send((srv, actix_rt::System::current())); let _ = sys.run(); }); let (srv, sys) = rx.recv().unwrap(); @@ -101,10 +105,13 @@ async fn test_start_ssl() { .system_exit() .disable_signals() .bind_openssl(format!("{}", addr), builder) - .unwrap() - .run(); + .unwrap(); + + sys.block_on(async { + let srv = srv.run(); + let _ = tx.send((srv, actix_rt::System::current())); + }); - let _ = tx.send((srv, actix_rt::System::current())); let _ = sys.run(); }); let (srv, sys) = rx.recv().unwrap(); diff --git a/tests/test_server.rs b/tests/test_server.rs index c6c316f0d..2f8ce625e 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -45,7 +45,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ struct TestBody { data: Bytes, chunk_size: usize, - delay: actix_rt::time::Delay, + delay: Pin>, } impl TestBody { @@ -53,7 +53,7 @@ impl TestBody { TestBody { data, chunk_size, - delay: actix_rt::time::delay_for(std::time::Duration::from_millis(10)), + delay: Box::pin(actix_rt::time::sleep(std::time::Duration::from_millis(10))), } } } @@ -67,7 +67,8 @@ impl futures_core::stream::Stream for TestBody { ) -> Poll> { ready!(Pin::new(&mut self.delay).poll(cx)); - self.delay = actix_rt::time::delay_for(std::time::Duration::from_millis(10)); + self.delay = + Box::pin(actix_rt::time::sleep(std::time::Duration::from_millis(10))); let chunk_size = std::cmp::min(self.chunk_size, self.data.len()); let chunk = self.data.split_to(chunk_size); if chunk.is_empty() { From e1683313ec239a9ff6ebb303f62121e2b6c2b40c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 4 Jan 2021 08:32:41 +0800 Subject: [PATCH 72/81] optimize ServiceRequest methods (#1870) Co-authored-by: Rob Ede --- src/app_service.rs | 2 +- src/request.rs | 61 ++++++++++++++++++++++++++-------------------- src/service.rs | 26 +++++++++++--------- 3 files changed, 50 insertions(+), 39 deletions(-) diff --git a/src/app_service.rs b/src/app_service.rs index f02bb831a..2f120cf13 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -238,7 +238,7 @@ where let (head, payload) = req.into_parts(); let req = if let Some(mut req) = self.pool.get_request() { - let inner = Rc::get_mut(&mut req.0).unwrap(); + let inner = Rc::get_mut(&mut req.inner).unwrap(); inner.path.get_mut().update(&head.uri); inner.path.reset(); inner.head = head; diff --git a/src/request.rs b/src/request.rs index 432134cd7..82304c1af 100644 --- a/src/request.rs +++ b/src/request.rs @@ -16,7 +16,12 @@ use crate::rmap::ResourceMap; #[derive(Clone)] /// An HTTP Request -pub struct HttpRequest(pub(crate) Rc); +pub struct HttpRequest { + // *. Rc is used exclusively and NO Weak + // is allowed anywhere in the code. Weak pointer is purposely ignored when + // doing Rc's ref counter check. + pub(crate) inner: Rc, +} pub(crate) struct HttpRequestInner { pub(crate) head: Message, @@ -42,15 +47,17 @@ impl HttpRequest { let mut data = SmallVec::<[Rc; 4]>::new(); data.push(app_data); - HttpRequest(Rc::new(HttpRequestInner { - head, - path, - payload, - rmap, - config, - app_data: data, - pool, - })) + HttpRequest { + inner: Rc::new(HttpRequestInner { + head, + path, + payload, + rmap, + config, + app_data: data, + pool, + }), + } } } @@ -58,14 +65,14 @@ impl HttpRequest { /// This method returns reference to the request head #[inline] pub fn head(&self) -> &RequestHead { - &self.0.head + &self.inner.head } /// This method returns mutable reference to the request head. /// panics if multiple references of http request exists. #[inline] pub(crate) fn head_mut(&mut self) -> &mut RequestHead { - &mut Rc::get_mut(&mut self.0).unwrap().head + &mut Rc::get_mut(&mut self.inner).unwrap().head } /// Request's uri. @@ -118,12 +125,12 @@ impl HttpRequest { /// access the matched value for that segment. #[inline] pub fn match_info(&self) -> &Path { - &self.0.path + &self.inner.path } #[inline] pub(crate) fn match_info_mut(&mut self) -> &mut Path { - &mut Rc::get_mut(&mut self.0).unwrap().path + &mut Rc::get_mut(&mut self.inner).unwrap().path } /// The resource definition pattern that matched the path. Useful for logging and metrics. @@ -134,7 +141,7 @@ impl HttpRequest { /// Returns a None when no resource is fully matched, including default services. #[inline] pub fn match_pattern(&self) -> Option { - self.0.rmap.match_pattern(self.path()) + self.inner.rmap.match_pattern(self.path()) } /// The resource name that matched the path. Useful for logging and metrics. @@ -142,7 +149,7 @@ impl HttpRequest { /// Returns a None when no resource is fully matched, including default services. #[inline] pub fn match_name(&self) -> Option<&str> { - self.0.rmap.match_name(self.path()) + self.inner.rmap.match_name(self.path()) } /// Request extensions @@ -184,7 +191,7 @@ impl HttpRequest { U: IntoIterator, I: AsRef, { - self.0.rmap.url_for(&self, name, elements) + self.inner.rmap.url_for(&self, name, elements) } /// Generate url for named resource @@ -199,7 +206,7 @@ impl HttpRequest { #[inline] /// Get a reference to a `ResourceMap` of current application. pub fn resource_map(&self) -> &ResourceMap { - &self.0.rmap + &self.inner.rmap } /// Peer socket address @@ -225,7 +232,7 @@ impl HttpRequest { /// App config #[inline] pub fn app_config(&self) -> &AppConfig { - &self.0.config + &self.inner.config } /// Get an application data object stored with `App::data` or `App::app_data` @@ -237,7 +244,7 @@ impl HttpRequest { /// let opt_t = req.app_data::>(); /// ``` pub fn app_data(&self) -> Option<&T> { - for container in self.0.app_data.iter().rev() { + for container in self.inner.app_data.iter().rev() { if let Some(data) = container.get::() { return Some(data); } @@ -259,13 +266,13 @@ impl HttpMessage for HttpRequest { /// Request extensions #[inline] fn extensions(&self) -> Ref<'_, Extensions> { - self.0.head.extensions() + self.inner.head.extensions() } /// Mutable reference to a the request's extensions #[inline] fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.0.head.extensions_mut() + self.inner.head.extensions_mut() } #[inline] @@ -279,7 +286,7 @@ impl Drop for HttpRequest { // if possible, contribute to current worker's HttpRequest allocation pool // This relies on no Weak exists anywhere.(There is none) - if let Some(inner) = Rc::get_mut(&mut self.0) { + if let Some(inner) = Rc::get_mut(&mut self.inner) { let v = &mut inner.pool.0.borrow_mut(); if v.len() < 128 { // clear additional app_data and keep the root one for reuse. @@ -287,7 +294,7 @@ impl Drop for HttpRequest { // inner is borrowed mut here. get head's Extension mutably // to reduce borrow check inner.head.extensions.get_mut().clear(); - v.push(self.0.clone()); + v.push(self.inner.clone()); } } } @@ -329,8 +336,8 @@ impl fmt::Debug for HttpRequest { writeln!( f, "\nHttpRequest {:?} {}:{}", - self.0.head.version, - self.0.head.method, + self.inner.head.version, + self.inner.head.method, self.path() )?; if !self.query_string().is_empty() { @@ -369,7 +376,7 @@ impl HttpRequestPool { /// Re-use a previously allocated (but now completed/discarded) HttpRequest object. #[inline] pub(crate) fn get_request(&self) -> Option { - self.0.borrow_mut().pop().map(HttpRequest) + self.0.borrow_mut().pop().map(|inner| HttpRequest { inner }) } /// Clears all allocated HttpRequest objects. diff --git a/src/service.rs b/src/service.rs index 85bc6123d..e6f71ed06 100644 --- a/src/service.rs +++ b/src/service.rs @@ -62,7 +62,7 @@ impl ServiceRequest { /// Deconstruct request into parts pub fn into_parts(mut self) -> (HttpRequest, Payload) { - let pl = Rc::get_mut(&mut (self.0).0).unwrap().payload.take(); + let pl = Rc::get_mut(&mut (self.0).inner).unwrap().payload.take(); (self.0, pl) } @@ -73,11 +73,12 @@ impl ServiceRequest { mut req: HttpRequest, pl: Payload, ) -> Result { - if Rc::strong_count(&req.0) == 1 && Rc::weak_count(&req.0) == 0 { - Rc::get_mut(&mut req.0).unwrap().payload = pl; - Ok(ServiceRequest(req)) - } else { - Err((req, pl)) + match Rc::get_mut(&mut req.inner) { + Some(p) => { + p.payload = pl; + Ok(ServiceRequest(req)) + } + None => Err((req, pl)), } } @@ -87,7 +88,10 @@ impl ServiceRequest { /// can be re-constructed only if rc's strong pointers count eq 1 and /// weak pointers count is 0. pub fn from_request(req: HttpRequest) -> Result { - if Rc::strong_count(&req.0) == 1 && Rc::weak_count(&req.0) == 0 { + // There is no weak pointer used on HttpRequest so intentionally + // ignore the check. + if Rc::strong_count(&req.inner) == 1 { + debug_assert!(Rc::weak_count(&req.inner) == 0); Ok(ServiceRequest(req)) } else { Err(req) @@ -227,7 +231,7 @@ impl ServiceRequest { /// Counterpart to [`HttpRequest::app_data`](super::HttpRequest::app_data()). pub fn app_data(&self) -> Option<&T> { - for container in (self.0).0.app_data.iter().rev() { + for container in (self.0).inner.app_data.iter().rev() { if let Some(data) = container.get::() { return Some(data); } @@ -238,13 +242,13 @@ impl ServiceRequest { /// Set request payload. pub fn set_payload(&mut self, payload: Payload) { - Rc::get_mut(&mut (self.0).0).unwrap().payload = payload; + Rc::get_mut(&mut (self.0).inner).unwrap().payload = payload; } #[doc(hidden)] /// Add app data container to request's resolution set. pub fn add_data_container(&mut self, extensions: Rc) { - Rc::get_mut(&mut (self.0).0) + Rc::get_mut(&mut (self.0).inner) .unwrap() .app_data .push(extensions); @@ -280,7 +284,7 @@ impl HttpMessage for ServiceRequest { #[inline] fn take_payload(&mut self) -> Payload { - Rc::get_mut(&mut (self.0).0).unwrap().payload.take() + Rc::get_mut(&mut (self.0).inner).unwrap().payload.take() } } From 21f6c9d7a503726a4e35edf4b38283b8ed8de8e2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 4 Jan 2021 00:49:02 +0000 Subject: [PATCH 73/81] improve code readability --- actix-http/benches/status-line.rs | 2 +- actix-http/src/body.rs | 4 +- actix-http/src/builder.rs | 8 +- actix-http/src/client/connector.rs | 14 +- actix-http/src/client/h1proto.rs | 2 +- actix-http/src/client/h2proto.rs | 2 +- actix-http/src/client/pool.rs | 2 +- actix-http/src/h1/decoder.rs | 4 +- actix-http/src/h1/dispatcher.rs | 2 +- actix-http/src/h1/encoder.rs | 8 +- actix-http/src/h1/service.rs | 16 +-- actix-http/src/h2/dispatcher.rs | 207 +++++++++++++++-------------- actix-http/src/h2/mod.rs | 36 ++--- actix-http/src/h2/service.rs | 33 +++-- actix-http/src/service.rs | 18 +-- actix-http/src/ws/codec.rs | 2 +- actix-http/src/ws/frame.rs | 2 +- actix-http/src/ws/proto.rs | 4 +- actix-multipart/src/server.rs | 22 +-- actix-web-actors/tests/test_ws.rs | 2 +- awc/src/request.rs | 2 +- awc/src/response.rs | 12 +- awc/src/sender.rs | 4 +- awc/src/ws.rs | 2 +- awc/tests/test_client.rs | 4 +- src/app.rs | 8 +- src/app_service.rs | 4 +- src/handler.rs | 6 +- src/info.rs | 2 +- src/middleware/compress.rs | 4 +- src/middleware/logger.rs | 10 +- src/responder.rs | 4 +- src/server.rs | 10 +- src/types/payload.rs | 6 +- 34 files changed, 238 insertions(+), 230 deletions(-) diff --git a/actix-http/benches/status-line.rs b/actix-http/benches/status-line.rs index 51f840f89..252a54dea 100644 --- a/actix-http/benches/status-line.rs +++ b/actix-http/benches/status-line.rs @@ -176,7 +176,7 @@ mod _original { buf[5] = b'0'; buf[7] = b'9'; } - _ => (), + _ => {}, } let mut curr: isize = 12; diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index c5d831c45..9636f2941 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -371,7 +371,7 @@ impl MessageBody for String { pub struct BodyStream { #[pin] stream: S, - _t: PhantomData, + _phantom: PhantomData, } impl BodyStream @@ -382,7 +382,7 @@ where pub fn new(stream: S) -> Self { BodyStream { stream, - _t: PhantomData, + _phantom: PhantomData, } } } diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index ecb4327df..fa430c4fe 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -28,7 +28,7 @@ pub struct HttpServiceBuilder { expect: X, upgrade: Option, on_connect_ext: Option>>, - _t: PhantomData, + _phantom: PhantomData, } impl HttpServiceBuilder @@ -49,7 +49,7 @@ where expect: ExpectHandler, upgrade: None, on_connect_ext: None, - _t: PhantomData, + _phantom: PhantomData, } } } @@ -138,7 +138,7 @@ where expect: expect.into_factory(), upgrade: self.upgrade, on_connect_ext: self.on_connect_ext, - _t: PhantomData, + _phantom: PhantomData, } } @@ -163,7 +163,7 @@ where expect: self.expect, upgrade: Some(upgrade.into_factory()), on_connect_ext: self.on_connect_ext, - _t: PhantomData, + _phantom: PhantomData, } } diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index b638336f7..326a2fc60 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -52,7 +52,7 @@ pub struct Connector { config: ConnectorConfig, #[allow(dead_code)] ssl: SslConnector, - _t: PhantomData, + _phantom: PhantomData, } trait Io: AsyncRead + AsyncWrite + Unpin {} @@ -72,7 +72,7 @@ impl Connector<(), ()> { ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]), connector: default_connector(), config: ConnectorConfig::default(), - _t: PhantomData, + _phantom: PhantomData, } } @@ -126,7 +126,7 @@ impl Connector { connector, config: self.config, ssl: self.ssl, - _t: PhantomData, + _phantom: PhantomData, } } } @@ -468,11 +468,11 @@ mod connect_impl { match req.uri.scheme_str() { Some("https") | Some("wss") => Either::Right(InnerConnectorResponseB { fut: self.ssl_pool.call(req), - _t: PhantomData, + _phantom: PhantomData, }), _ => Either::Left(InnerConnectorResponseA { fut: self.tcp_pool.call(req), - _t: PhantomData, + _phantom: PhantomData, }), } } @@ -486,7 +486,7 @@ mod connect_impl { { #[pin] fut: as Service>::Future, - _t: PhantomData, + _phantom: PhantomData, } impl Future for InnerConnectorResponseA @@ -513,7 +513,7 @@ mod connect_impl { { #[pin] fut: as Service>::Future, - _t: PhantomData, + _phantom: PhantomData, } impl Future for InnerConnectorResponseB diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 754c53968..3265394f0 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -72,7 +72,7 @@ where // send request body match body.size() { - BodySize::None | BodySize::Empty | BodySize::Sized(0) => (), + BodySize::None | BodySize::Empty | BodySize::Sized(0) => {}, _ => send_body(body, Pin::new(&mut framed_inner)).await?, }; diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 3f9a981f4..d35f9e24b 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -89,7 +89,7 @@ where CONNECTION | TRANSFER_ENCODING => continue, // http2 specific CONTENT_LENGTH if skip_len => continue, // DATE => has_date = true, - _ => (), + _ => {}, } req.headers_mut().append(key, value.clone()); } diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index f9973a850..cee823a42 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -334,7 +334,7 @@ where let mut read_buf = ReadBuf::new(&mut buf); if let ConnectionType::H1(ref mut s) = io { match Pin::new(s).poll_read(cx, &mut read_buf) { - Poll::Pending => (), + Poll::Pending => {}, Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => { if let Some(timeout) = self.config.disconnect_timeout { if let ConnectionType::H1(io) = io { diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 8e891dc5c..d3ac497b6 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -137,7 +137,7 @@ pub(crate) trait MessageType: Sized { expect = true; } } - _ => (), + _ => {}, } headers.append(name, value); @@ -685,7 +685,7 @@ mod tests { match MessageDecoder::::default().decode($e) { Err(err) => match err { ParseError::Io(_) => unreachable!("Parse error expected"), - _ => (), + _ => {}, }, _ => unreachable!("Error expected"), } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 41caea902..41e923b6e 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -736,7 +736,7 @@ where let _ = this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx); } } - Poll::Pending => (), + Poll::Pending => {}, } Ok(()) diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 4fadbb518..69800e861 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -21,7 +21,7 @@ const AVERAGE_HEADER_SIZE: usize = 30; pub(crate) struct MessageEncoder { pub length: BodySize, pub te: TransferEncoding, - _t: PhantomData, + _phantom: PhantomData, } impl Default for MessageEncoder { @@ -29,7 +29,7 @@ impl Default for MessageEncoder { MessageEncoder { length: BodySize::None, te: TransferEncoding::empty(), - _t: PhantomData, + _phantom: PhantomData, } } } @@ -118,7 +118,7 @@ pub(crate) trait MessageType: Sized { dst.put_slice(b"connection: close\r\n") } } - _ => (), + _ => {}, } // merging headers from head and extra headers. HeaderMap::new() does not allocate. @@ -148,7 +148,7 @@ pub(crate) trait MessageType: Sized { CONNECTION => continue, TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue, DATE => has_date = true, - _ => (), + _ => {}, } let k = key.as_str().as_bytes(); diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 67f1127c7..34b7e31a1 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -30,7 +30,7 @@ pub struct H1Service { expect: X, upgrade: Option, on_connect_ext: Option>>, - _t: PhantomData, + _phantom: PhantomData, } impl H1Service @@ -52,7 +52,7 @@ where expect: ExpectHandler, upgrade: None, on_connect_ext: None, - _t: PhantomData, + _phantom: PhantomData, } } } @@ -211,7 +211,7 @@ where srv: self.srv, upgrade: self.upgrade, on_connect_ext: self.on_connect_ext, - _t: PhantomData, + _phantom: PhantomData, } } @@ -227,7 +227,7 @@ where srv: self.srv, expect: self.expect, on_connect_ext: self.on_connect_ext, - _t: PhantomData, + _phantom: PhantomData, } } @@ -270,7 +270,7 @@ where upgrade: None, on_connect_ext: self.on_connect_ext.clone(), cfg: Some(self.cfg.clone()), - _t: PhantomData, + _phantom: PhantomData, } } } @@ -299,7 +299,7 @@ where upgrade: Option, on_connect_ext: Option>>, cfg: Option, - _t: PhantomData<(T, B)>, + _phantom: PhantomData<(T, B)>, } impl Future for H1ServiceResponse @@ -371,7 +371,7 @@ where upgrade: Option>, on_connect_ext: Option>>, cfg: ServiceConfig, - _t: PhantomData, + _phantom: PhantomData, } impl H1ServiceHandler @@ -398,7 +398,7 @@ where upgrade: upgrade.map(CloneableService::new), cfg, on_connect_ext, - _t: PhantomData, + _phantom: PhantomData, } } } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 4aeda942a..b8828edd0 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -1,14 +1,15 @@ -use std::convert::TryFrom; use std::future::Future; use std::marker::PhantomData; use std::net; use std::pin::Pin; use std::task::{Context, Poll}; +use std::{cmp, convert::TryFrom}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::time::{Instant, Sleep}; use actix_service::Service; use bytes::{Bytes, BytesMut}; +use futures_core::ready; use h2::server::{Connection, SendResponse}; use h2::SendStream; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; @@ -27,7 +28,7 @@ use crate::Extensions; const CHUNK_SIZE: usize = 16_384; -/// Dispatcher for HTTP/2 protocol +/// Dispatcher for HTTP/2 protocol. #[pin_project::pin_project] pub struct Dispatcher where @@ -42,7 +43,7 @@ where peer_addr: Option, ka_expire: Instant, ka_timer: Option, - _t: PhantomData, + _phantom: PhantomData, } impl Dispatcher @@ -50,7 +51,6 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, S::Error: Into, - // S::Future: 'static, S::Response: Into>, B: MessageBody, { @@ -86,7 +86,7 @@ where on_connect_data, ka_expire, ka_timer, - _t: PhantomData, + _phantom: PhantomData, } } } @@ -107,10 +107,12 @@ where let this = self.get_mut(); loop { - match Pin::new(&mut this.connection).poll_accept(cx) { - Poll::Ready(None) => return Poll::Ready(Ok(())), - Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())), - Poll::Ready(Some(Ok((req, res)))) => { + match ready!(Pin::new(&mut this.connection).poll_accept(cx)) { + None => return Poll::Ready(Ok(())), + + Some(Err(err)) => return Poll::Ready(Err(err.into())), + + Some(Ok((req, res))) => { // update keep-alive expire if this.ka_timer.is_some() { if let Some(expire) = this.config.keep_alive_expire() { @@ -119,11 +121,9 @@ where } let (parts, body) = req.into_parts(); - let mut req = Request::with_payload(Payload::< - crate::payload::PayloadStream, - >::H2( - crate::h2::Payload::new(body) - )); + let pl = crate::h2::Payload::new(body); + let pl = Payload::::H2(pl); + let mut req = Request::with_payload(pl); let head = &mut req.head_mut(); head.uri = parts.uri; @@ -135,22 +135,18 @@ where // merge on_connect_ext data into request extensions req.extensions_mut().drain_from(&mut this.on_connect_data); - actix_rt::spawn(ServiceResponse::< - S::Future, - S::Response, - S::Error, - B, - > { + let svc = ServiceResponse:: { state: ServiceResponseState::ServiceCall( this.service.call(req), Some(res), ), config: this.config.clone(), buffer: None, - _t: PhantomData, - }); + _phantom: PhantomData, + }; + + actix_rt::spawn(svc); } - Poll::Pending => return Poll::Pending, } } } @@ -162,7 +158,7 @@ struct ServiceResponse { state: ServiceResponseState, config: ServiceConfig, buffer: Option, - _t: PhantomData<(I, E)>, + _phantom: PhantomData<(I, E)>, } #[pin_project::pin_project(project = ServiceResponseStateProj)] @@ -199,8 +195,9 @@ where skip_len = true; *size = BodySize::Stream; } - _ => (), + _ => {} } + let _ = match size { BodySize::None | BodySize::Stream => None, BodySize::Empty => res @@ -215,11 +212,13 @@ where // copy headers for (key, value) in head.headers.iter() { match *key { - CONNECTION | TRANSFER_ENCODING => continue, // http2 specific + // omit HTTP/1 only headers + CONNECTION | TRANSFER_ENCODING => continue, CONTENT_LENGTH if skip_len => continue, DATE => has_date = true, - _ => (), + _ => {} } + res.headers_mut().append(key, value.clone()); } @@ -251,109 +250,117 @@ where let mut this = self.as_mut().project(); match this.state.project() { - ServiceResponseStateProj::ServiceCall(call, send) => match call.poll(cx) { - Poll::Ready(Ok(res)) => { - let (res, body) = res.into().replace_body(()); + ServiceResponseStateProj::ServiceCall(call, send) => { + match ready!(call.poll(cx)) { + Ok(res) => { + let (res, body) = res.into().replace_body(()); - let mut send = send.take().unwrap(); - let mut size = body.size(); - let h2_res = self.as_mut().prepare_response(res.head(), &mut size); - this = self.as_mut().project(); + let mut send = send.take().unwrap(); + let mut size = body.size(); + let h2_res = + self.as_mut().prepare_response(res.head(), &mut size); + this = self.as_mut().project(); - let stream = match send.send_response(h2_res, size.is_eof()) { - Err(e) => { - trace!("Error sending h2 response: {:?}", e); - return Poll::Ready(()); + let stream = match send.send_response(h2_res, size.is_eof()) { + Err(e) => { + trace!("Error sending HTTP/2 response: {:?}", e); + return Poll::Ready(()); + } + Ok(stream) => stream, + }; + + if size.is_eof() { + Poll::Ready(()) + } else { + this.state + .set(ServiceResponseState::SendPayload(stream, body)); + self.poll(cx) } - Ok(stream) => stream, - }; + } - if size.is_eof() { - Poll::Ready(()) - } else { - this.state - .set(ServiceResponseState::SendPayload(stream, body)); - self.poll(cx) + Err(e) => { + let res: Response = e.into().into(); + let (res, body) = res.replace_body(()); + + let mut send = send.take().unwrap(); + let mut size = body.size(); + let h2_res = + self.as_mut().prepare_response(res.head(), &mut size); + this = self.as_mut().project(); + + let stream = match send.send_response(h2_res, size.is_eof()) { + Err(e) => { + trace!("Error sending HTTP/2 response: {:?}", e); + return Poll::Ready(()); + } + Ok(stream) => stream, + }; + + if size.is_eof() { + Poll::Ready(()) + } else { + this.state.set(ServiceResponseState::SendPayload( + stream, + body.into_body(), + )); + self.poll(cx) + } } } - Poll::Pending => Poll::Pending, - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - let (res, body) = res.replace_body(()); + } - let mut send = send.take().unwrap(); - let mut size = body.size(); - let h2_res = self.as_mut().prepare_response(res.head(), &mut size); - this = self.as_mut().project(); - - let stream = match send.send_response(h2_res, size.is_eof()) { - Err(e) => { - trace!("Error sending h2 response: {:?}", e); - return Poll::Ready(()); - } - Ok(stream) => stream, - }; - - if size.is_eof() { - Poll::Ready(()) - } else { - this.state.set(ServiceResponseState::SendPayload( - stream, - body.into_body(), - )); - self.poll(cx) - } - } - }, ServiceResponseStateProj::SendPayload(ref mut stream, ref mut body) => { loop { loop { - if let Some(ref mut buffer) = this.buffer { - match stream.poll_capacity(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(None) => return Poll::Ready(()), - Poll::Ready(Some(Ok(cap))) => { - let len = buffer.len(); - let bytes = buffer.split_to(std::cmp::min(cap, len)); + match this.buffer { + Some(ref mut buffer) => { + match ready!(stream.poll_capacity(cx)) { + None => return Poll::Ready(()), - if let Err(e) = stream.send_data(bytes, false) { + Some(Ok(cap)) => { + let len = buffer.len(); + let bytes = buffer.split_to(cmp::min(cap, len)); + + if let Err(e) = stream.send_data(bytes, false) { + warn!("{:?}", e); + return Poll::Ready(()); + } else if !buffer.is_empty() { + let cap = cmp::min(buffer.len(), CHUNK_SIZE); + stream.reserve_capacity(cap); + } else { + this.buffer.take(); + } + } + + Some(Err(e)) => { warn!("{:?}", e); return Poll::Ready(()); - } else if !buffer.is_empty() { - let cap = - std::cmp::min(buffer.len(), CHUNK_SIZE); - stream.reserve_capacity(cap); - } else { - this.buffer.take(); } } - Poll::Ready(Some(Err(e))) => { - warn!("{:?}", e); - return Poll::Ready(()); - } } - } else { - match body.as_mut().poll_next(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(None) => { + + None => match ready!(body.as_mut().poll_next(cx)) { + None => { if let Err(e) = stream.send_data(Bytes::new(), true) { warn!("{:?}", e); } return Poll::Ready(()); } - Poll::Ready(Some(Ok(chunk))) => { - stream.reserve_capacity(std::cmp::min( + + Some(Ok(chunk)) => { + stream.reserve_capacity(cmp::min( chunk.len(), CHUNK_SIZE, )); *this.buffer = Some(chunk); } - Poll::Ready(Some(Err(e))) => { + + Some(Err(e)) => { error!("Response payload stream error: {:?}", e); return Poll::Ready(()); } - } + }, } } } diff --git a/actix-http/src/h2/mod.rs b/actix-http/src/h2/mod.rs index b00969227..c05ee609d 100644 --- a/actix-http/src/h2/mod.rs +++ b/actix-http/src/h2/mod.rs @@ -1,9 +1,12 @@ -//! HTTP/2 implementation -use std::pin::Pin; -use std::task::{Context, Poll}; +//! HTTP/2 implementation. + +use std::{ + pin::Pin, + task::{Context, Poll}, +}; use bytes::Bytes; -use futures_core::Stream; +use futures_core::{ready, Stream}; use h2::RecvStream; mod dispatcher; @@ -13,14 +16,14 @@ pub use self::dispatcher::Dispatcher; pub use self::service::H2Service; use crate::error::PayloadError; -/// H2 receive stream +/// HTTP/2 peer stream. pub struct Payload { - pl: RecvStream, + stream: RecvStream, } impl Payload { - pub(crate) fn new(pl: RecvStream) -> Self { - Self { pl } + pub(crate) fn new(stream: RecvStream) -> Self { + Self { stream } } } @@ -33,18 +36,17 @@ impl Stream for Payload { ) -> Poll> { let this = self.get_mut(); - match Pin::new(&mut this.pl).poll_data(cx) { - Poll::Ready(Some(Ok(chunk))) => { + match ready!(Pin::new(&mut this.stream).poll_data(cx)) { + Some(Ok(chunk)) => { let len = chunk.len(); - if let Err(err) = this.pl.flow_control().release_capacity(len) { - Poll::Ready(Some(Err(err.into()))) - } else { - Poll::Ready(Some(Ok(chunk))) + + match this.stream.flow_control().release_capacity(len) { + Ok(()) => Poll::Ready(Some(Ok(chunk))), + Err(err) => Poll::Ready(Some(Err(err.into()))), } } - Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err.into()))), - Poll::Pending => Poll::Pending, - Poll::Ready(None) => Poll::Ready(None), + Some(Err(err)) => Poll::Ready(Some(Err(err.into()))), + None => Poll::Ready(None), } } } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 719f3622c..462f5c2c1 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -26,12 +26,12 @@ use crate::{ConnectCallback, Extensions}; use super::dispatcher::Dispatcher; -/// `ServiceFactory` implementation for HTTP2 transport +/// `ServiceFactory` implementation for HTTP/2 transport pub struct H2Service { srv: S, cfg: ServiceConfig, on_connect_ext: Option>>, - _t: PhantomData<(T, B)>, + _phantom: PhantomData<(T, B)>, } impl H2Service @@ -42,7 +42,7 @@ where >::Future: 'static, B: MessageBody + 'static, { - /// Create new `HttpService` instance with config. + /// Create new `H2Service` instance with config. pub(crate) fn with_config>( cfg: ServiceConfig, service: F, @@ -51,7 +51,7 @@ where cfg, on_connect_ext: None, srv: service.into_factory(), - _t: PhantomData, + _phantom: PhantomData, } } @@ -70,7 +70,7 @@ where >::Future: 'static, B: MessageBody + 'static, { - /// Create simple tcp based service + /// Create plain TCP based service pub fn tcp( self, ) -> impl ServiceFactory< @@ -106,7 +106,7 @@ mod openssl { >::Future: 'static, B: MessageBody + 'static, { - /// Create ssl based service + /// Create OpenSSL based service pub fn openssl( self, acceptor: SslAcceptor, @@ -149,7 +149,7 @@ mod rustls { >::Future: 'static, B: MessageBody + 'static, { - /// Create openssl based service + /// Create Rustls based service pub fn rustls( self, mut config: ServerConfig, @@ -200,7 +200,7 @@ where fut: self.srv.new_service(()), cfg: Some(self.cfg.clone()), on_connect_ext: self.on_connect_ext.clone(), - _t: PhantomData, + _phantom: PhantomData, } } } @@ -215,7 +215,7 @@ where fut: S::Future, cfg: Option, on_connect_ext: Option>>, - _t: PhantomData, + _phantom: PhantomData, } impl Future for H2ServiceResponse @@ -232,14 +232,14 @@ where fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.as_mut().project(); - Poll::Ready(ready!(this.fut.poll(cx)).map(|service| { + this.fut.poll(cx).map_ok(|service| { let this = self.as_mut().project(); H2ServiceHandler::new( this.cfg.take().unwrap(), this.on_connect_ext.clone(), service, ) - })) + }) } } @@ -251,7 +251,7 @@ where srv: CloneableService, cfg: ServiceConfig, on_connect_ext: Option>>, - _t: PhantomData, + _phantom: PhantomData, } impl H2ServiceHandler @@ -271,7 +271,7 @@ where cfg, on_connect_ext, srv: CloneableService::new(srv), - _t: PhantomData, + _phantom: PhantomData, } } } @@ -363,8 +363,8 @@ where ref peer_addr, ref mut on_connect_data, ref mut handshake, - ) => match Pin::new(handshake).poll(cx) { - Poll::Ready(Ok(conn)) => { + ) => match ready!(Pin::new(handshake).poll(cx)) { + Ok(conn) => { self.state = State::Incoming(Dispatcher::new( srv.take().unwrap(), conn, @@ -375,11 +375,10 @@ where )); self.poll(cx) } - Poll::Ready(Err(err)) => { + Err(err) => { trace!("H2 handshake error: {}", err); Poll::Ready(Err(err.into())) } - Poll::Pending => Poll::Pending, }, } } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index f23115cd5..af625b1bf 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -28,7 +28,7 @@ pub struct HttpService { expect: X, upgrade: Option, on_connect_ext: Option>>, - _t: PhantomData, + _phantom: PhantomData, } impl HttpService @@ -65,7 +65,7 @@ where expect: h1::ExpectHandler, upgrade: None, on_connect_ext: None, - _t: PhantomData, + _phantom: PhantomData, } } @@ -80,7 +80,7 @@ where expect: h1::ExpectHandler, upgrade: None, on_connect_ext: None, - _t: PhantomData, + _phantom: PhantomData, } } } @@ -112,7 +112,7 @@ where srv: self.srv, upgrade: self.upgrade, on_connect_ext: self.on_connect_ext, - _t: PhantomData, + _phantom: PhantomData, } } @@ -133,7 +133,7 @@ where srv: self.srv, expect: self.expect, on_connect_ext: self.on_connect_ext, - _t: PhantomData, + _phantom: PhantomData, } } @@ -348,7 +348,7 @@ where upgrade: None, on_connect_ext: self.on_connect_ext.clone(), cfg: self.cfg.clone(), - _t: PhantomData, + _phantom: PhantomData, } } } @@ -371,7 +371,7 @@ where upgrade: Option, on_connect_ext: Option>>, cfg: ServiceConfig, - _t: PhantomData<(T, B)>, + _phantom: PhantomData<(T, B)>, } impl Future for HttpServiceResponse @@ -446,7 +446,7 @@ where upgrade: Option>, cfg: ServiceConfig, on_connect_ext: Option>>, - _t: PhantomData, + _phantom: PhantomData, } impl HttpServiceHandler @@ -474,7 +474,7 @@ where srv: CloneableService::new(srv), expect: CloneableService::new(expect), upgrade: upgrade.map(CloneableService::new), - _t: PhantomData, + _phantom: PhantomData, } } } diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index 7c9628b1a..bb68a4d76 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -184,7 +184,7 @@ impl Encoder for Codec { } } }, - Message::Nop => (), + Message::Nop => {}, } Ok(()) } diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 0598a9b4e..25231b2f3 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -125,7 +125,7 @@ impl Parser { debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); return Ok(Some((true, OpCode::Close, None))); } - _ => (), + _ => {}, } // unmask diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index fc271a8f5..06db60c67 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -222,7 +222,7 @@ mod test { macro_rules! opcode_into { ($from:expr => $opcode:pat) => { match OpCode::from($from) { - e @ $opcode => (), + e @ $opcode => {}, e => unreachable!("{:?}", e), } }; @@ -232,7 +232,7 @@ mod test { ($from:expr => $opcode:pat) => { let res: u8 = $from.into(); match res { - e @ $opcode => (), + e @ $opcode => {}, e => unreachable!("{:?}", e), } }; diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index b476f1791..73b2756e5 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -326,7 +326,7 @@ impl InnerMultipart { } } } - _ => (), + _ => {}, } // read field headers for next field @@ -835,7 +835,7 @@ mod tests { async fn test_boundary() { let headers = HeaderMap::new(); match Multipart::boundary(&headers) { - Err(MultipartError::NoContentType) => (), + Err(MultipartError::NoContentType) => {}, _ => unreachable!("should not happen"), } @@ -846,7 +846,7 @@ mod tests { ); match Multipart::boundary(&headers) { - Err(MultipartError::ParseContentType) => (), + Err(MultipartError::ParseContentType) => {}, _ => unreachable!("should not happen"), } @@ -856,7 +856,7 @@ mod tests { header::HeaderValue::from_static("multipart/mixed"), ); match Multipart::boundary(&headers) { - Err(MultipartError::Boundary) => (), + Err(MultipartError::Boundary) => {}, _ => unreachable!("should not happen"), } @@ -956,17 +956,17 @@ mod tests { let mut multipart = Multipart::new(&headers, payload); match multipart.next().await.unwrap() { - Ok(_) => (), + Ok(_) => {}, _ => unreachable!(), } match multipart.next().await.unwrap() { - Ok(_) => (), + Ok(_) => {}, _ => unreachable!(), } match multipart.next().await { - None => (), + None => {}, _ => unreachable!(), } } @@ -993,7 +993,7 @@ mod tests { _ => unreachable!(), } match field.next().await { - None => (), + None => {}, _ => unreachable!(), } } @@ -1010,7 +1010,7 @@ mod tests { _ => unreachable!(), } match field.next().await { - None => (), + None => {}, _ => unreachable!(), } } @@ -1018,7 +1018,7 @@ mod tests { } match multipart.next().await { - None => (), + None => {}, _ => unreachable!(), } } @@ -1066,7 +1066,7 @@ mod tests { } match multipart.next().await { - None => (), + None => {}, _ => unreachable!(), } } diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index dda9f6f0b..a8ade9ff4 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -21,7 +21,7 @@ impl StreamHandler> for Ws { ws::Message::Text(text) => ctx.text(text), ws::Message::Binary(bin) => ctx.binary(bin), ws::Message::Close(reason) => ctx.close(reason), - _ => (), + _ => {}, } } } diff --git a/awc/src/request.rs b/awc/src/request.rs index 1e49aae3c..abfd39351 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -523,7 +523,7 @@ impl ClientRequest { return Err(InvalidUrl::MissingScheme.into()); } else if let Some(scheme) = uri.scheme() { match scheme.as_str() { - "http" | "ws" | "https" | "wss" => (), + "http" | "ws" | "https" | "wss" => {}, _ => return Err(InvalidUrl::UnknownScheme.into()), } } else { diff --git a/awc/src/response.rs b/awc/src/response.rs index 8364aa556..c9886252e 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -234,7 +234,7 @@ pub struct JsonBody { length: Option, err: Option, fut: Option>, - _t: PhantomData, + _phantom: PhantomData, } impl JsonBody @@ -255,7 +255,7 @@ where length: None, fut: None, err: Some(JsonPayloadError::ContentType), - _t: PhantomData, + _phantom: PhantomData, }; } @@ -272,7 +272,7 @@ where length: len, err: None, fut: Some(ReadBody::new(req.take_payload(), 65536)), - _t: PhantomData, + _phantom: PhantomData, } } @@ -370,14 +370,14 @@ mod tests { async fn test_body() { let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish(); match req.body().await.err().unwrap() { - PayloadError::UnknownLength => (), + PayloadError::UnknownLength => {}, _ => unreachable!("error"), } let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); match req.body().await.err().unwrap() { - PayloadError::Overflow => (), + PayloadError::Overflow => {}, _ => unreachable!("error"), } @@ -390,7 +390,7 @@ mod tests { .set_payload(Bytes::from_static(b"11111111111111")) .finish(); match req.body().limit(5).await.err().unwrap() { - PayloadError::Overflow => (), + PayloadError::Overflow => {}, _ => unreachable!("error"), } } diff --git a/awc/src/sender.rs b/awc/src/sender.rs index b5ff61da5..ef65c18dd 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -86,7 +86,7 @@ impl Future for SendClientRequest { SendClientRequest::Fut(send, delay, response_decompress) => { if delay.is_some() { match Pin::new(delay.as_mut().unwrap()).poll(cx) { - Poll::Pending => (), + Poll::Pending => {}, _ => return Poll::Ready(Err(SendRequestError::Timeout)), } } @@ -127,7 +127,7 @@ impl Future for SendClientRequest { SendClientRequest::Fut(send, delay, _) => { if delay.is_some() { match Pin::new(delay.as_mut().unwrap()).poll(cx) { - Poll::Pending => (), + Poll::Pending => {}, _ => return Poll::Ready(Err(SendRequestError::Timeout)), } } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index dd43d08b3..9065302d9 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -259,7 +259,7 @@ impl WebsocketsRequest { return Err(InvalidUrl::MissingScheme.into()); } else if let Some(scheme) = uri.scheme() { match scheme.as_str() { - "http" | "ws" | "https" | "wss" => (), + "http" | "ws" | "https" | "wss" => {}, _ => return Err(InvalidUrl::UnknownScheme.into()), } } else { diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 1b7413312..114563b31 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -127,7 +127,7 @@ async fn test_timeout() { let request = client.get(srv.url("/")).send(); match request.await { - Err(SendRequestError::Timeout) => (), + Err(SendRequestError::Timeout) => {}, _ => panic!(), } } @@ -149,7 +149,7 @@ async fn test_timeout_override() { .timeout(Duration::from_millis(50)) .send(); match request.await { - Err(SendRequestError::Timeout) => (), + Err(SendRequestError::Timeout) => {}, _ => panic!(), } } diff --git a/src/app.rs b/src/app.rs index d41d692ee..fc336e857 100644 --- a/src/app.rs +++ b/src/app.rs @@ -38,7 +38,7 @@ pub struct App { data_factories: Vec, external: Vec, extensions: Extensions, - _t: PhantomData, + _phantom: PhantomData, } impl App { @@ -55,7 +55,7 @@ impl App { factory_ref: fref, external: Vec::new(), extensions: Extensions::new(), - _t: PhantomData, + _phantom: PhantomData, } } } @@ -381,7 +381,7 @@ where factory_ref: self.factory_ref, external: self.external, extensions: self.extensions, - _t: PhantomData, + _phantom: PhantomData, } } @@ -443,7 +443,7 @@ where factory_ref: self.factory_ref, external: self.external, extensions: self.extensions, - _t: PhantomData, + _phantom: PhantomData, } } } diff --git a/src/app_service.rs b/src/app_service.rs index 2f120cf13..686be6312 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -123,7 +123,7 @@ where ), config, rmap, - _t: PhantomData, + _phantom: PhantomData, } } } @@ -148,7 +148,7 @@ where data: Rc<[Box]>, extensions: Option, - _t: PhantomData, + _phantom: PhantomData, } impl Future for AppInitResult diff --git a/src/handler.rs b/src/handler.rs index 14e8cb40b..30cc59842 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -49,7 +49,7 @@ where R::Output: Responder, { hnd: F, - _t: PhantomData<(T, R)>, + _phantom: PhantomData<(T, R)>, } impl HandlerService @@ -62,7 +62,7 @@ where pub fn new(hnd: F) -> Self { Self { hnd, - _t: PhantomData, + _phantom: PhantomData, } } } @@ -77,7 +77,7 @@ where fn clone(&self) -> Self { Self { hnd: self.hnd.clone(), - _t: PhantomData, + _phantom: PhantomData, } } } diff --git a/src/info.rs b/src/info.rs index 975604041..cefe87eb4 100644 --- a/src/info.rs +++ b/src/info.rs @@ -55,7 +55,7 @@ impl ConnectionInfo { host = Some(val.trim()); } } - _ => (), + _ => {}, } } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index faff5003a..b6ad2c4b5 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -104,7 +104,7 @@ where CompressResponse { encoding, fut: self.service.call(req), - _t: PhantomData, + _phantom: PhantomData, } } } @@ -119,7 +119,7 @@ where #[pin] fut: S::Future, encoding: ContentEncoding, - _t: PhantomData, + _phantom: PhantomData, } impl Future for CompressResponse diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 2a543f66f..83ff5b9ec 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -236,7 +236,7 @@ where fut: self.service.call(req), format: None, time: OffsetDateTime::now_utc(), - _t: PhantomData, + _phantom: PhantomData, } } else { let now = OffsetDateTime::now_utc(); @@ -249,7 +249,7 @@ where fut: self.service.call(req), format: Some(format), time: now, - _t: PhantomData, + _phantom: PhantomData, } } } @@ -266,7 +266,7 @@ where fut: S::Future, time: OffsetDateTime, format: Option, - _t: PhantomData, + _phantom: PhantomData, } impl Future for LoggerResponse @@ -522,7 +522,7 @@ impl FormatText { }; *self = FormatText::Str(s.to_string()) } - _ => (), + _ => {}, } } @@ -587,7 +587,7 @@ impl FormatText { *self = s; } - _ => (), + _ => {}, } } } diff --git a/src/responder.rs b/src/responder.rs index d1c22323f..58e33f39d 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -349,14 +349,14 @@ where pub struct ResponseFuture { #[pin] fut: T, - _t: PhantomData, + _phantom: PhantomData, } impl ResponseFuture { pub fn new(fut: T) -> Self { ResponseFuture { fut, - _t: PhantomData, + _phantom: PhantomData, } } } diff --git a/src/server.rs b/src/server.rs index fc80cbed8..26089ccba 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,6 @@ use std::{ any::Any, - fmt, io, + cmp, fmt, io, marker::PhantomData, net, sync::{Arc, Mutex}, @@ -71,7 +71,7 @@ where sockets: Vec, builder: ServerBuilder, on_connect_fn: Option>, - _t: PhantomData<(S, B)>, + _phantom: PhantomData<(S, B)>, } impl HttpServer @@ -100,7 +100,7 @@ where sockets: Vec::new(), builder: ServerBuilder::default(), on_connect_fn: None, - _t: PhantomData, + _phantom: PhantomData, } } @@ -125,7 +125,7 @@ where sockets: self.sockets, builder: self.builder, on_connect_fn: Some(Arc::new(f)), - _t: PhantomData, + _phantom: PhantomData, } } @@ -653,7 +653,7 @@ fn create_tcp_listener( socket.set_reuse_address(true)?; socket.bind(&addr.into())?; // clamp backlog to max u32 that fits in i32 range - let backlog = backlog.min(i32::MAX as u32) as i32; + let backlog = cmp::min(backlog, i32::MAX as u32) as i32; socket.listen(backlog)?; Ok(socket.into_tcp_listener()) } diff --git a/src/types/payload.rs b/src/types/payload.rs index 9228b37aa..1b1c1ad73 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -539,7 +539,7 @@ mod tests { .into_parts(); let res = HttpMessageBody::new(&req, &mut pl).await; match res.err().unwrap() { - PayloadError::UnknownLength => (), + PayloadError::UnknownLength => {}, _ => unreachable!("error"), } @@ -548,7 +548,7 @@ mod tests { .into_parts(); let res = HttpMessageBody::new(&req, &mut pl).await; match res.err().unwrap() { - PayloadError::Overflow => (), + PayloadError::Overflow => {}, _ => unreachable!("error"), } @@ -563,7 +563,7 @@ mod tests { .to_http_parts(); let res = HttpMessageBody::new(&req, &mut pl).limit(5).await; match res.err().unwrap() { - PayloadError::Overflow => (), + PayloadError::Overflow => {}, _ => unreachable!("error"), } } From 2d4a17442011fb01c685cdef298cae6887f5de56 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 4 Jan 2021 01:01:35 +0000 Subject: [PATCH 74/81] fmt --- actix-http/benches/status-line.rs | 2 +- actix-http/src/client/h1proto.rs | 2 +- actix-http/src/client/h2proto.rs | 2 +- actix-http/src/client/pool.rs | 2 +- actix-http/src/h1/decoder.rs | 4 ++-- actix-http/src/h1/dispatcher.rs | 2 +- actix-http/src/h1/encoder.rs | 4 ++-- actix-http/src/ws/codec.rs | 2 +- actix-http/src/ws/frame.rs | 2 +- actix-http/src/ws/proto.rs | 4 ++-- actix-multipart/src/server.rs | 22 +++++++++++----------- actix-web-actors/tests/test_ws.rs | 2 +- awc/src/request.rs | 2 +- awc/src/response.rs | 6 +++--- awc/src/sender.rs | 4 ++-- awc/src/ws.rs | 2 +- awc/tests/test_client.rs | 4 ++-- src/info.rs | 2 +- src/middleware/logger.rs | 4 ++-- src/types/payload.rs | 6 +++--- 20 files changed, 40 insertions(+), 40 deletions(-) diff --git a/actix-http/benches/status-line.rs b/actix-http/benches/status-line.rs index 252a54dea..f62d18ed8 100644 --- a/actix-http/benches/status-line.rs +++ b/actix-http/benches/status-line.rs @@ -176,7 +176,7 @@ mod _original { buf[5] = b'0'; buf[7] = b'9'; } - _ => {}, + _ => {} } let mut curr: isize = 12; diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 3265394f0..758ad8424 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -72,7 +72,7 @@ where // send request body match body.size() { - BodySize::None | BodySize::Empty | BodySize::Sized(0) => {}, + BodySize::None | BodySize::Empty | BodySize::Sized(0) => {} _ => send_body(body, Pin::new(&mut framed_inner)).await?, }; diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index d35f9e24b..4c609ef22 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -89,7 +89,7 @@ where CONNECTION | TRANSFER_ENCODING => continue, // http2 specific CONTENT_LENGTH if skip_len => continue, // DATE => has_date = true, - _ => {}, + _ => {} } req.headers_mut().append(key, value.clone()); } diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index cee823a42..7da2b6234 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -334,7 +334,7 @@ where let mut read_buf = ReadBuf::new(&mut buf); if let ConnectionType::H1(ref mut s) = io { match Pin::new(s).poll_read(cx, &mut read_buf) { - Poll::Pending => {}, + Poll::Pending => {} Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => { if let Some(timeout) = self.config.disconnect_timeout { if let ConnectionType::H1(io) = io { diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index d3ac497b6..85379b084 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -137,7 +137,7 @@ pub(crate) trait MessageType: Sized { expect = true; } } - _ => {}, + _ => {} } headers.append(name, value); @@ -685,7 +685,7 @@ mod tests { match MessageDecoder::::default().decode($e) { Err(err) => match err { ParseError::Io(_) => unreachable!("Parse error expected"), - _ => {}, + _ => {} }, _ => unreachable!("Error expected"), } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 41e923b6e..a9510dc1e 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -736,7 +736,7 @@ where let _ = this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx); } } - Poll::Pending => {}, + Poll::Pending => {} } Ok(()) diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 69800e861..4427174ec 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -118,7 +118,7 @@ pub(crate) trait MessageType: Sized { dst.put_slice(b"connection: close\r\n") } } - _ => {}, + _ => {} } // merging headers from head and extra headers. HeaderMap::new() does not allocate. @@ -148,7 +148,7 @@ pub(crate) trait MessageType: Sized { CONNECTION => continue, TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue, DATE => has_date = true, - _ => {}, + _ => {} } let k = key.as_str().as_bytes(); diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index bb68a4d76..ba4a48bba 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -184,7 +184,7 @@ impl Encoder for Codec { } } }, - Message::Nop => {}, + Message::Nop => {} } Ok(()) } diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 25231b2f3..8c49524e6 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -125,7 +125,7 @@ impl Parser { debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); return Ok(Some((true, OpCode::Close, None))); } - _ => {}, + _ => {} } // unmask diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 06db60c67..345681429 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -222,7 +222,7 @@ mod test { macro_rules! opcode_into { ($from:expr => $opcode:pat) => { match OpCode::from($from) { - e @ $opcode => {}, + e @ $opcode => {} e => unreachable!("{:?}", e), } }; @@ -232,7 +232,7 @@ mod test { ($from:expr => $opcode:pat) => { let res: u8 = $from.into(); match res { - e @ $opcode => {}, + e @ $opcode => {} e => unreachable!("{:?}", e), } }; diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 73b2756e5..2cde5e8cf 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -326,7 +326,7 @@ impl InnerMultipart { } } } - _ => {}, + _ => {} } // read field headers for next field @@ -835,7 +835,7 @@ mod tests { async fn test_boundary() { let headers = HeaderMap::new(); match Multipart::boundary(&headers) { - Err(MultipartError::NoContentType) => {}, + Err(MultipartError::NoContentType) => {} _ => unreachable!("should not happen"), } @@ -846,7 +846,7 @@ mod tests { ); match Multipart::boundary(&headers) { - Err(MultipartError::ParseContentType) => {}, + Err(MultipartError::ParseContentType) => {} _ => unreachable!("should not happen"), } @@ -856,7 +856,7 @@ mod tests { header::HeaderValue::from_static("multipart/mixed"), ); match Multipart::boundary(&headers) { - Err(MultipartError::Boundary) => {}, + Err(MultipartError::Boundary) => {} _ => unreachable!("should not happen"), } @@ -956,17 +956,17 @@ mod tests { let mut multipart = Multipart::new(&headers, payload); match multipart.next().await.unwrap() { - Ok(_) => {}, + Ok(_) => {} _ => unreachable!(), } match multipart.next().await.unwrap() { - Ok(_) => {}, + Ok(_) => {} _ => unreachable!(), } match multipart.next().await { - None => {}, + None => {} _ => unreachable!(), } } @@ -993,7 +993,7 @@ mod tests { _ => unreachable!(), } match field.next().await { - None => {}, + None => {} _ => unreachable!(), } } @@ -1010,7 +1010,7 @@ mod tests { _ => unreachable!(), } match field.next().await { - None => {}, + None => {} _ => unreachable!(), } } @@ -1018,7 +1018,7 @@ mod tests { } match multipart.next().await { - None => {}, + None => {} _ => unreachable!(), } } @@ -1066,7 +1066,7 @@ mod tests { } match multipart.next().await { - None => {}, + None => {} _ => unreachable!(), } } diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index a8ade9ff4..a5233e5e0 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -21,7 +21,7 @@ impl StreamHandler> for Ws { ws::Message::Text(text) => ctx.text(text), ws::Message::Binary(bin) => ctx.binary(bin), ws::Message::Close(reason) => ctx.close(reason), - _ => {}, + _ => {} } } } diff --git a/awc/src/request.rs b/awc/src/request.rs index abfd39351..51c3f5190 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -523,7 +523,7 @@ impl ClientRequest { return Err(InvalidUrl::MissingScheme.into()); } else if let Some(scheme) = uri.scheme() { match scheme.as_str() { - "http" | "ws" | "https" | "wss" => {}, + "http" | "ws" | "https" | "wss" => {} _ => return Err(InvalidUrl::UnknownScheme.into()), } } else { diff --git a/awc/src/response.rs b/awc/src/response.rs index c9886252e..a32412b23 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -370,14 +370,14 @@ mod tests { async fn test_body() { let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish(); match req.body().await.err().unwrap() { - PayloadError::UnknownLength => {}, + PayloadError::UnknownLength => {} _ => unreachable!("error"), } let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); match req.body().await.err().unwrap() { - PayloadError::Overflow => {}, + PayloadError::Overflow => {} _ => unreachable!("error"), } @@ -390,7 +390,7 @@ mod tests { .set_payload(Bytes::from_static(b"11111111111111")) .finish(); match req.body().limit(5).await.err().unwrap() { - PayloadError::Overflow => {}, + PayloadError::Overflow => {} _ => unreachable!("error"), } } diff --git a/awc/src/sender.rs b/awc/src/sender.rs index ef65c18dd..d4d3d9b72 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -86,7 +86,7 @@ impl Future for SendClientRequest { SendClientRequest::Fut(send, delay, response_decompress) => { if delay.is_some() { match Pin::new(delay.as_mut().unwrap()).poll(cx) { - Poll::Pending => {}, + Poll::Pending => {} _ => return Poll::Ready(Err(SendRequestError::Timeout)), } } @@ -127,7 +127,7 @@ impl Future for SendClientRequest { SendClientRequest::Fut(send, delay, _) => { if delay.is_some() { match Pin::new(delay.as_mut().unwrap()).poll(cx) { - Poll::Pending => {}, + Poll::Pending => {} _ => return Poll::Ready(Err(SendRequestError::Timeout)), } } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 9065302d9..a1fa07d8d 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -259,7 +259,7 @@ impl WebsocketsRequest { return Err(InvalidUrl::MissingScheme.into()); } else if let Some(scheme) = uri.scheme() { match scheme.as_str() { - "http" | "ws" | "https" | "wss" => {}, + "http" | "ws" | "https" | "wss" => {} _ => return Err(InvalidUrl::UnknownScheme.into()), } } else { diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 114563b31..6cae77a49 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -127,7 +127,7 @@ async fn test_timeout() { let request = client.get(srv.url("/")).send(); match request.await { - Err(SendRequestError::Timeout) => {}, + Err(SendRequestError::Timeout) => {} _ => panic!(), } } @@ -149,7 +149,7 @@ async fn test_timeout_override() { .timeout(Duration::from_millis(50)) .send(); match request.await { - Err(SendRequestError::Timeout) => {}, + Err(SendRequestError::Timeout) => {} _ => panic!(), } } diff --git a/src/info.rs b/src/info.rs index cefe87eb4..75ebf67eb 100644 --- a/src/info.rs +++ b/src/info.rs @@ -55,7 +55,7 @@ impl ConnectionInfo { host = Some(val.trim()); } } - _ => {}, + _ => {} } } } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 83ff5b9ec..c952aeac9 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -522,7 +522,7 @@ impl FormatText { }; *self = FormatText::Str(s.to_string()) } - _ => {}, + _ => {} } } @@ -587,7 +587,7 @@ impl FormatText { *self = s; } - _ => {}, + _ => {} } } } diff --git a/src/types/payload.rs b/src/types/payload.rs index 1b1c1ad73..14457176d 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -539,7 +539,7 @@ mod tests { .into_parts(); let res = HttpMessageBody::new(&req, &mut pl).await; match res.err().unwrap() { - PayloadError::UnknownLength => {}, + PayloadError::UnknownLength => {} _ => unreachable!("error"), } @@ -548,7 +548,7 @@ mod tests { .into_parts(); let res = HttpMessageBody::new(&req, &mut pl).await; match res.err().unwrap() { - PayloadError::Overflow => {}, + PayloadError::Overflow => {} _ => unreachable!("error"), } @@ -563,7 +563,7 @@ mod tests { .to_http_parts(); let res = HttpMessageBody::new(&req, &mut pl).limit(5).await; match res.err().unwrap() { - PayloadError::Overflow => {}, + PayloadError::Overflow => {} _ => unreachable!("error"), } } From 007a1459884ea36c97a2e8cf2f29ec9b63396b4e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 4 Jan 2021 04:29:07 +0000 Subject: [PATCH 75/81] use ahash for internal hashmaps --- Cargo.toml | 2 +- src/middleware/errhandlers.rs | 8 ++++---- src/rmap.rs | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 165004447..5388de4ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,12 +87,12 @@ actix-web-codegen = "0.4.0" actix-http = "2.2.0" awc = { version = "2.0.3", default-features = false } +ahash = "0.6" bytes = "1" derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } -fxhash = "0.2.1" log = "0.4" mime = "0.3" socket2 = "0.3.16" diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index 9e78bb7d0..d25b9c2ef 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -4,7 +4,7 @@ use std::task::{Context, Poll}; use actix_service::{Service, Transform}; use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; -use fxhash::FxHashMap; +use ahash::AHashMap; use crate::dev::{ServiceRequest, ServiceResponse}; use crate::error::{Error, Result}; @@ -52,13 +52,13 @@ type ErrorHandler = dyn Fn(ServiceResponse) -> Result { - handlers: Rc>>>, + handlers: Rc>>>, } impl Default for ErrorHandlers { fn default() -> Self { ErrorHandlers { - handlers: Rc::new(FxHashMap::default()), + handlers: Rc::new(AHashMap::default()), } } } @@ -104,7 +104,7 @@ where #[doc(hidden)] pub struct ErrorHandlersMiddleware { service: S, - handlers: Rc>>>, + handlers: Rc>>>, } impl Service for ErrorHandlersMiddleware diff --git a/src/rmap.rs b/src/rmap.rs index 6827a11b2..3c8805d57 100644 --- a/src/rmap.rs +++ b/src/rmap.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::rc::{Rc, Weak}; use actix_router::ResourceDef; -use fxhash::FxHashMap; +use ahash::AHashMap; use url::Url; use crate::error::UrlGenerationError; @@ -12,7 +12,7 @@ use crate::request::HttpRequest; pub struct ResourceMap { root: ResourceDef, parent: RefCell>, - named: FxHashMap, + named: AHashMap, patterns: Vec<(ResourceDef, Option>)>, } @@ -21,7 +21,7 @@ impl ResourceMap { ResourceMap { root, parent: RefCell::new(Weak::new()), - named: FxHashMap::default(), + named: AHashMap::default(), patterns: Vec::new(), } } From 36aee18c64d0c1f79b796f2eca2d499cee56e6c1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 4 Jan 2021 04:33:15 +0000 Subject: [PATCH 76/81] fmt --- src/middleware/errhandlers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index d25b9c2ef..8503b3e1e 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -3,8 +3,8 @@ use std::rc::Rc; use std::task::{Context, Poll}; use actix_service::{Service, Transform}; -use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; use ahash::AHashMap; +use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; use crate::dev::{ServiceRequest, ServiceResponse}; use crate::error::{Error, Result}; From 7d632d0b7b19baa761e94ba82c48cea292b31a59 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 4 Jan 2021 11:27:32 +0000 Subject: [PATCH 77/81] use ByteString as container for websocket text message (#1864) --- actix-http/CHANGES.md | 8 +-- actix-http/Cargo.toml | 3 +- actix-http/src/ws/codec.rs | 57 +++++++++++------- actix-http/src/ws/mod.rs | 99 ++++++++++++++++++------------- actix-http/src/ws/proto.rs | 8 ++- actix-http/tests/test_ws.rs | 7 +-- actix-web-actors/CHANGES.md | 2 + actix-web-actors/Cargo.toml | 1 + actix-web-actors/src/ws.rs | 32 +++++----- actix-web-actors/tests/test_ws.rs | 5 +- awc/src/lib.rs | 2 +- awc/src/ws.rs | 2 +- awc/tests/test_ws.rs | 7 +-- 13 files changed, 131 insertions(+), 102 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 147285ddf..eadbf6f46 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,14 +2,11 @@ ## Unreleased - 2021-xx-xx ### Changed -* Bumped `rand` to `0.8`. * Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +* Bumped `rand` to `0.8`. * Update `bytes` to `1.0`. [#1813] * Update `h2` to `0.3`. [#1813] - - -[#1813]: https://github.com/actix/actix-web/pull/1813 - +* The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864] ### Removed * Deprecated `on_connect` methods have been removed. Prefer the new @@ -22,6 +19,7 @@ [#1813]: https://github.com/actix/actix-web/pull/1813 [#1857]: https://github.com/actix/actix-web/pull/1857 +[#1864]: https://github.com/actix/actix-web/pull/1864 ## 2.2.0 - 2020-11-25 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e98bcf76d..e80800d06 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -51,9 +51,10 @@ actix = { version = "0.11.0-beta.1", optional = true } base64 = "0.13" bitflags = "1.2" bytes = "1" +bytestring = "1" cookie = { version = "0.14.1", features = ["percent-encode"] } copyless = "0.1.4" -derive_more = "0.99.2" +derive_more = "0.99.5" either = "1.5.3" encoding_rs = "0.8" futures-channel = { version = "0.3.7", default-features = false } diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index ba4a48bba..84f5b3c73 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -1,47 +1,60 @@ use actix_codec::{Decoder, Encoder}; +use bitflags::bitflags; use bytes::{Bytes, BytesMut}; +use bytestring::ByteString; use super::frame::Parser; use super::proto::{CloseReason, OpCode}; use super::ProtocolError; -/// `WebSocket` Message +/// A WebSocket message. #[derive(Debug, PartialEq)] pub enum Message { - /// Text message - Text(String), - /// Binary message + /// Text message. + Text(ByteString), + + /// Binary message. Binary(Bytes), - /// Continuation + + /// Continuation. Continuation(Item), - /// Ping message + + /// Ping message. Ping(Bytes), - /// Pong message + + /// Pong message. Pong(Bytes), - /// Close message with optional reason + + /// Close message with optional reason. Close(Option), - /// No-op. Useful for actix-net services + + /// No-op. Useful for low-level services. Nop, } -/// `WebSocket` frame +/// A WebSocket frame. #[derive(Debug, PartialEq)] pub enum Frame { - /// Text frame, codec does not verify utf8 encoding + /// Text frame. Note that the codec does not validate UTF-8 encoding. Text(Bytes), - /// Binary frame + + /// Binary frame. Binary(Bytes), - /// Continuation + + /// Continuation. Continuation(Item), - /// Ping message + + /// Ping message. Ping(Bytes), - /// Pong message + + /// Pong message. Pong(Bytes), - /// Close message with optional reason + + /// Close message with optional reason. Close(Option), } -/// `WebSocket` continuation item +/// A `WebSocket` continuation item. #[derive(Debug, PartialEq)] pub enum Item { FirstText(Bytes), @@ -51,13 +64,13 @@ pub enum Item { } #[derive(Debug, Copy, Clone)] -/// WebSockets protocol codec +/// WebSocket protocol codec. pub struct Codec { flags: Flags, max_size: usize, } -bitflags::bitflags! { +bitflags! { struct Flags: u8 { const SERVER = 0b0000_0001; const CONTINUATION = 0b0000_0010; @@ -66,7 +79,7 @@ bitflags::bitflags! { } impl Codec { - /// Create new websocket frames decoder + /// Create new websocket frames decoder. pub fn new() -> Codec { Codec { max_size: 65_536, @@ -74,9 +87,9 @@ impl Codec { } } - /// Set max frame size + /// Set max frame size. /// - /// By default max size is set to 64kb + /// By default max size is set to 64kb. pub fn max_size(mut self, size: usize) -> Self { self.max_size = size; self diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index cd212fb7e..a2b093ce4 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -1,11 +1,11 @@ //! 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. +//! 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 derive_more::{Display, From}; +use derive_more::{Display, Error, From}; use http::{header, Method, StatusCode}; use crate::error::ResponseError; @@ -23,86 +23,103 @@ pub use self::dispatcher::Dispatcher; pub use self::frame::Parser; pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode}; -/// Websocket protocol errors -#[derive(Debug, Display, From)] +/// WebSocket protocol errors. +#[derive(Debug, Display, From, Error)] pub enum ProtocolError { - /// Received an unmasked frame from client - #[display(fmt = "Received an unmasked frame from client")] + /// Received an unmasked frame from client. + #[display(fmt = "Received an unmasked frame from client.")] UnmaskedFrame, - /// Received a masked frame from server - #[display(fmt = "Received a masked frame from server")] + + /// Received a masked frame from server. + #[display(fmt = "Received a masked frame from server.")] MaskedFrame, - /// Encountered invalid opcode - #[display(fmt = "Invalid opcode: {}", _0)] - InvalidOpcode(u8), + + /// Encountered invalid opcode. + #[display(fmt = "Invalid opcode: {}.", _0)] + InvalidOpcode(#[error(not(source))] u8), + /// Invalid control frame length - #[display(fmt = "Invalid control frame length: {}", _0)] - InvalidLength(usize), - /// Bad web socket op code - #[display(fmt = "Bad web socket op code")] + #[display(fmt = "Invalid control frame length: {}.", _0)] + InvalidLength(#[error(not(source))] usize), + + /// Bad opcode. + #[display(fmt = "Bad opcode.")] BadOpCode, + /// A payload reached size limit. #[display(fmt = "A payload reached size limit.")] Overflow, - /// Continuation is not started + + /// Continuation is not started. #[display(fmt = "Continuation is not started.")] ContinuationNotStarted, - /// Received new continuation but it is already started - #[display(fmt = "Received new continuation but it is already started")] + + /// Received new continuation but it is already started. + #[display(fmt = "Received new continuation but it is already started.")] ContinuationStarted, - /// Unknown continuation fragment - #[display(fmt = "Unknown continuation fragment.")] - ContinuationFragment(OpCode), - /// Io error - #[display(fmt = "io error: {}", _0)] + + /// Unknown continuation fragment. + #[display(fmt = "Unknown continuation fragment: {}.", _0)] + ContinuationFragment(#[error(not(source))] OpCode), + + /// I/O error. + #[display(fmt = "I/O error: {}", _0)] Io(io::Error), } -impl std::error::Error for ProtocolError {} - impl ResponseError for ProtocolError {} -/// Websocket handshake errors +/// WebSocket handshake errors #[derive(PartialEq, Debug, Display)] pub enum HandshakeError { - /// Only get method is allowed - #[display(fmt = "Method not allowed")] + /// Only get method is allowed. + #[display(fmt = "Method not allowed.")] GetMethodRequired, - /// Upgrade header if not set to websocket - #[display(fmt = "Websocket upgrade is expected")] + + /// Upgrade header if not set to websocket. + #[display(fmt = "WebSocket upgrade is expected.")] NoWebsocketUpgrade, - /// Connection header is not set to upgrade - #[display(fmt = "Connection upgrade is expected")] + + /// Connection header is not set to upgrade. + #[display(fmt = "Connection upgrade is expected.")] NoConnectionUpgrade, - /// Websocket version header is not set - #[display(fmt = "Websocket version header is required")] + + /// WebSocket version header is not set. + #[display(fmt = "WebSocket version header is required.")] NoVersionHeader, - /// Unsupported websocket version - #[display(fmt = "Unsupported version")] + + /// Unsupported websocket version. + #[display(fmt = "Unsupported version.")] UnsupportedVersion, - /// Websocket key is not set or wrong - #[display(fmt = "Unknown websocket key")] + + /// WebSocket key is not set or wrong. + #[display(fmt = "Unknown websocket key.")] BadWebsocketKey, } impl ResponseError for HandshakeError { fn error_response(&self) -> Response { - match *self { + match self { HandshakeError::GetMethodRequired => Response::MethodNotAllowed() .header(header::ALLOW, "GET") .finish(), + HandshakeError::NoWebsocketUpgrade => Response::BadRequest() .reason("No WebSocket UPGRADE header found") .finish(), + HandshakeError::NoConnectionUpgrade => Response::BadRequest() .reason("No CONNECTION upgrade") .finish(), + HandshakeError::NoVersionHeader => Response::BadRequest() .reason("Websocket version header is required") .finish(), + HandshakeError::UnsupportedVersion => Response::BadRequest() .reason("Unsupported version") .finish(), + HandshakeError::BadWebsocketKey => { Response::BadRequest().reason("Handshake error").finish() } diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 345681429..6fa3debc5 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -2,21 +2,27 @@ use std::convert::{From, Into}; use std::fmt; use self::OpCode::*; -/// Operation codes as part of rfc6455. +/// Operation codes as part of RFC6455. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum OpCode { /// Indicates a continuation frame of a fragmented message. Continue, + /// Indicates a text data frame. Text, + /// Indicates a binary data frame. Binary, + /// Indicates a close control frame. Close, + /// Indicates a ping control frame. Ping, + /// Indicates a pong control frame. Pong, + /// Indicates an invalid opcode was received. Bad, } diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index e31f2745c..976fc9164 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -74,7 +74,7 @@ async fn service(msg: ws::Frame) -> Result { let msg = match msg { ws::Frame::Ping(msg) => ws::Message::Pong(msg), ws::Frame::Text(text) => { - ws::Message::Text(String::from_utf8_lossy(&text).to_string()) + ws::Message::Text(String::from_utf8_lossy(&text).into_owned().into()) } ws::Frame::Binary(bin) => ws::Message::Binary(bin), ws::Frame::Continuation(item) => ws::Message::Continuation(item), @@ -101,10 +101,7 @@ async fn test_simple() { // client service let mut framed = srv.ws().await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); + framed.send(ws::Message::Text("text".into())).await.unwrap(); let (item, mut framed) = framed.into_future().await; assert_eq!( item.unwrap().unwrap(), diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index e47f09135..dab35953a 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -3,8 +3,10 @@ ## Unreleased - 2021-xx-xx * Update `pin-project` to `1.0`. * Update `bytes` to `1.0`. [#1813] +* `WebsocketContext::text` now takes an `Into`. [#1864] [#1813]: https://github.com/actix/actix-web/pull/1813 +[#1864]: https://github.com/actix/actix-web/pull/1864 ## 3.0.0 - 2020-09-11 * No significant changes from `3.0.0-beta.2`. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 28b9d6fa2..dac4060ba 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -22,6 +22,7 @@ actix-http = "2.0.0" actix-web = { version = "3.0.0", default-features = false } bytes = "1" +bytestring = "1" futures-core = { version = "0.3.7", default-features = false } pin-project = "1.0.0" tokio = { version = "1", features = ["sync"] } diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 9dd7bf500..60942c6c6 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -1,9 +1,10 @@ -//! Websocket integration -use std::collections::VecDeque; +//! Websocket integration. + use std::future::Future; use std::io; use std::pin::Pin; use std::task::{Context, Poll}; +use std::{collections::VecDeque, convert::TryFrom}; use actix::dev::{ AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, @@ -24,10 +25,11 @@ use actix_web::error::{Error, PayloadError}; use actix_web::http::{header, Method, StatusCode}; use actix_web::{HttpRequest, HttpResponse}; use bytes::{Bytes, BytesMut}; +use bytestring::ByteString; use futures_core::Stream; use tokio::sync::oneshot::Sender; -/// Do websocket handshake and start ws actor. +/// Perform WebSocket handshake and start actor. pub fn start(actor: A, req: &HttpRequest, stream: T) -> Result where A: Actor> @@ -38,7 +40,7 @@ where Ok(res.streaming(WebsocketContext::create(actor, stream))) } -/// Do websocket handshake and start ws actor. +/// Perform WebSocket handshake and start actor. /// /// `req` is an HTTP Request that should be requesting a websocket protocol /// change. `stream` should be a `Bytes` stream (such as @@ -338,13 +340,13 @@ where /// Send text frame #[inline] - pub fn text>(&mut self, text: T) { + pub fn text(&mut self, text: impl Into) { self.write_raw(Message::Text(text.into())); } /// Send binary frame #[inline] - pub fn binary>(&mut self, data: B) { + pub fn binary(&mut self, data: impl Into) { self.write_raw(Message::Binary(data.into())); } @@ -528,16 +530,14 @@ where } Some(frm) => { let msg = match frm { - Frame::Text(data) => Message::Text( - std::str::from_utf8(&data) - .map_err(|e| { - ProtocolError::Io(io::Error::new( - io::ErrorKind::Other, - format!("{}", e), - )) - })? - .to_string(), - ), + Frame::Text(data) => { + Message::Text(ByteString::try_from(data).map_err(|e| { + ProtocolError::Io(io::Error::new( + io::ErrorKind::Other, + format!("{}", e), + )) + })?) + } Frame::Binary(data) => Message::Binary(data), Frame::Ping(s) => Message::Ping(s), Frame::Pong(s) => Message::Pong(s), diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index a5233e5e0..7fd59a4a7 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -38,10 +38,7 @@ async fn test_simple() { // client service let mut framed = srv.ws().await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); + framed.send(ws::Message::Text("text".into())).await.unwrap(); let item = framed.next().await.unwrap().unwrap(); assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text"))); diff --git a/awc/src/lib.rs b/awc/src/lib.rs index fb6ed086a..d9db7a2cf 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -76,7 +76,7 @@ //! .await?; //! //! connection -//! .send(awc::ws::Message::Text("Echo".to_string())) +//! .send(awc::ws::Message::Text("Echo".into())) //! .await?; //! let response = connection.next().await.unwrap()?; //! # assert_eq!(response, awc::ws::Frame::Text("Echo".as_bytes().into())); diff --git a/awc/src/ws.rs b/awc/src/ws.rs index a1fa07d8d..b90d0942b 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -17,7 +17,7 @@ //! .unwrap(); //! //! connection -//! .send(ws::Message::Text("Echo".to_string())) +//! .send(ws::Message::Text("Echo".into())) //! .await //! .unwrap(); //! let response = connection.next().await.unwrap().unwrap(); diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs index 1c1068668..8eb912dac 100644 --- a/awc/tests/test_ws.rs +++ b/awc/tests/test_ws.rs @@ -11,7 +11,7 @@ async fn ws_service(req: ws::Frame) -> Result { match req { ws::Frame::Ping(msg) => Ok(ws::Message::Pong(msg)), ws::Frame::Text(text) => Ok(ws::Message::Text( - String::from_utf8(Vec::from(text.as_ref())).unwrap(), + String::from_utf8(Vec::from(text.as_ref())).unwrap().into(), )), ws::Frame::Binary(bin) => Ok(ws::Message::Binary(bin)), ws::Frame::Close(reason) => Ok(ws::Message::Close(reason)), @@ -43,10 +43,7 @@ async fn test_simple() { // client service let mut framed = srv.ws().await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); + framed.send(ws::Message::Text("text".into())).await.unwrap(); let item = framed.next().await.unwrap().unwrap(); assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text"))); From e5678733269decfeb9e1a4a9a22e8ef7da0b000b Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 4 Jan 2021 21:03:46 +0800 Subject: [PATCH 78/81] optimize message pool release (#1871) --- actix-http/src/message.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 5e53f73b6..3673017bc 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -67,7 +67,7 @@ impl Head for RequestHead { fn clear(&mut self) { self.flags = Flags::empty(); self.headers.clear(); - self.extensions.borrow_mut().clear(); + self.extensions.get_mut().clear(); } fn pool() -> &'static MessagePool { @@ -440,9 +440,11 @@ impl MessagePool { #[inline] fn get_message(&'static self) -> Message { if let Some(mut msg) = self.0.borrow_mut().pop() { - if let Some(r) = Rc::get_mut(&mut msg) { - r.clear(); - } + // Message is put in pool only when it's the last copy. + // which means it's guaranteed to be unique when popped out. + Rc::get_mut(&mut msg) + .expect("Multiple copies exist") + .clear(); Message { head: msg } } else { Message { From 93161df141544277b1f7c983a5c3a616cfd44dd3 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 5 Jan 2021 07:47:38 +0800 Subject: [PATCH 79/81] clean up body type (#1872) --- actix-http/src/body.rs | 52 ++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index 9636f2941..5d1cf7329 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -1,4 +1,3 @@ -use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; use std::{fmt, mem}; @@ -68,7 +67,7 @@ impl MessageBody for Box { #[pin_project(project = ResponseBodyProj)] pub enum ResponseBody { Body(#[pin] B), - Other(#[pin] Body), + Other(Body), } impl ResponseBody { @@ -110,7 +109,7 @@ impl MessageBody for ResponseBody { ) -> Poll>> { match self.project() { ResponseBodyProj::Body(body) => body.poll_next(cx), - ResponseBodyProj::Other(body) => body.poll_next(cx), + ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), } } } @@ -124,12 +123,11 @@ impl Stream for ResponseBody { ) -> Poll> { match self.project() { ResponseBodyProj::Body(body) => body.poll_next(cx), - ResponseBodyProj::Other(body) => body.poll_next(cx), + ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), } } } -#[pin_project(project = BodyProj)] /// Represents various types of http message body. pub enum Body { /// Empty response. `Content-Length` header is not set. @@ -168,10 +166,10 @@ impl MessageBody for Body { self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { - match self.project() { - BodyProj::None => Poll::Ready(None), - BodyProj::Empty => Poll::Ready(None), - BodyProj::Bytes(ref mut bin) => { + match self.get_mut() { + Body::None => Poll::Ready(None), + Body::Empty => Poll::Ready(None), + Body::Bytes(ref mut bin) => { let len = bin.len(); if len == 0 { Poll::Ready(None) @@ -179,7 +177,7 @@ impl MessageBody for Body { Poll::Ready(Some(Ok(mem::take(bin)))) } } - BodyProj::Message(ref mut body) => Pin::new(body.as_mut()).poll_next(cx), + Body::Message(body) => Pin::new(&mut **body).poll_next(cx), } } } @@ -266,12 +264,12 @@ where } } -impl From> for Body +impl From> for Body where S: Stream> + Unpin + 'static, E: Into + 'static, { - fn from(s: BodyStream) -> Body { + fn from(s: BodyStream) -> Body { Body::from_message(s) } } @@ -367,27 +365,21 @@ impl MessageBody for String { /// Type represent streaming body. /// Response does not contain `content-length` header and appropriate transfer encoding is used. -#[pin_project] -pub struct BodyStream { - #[pin] +pub struct BodyStream { stream: S, - _phantom: PhantomData, } -impl BodyStream +impl BodyStream where S: Stream> + Unpin, E: Into, { pub fn new(stream: S) -> Self { - BodyStream { - stream, - _phantom: PhantomData, - } + BodyStream { stream } } } -impl MessageBody for BodyStream +impl MessageBody for BodyStream where S: Stream> + Unpin, E: Into, @@ -402,13 +394,12 @@ where /// ended on a zero-length chunk, but rather proceed until the underlying /// [`Stream`] ends. fn poll_next( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { - let mut stream = self.project().stream; loop { - let stream = stream.as_mut(); - return Poll::Ready(match ready!(stream.poll_next(cx)) { + let stream = &mut self.as_mut().stream; + return Poll::Ready(match ready!(Pin::new(stream).poll_next(cx)) { Some(Ok(ref bytes)) if bytes.is_empty() => continue, opt => opt.map(|res| res.map_err(Into::into)), }); @@ -418,10 +409,8 @@ where /// 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. -#[pin_project] pub struct SizedStream { size: u64, - #[pin] stream: S, } @@ -448,13 +437,12 @@ where /// ended on a zero-length chunk, but rather proceed until the underlying /// [`Stream`] ends. fn poll_next( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { - let mut stream: Pin<&mut S> = self.project().stream; loop { - let stream = stream.as_mut(); - return Poll::Ready(match ready!(stream.poll_next(cx)) { + let stream = &mut self.as_mut().stream; + return Poll::Ready(match ready!(Pin::new(stream).poll_next(cx)) { Some(Ok(ref bytes)) if bytes.is_empty() => continue, val => val, }); From 4f5971d79ef7d3217cfaa9b82169bee950b945b9 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 5 Jan 2021 08:22:57 +0800 Subject: [PATCH 80/81] add Compat middleware (#1865) --- CHANGES.md | 7 +- src/middleware/compat.rs | 192 ++++++++++++++++++++++++++++++++++++ src/middleware/condition.rs | 4 +- src/middleware/mod.rs | 2 + 4 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 src/middleware/compat.rs diff --git a/CHANGES.md b/CHANGES.md index 32f444ec1..0077cd518 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* `Compat` middleware enabling generic response body/error type of middlewares + like `Logger` and `Compress` to be used in `middleware::Condition` + and `Resource`, `Scope` services. [#1865] + ### Changed * Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] * Bumped `rand` to `0.8`. @@ -9,7 +14,7 @@ * MSRV is now 1.46.0. [#1813]: https://github.com/actix/actix-web/pull/1813 - +[#1865]: https://github.com/actix/actix-web/pull/1865 ### Fixed * added the actual parsing error to `test::read_body_json` [#1812] diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs new file mode 100644 index 000000000..66346b6d6 --- /dev/null +++ b/src/middleware/compat.rs @@ -0,0 +1,192 @@ +//! `Middleware` for enabling any middleware to be used in `Resource`, `Scope` and `Condition`. +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use actix_http::body::Body; +use actix_http::body::{MessageBody, ResponseBody}; +use actix_service::{Service, Transform}; +use futures_core::future::LocalBoxFuture; +use futures_core::ready; + +use crate::error::Error; +use crate::service::ServiceResponse; + +/// `Middleware` for enabling any middleware to be used in `Resource`, `Scope` and `Condition`. +/// +/// +/// ## Usage +/// +/// ```rust +/// use actix_web::middleware::{Logger, Compat}; +/// use actix_web::{App, web}; +/// +/// let logger = Logger::default(); +/// +/// // this would not compile +/// // let app = App::new().service(web::scope("scoped").wrap(logger)); +/// +/// // by using scoped middleware we can use logger in scope. +/// let app = App::new().service(web::scope("scoped").wrap(Compat::new(logger))); +/// ``` +pub struct Compat { + transform: T, +} + +impl Compat { + pub fn new(transform: T) -> Self { + Self { transform } + } +} + +impl Transform for Compat +where + S: Service, + T: Transform, + T::Future: 'static, + T::Response: MapServiceResponseBody, + Error: From, +{ + type Response = ServiceResponse; + type Error = Error; + type Transform = CompatMiddleware; + type InitError = T::InitError; + type Future = LocalBoxFuture<'static, Result>; + + fn new_transform(&self, service: S) -> Self::Future { + let fut = self.transform.new_transform(service); + Box::pin(async move { + let service = fut.await?; + Ok(CompatMiddleware { service }) + }) + } +} + +pub struct CompatMiddleware { + service: S, +} + +impl Service for CompatMiddleware +where + S: Service, + S::Response: MapServiceResponseBody, + Error: From, +{ + type Response = ServiceResponse; + type Error = Error; + type Future = CompatMiddlewareFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.service.poll_ready(cx).map_err(From::from) + } + + fn call(&mut self, req: Req) -> Self::Future { + let fut = self.service.call(req); + CompatMiddlewareFuture { fut } + } +} + +#[pin_project::pin_project] +pub struct CompatMiddlewareFuture { + #[pin] + fut: Fut, +} + +impl Future for CompatMiddlewareFuture +where + Fut: Future>, + T: MapServiceResponseBody, + Error: From, +{ + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let res = ready!(self.project().fut.poll(cx))?; + Poll::Ready(Ok(res.map_body())) + } +} + +// trait for convert ServiceResponse's ResponseBody generic type +// to ResponseBody +pub trait MapServiceResponseBody { + fn map_body(self) -> ServiceResponse; +} + +impl MapServiceResponseBody for ServiceResponse { + fn map_body(self) -> ServiceResponse { + self.map_body(|_, body| ResponseBody::Other(Body::from_message(body))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use actix_service::IntoService; + + use crate::dev::ServiceRequest; + use crate::http::StatusCode; + use crate::middleware::{Compress, Condition, Logger}; + use crate::test::{call_service, init_service, TestRequest}; + use crate::{web, App, HttpResponse}; + + #[actix_rt::test] + async fn test_scope_middleware() { + let logger = Logger::default(); + let compress = Compress::default(); + + let mut srv = init_service( + App::new().service( + web::scope("app") + .wrap(Compat::new(logger)) + .wrap(Compat::new(compress)) + .service( + web::resource("/test").route(web::get().to(HttpResponse::Ok)), + ), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + } + + #[actix_rt::test] + async fn test_resource_scope_middleware() { + let logger = Logger::default(); + let compress = Compress::default(); + + let mut srv = init_service( + App::new().service( + web::resource("app/test") + .wrap(Compat::new(logger)) + .wrap(Compat::new(compress)) + .route(web::get().to(HttpResponse::Ok)), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + } + + #[actix_rt::test] + async fn test_condition_scope_middleware() { + let srv = |req: ServiceRequest| { + Box::pin(async move { + Ok(req.into_response(HttpResponse::InternalServerError().finish())) + }) + }; + + let logger = Logger::default(); + + let mut mw = Condition::new(true, Compat::new(logger)) + .new_transform(srv.into_service()) + .await + .unwrap(); + let resp = call_service(&mut mw, TestRequest::default().to_srv_request()).await; + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + } +} diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index 87323e325..04173e053 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -6,7 +6,9 @@ use futures_util::future::{ok, Either, FutureExt, LocalBoxFuture}; /// `Middleware` for conditionally enables another middleware. /// The controlled middleware must not change the `Service` interfaces. -/// This means you cannot control such middlewares like `Logger` or `Compress`. +/// +/// This means you cannot control such middlewares like `Logger` or `Compress` directly. +/// *. See `Compat` middleware for alternative. /// /// ## Usage /// diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 12c12a98c..af44fd8c7 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -5,12 +5,14 @@ mod compress; #[cfg(feature = "compress")] pub use self::compress::Compress; +mod compat; mod condition; mod defaultheaders; pub mod errhandlers; mod logger; pub mod normalize; +pub use self::compat::Compat; pub use self::condition::Condition; pub use self::defaultheaders::DefaultHeaders; pub use self::logger::Logger; From 68117543ea0ec020af7a5a7581eb4139595da90c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 5 Jan 2021 09:51:58 +0000 Subject: [PATCH 81/81] major cleanup of middleware module (#1875) * major cleanup of middleware module * update changelog --- CHANGES.md | 20 ++- MIGRATION.md | 10 ++ examples/basic.rs | 3 +- src/middleware/compat.rs | 44 ++--- src/middleware/compress.rs | 61 ++++--- src/middleware/condition.rs | 69 ++++---- .../{defaultheaders.rs => default_headers.rs} | 88 +++++---- .../{errhandlers.rs => err_handlers.rs} | 41 ++--- src/middleware/logger.rs | 167 ++++++++---------- src/middleware/mod.rs | 29 +-- src/middleware/normalize.rs | 69 ++++---- tests/test_server.rs | 21 ++- 12 files changed, 319 insertions(+), 303 deletions(-) rename src/middleware/{defaultheaders.rs => default_headers.rs} (79%) rename src/middleware/{errhandlers.rs => err_handlers.rs} (85%) diff --git a/CHANGES.md b/CHANGES.md index 0077cd518..f0b55801b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,25 +2,31 @@ ## Unreleased - 2021-xx-xx ### Added -* `Compat` middleware enabling generic response body/error type of middlewares - like `Logger` and `Compress` to be used in `middleware::Condition` - and `Resource`, `Scope` services. [#1865] +* `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and + `Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865] ### Changed * Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] * Bumped `rand` to `0.8`. * Update `rust-tls` to `0.19`. [#1813] * Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] +* The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration + guide for implications. [#1875] +* Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875] * MSRV is now 1.46.0. -[#1813]: https://github.com/actix/actix-web/pull/1813 -[#1865]: https://github.com/actix/actix-web/pull/1865 - ### Fixed -* added the actual parsing error to `test::read_body_json` [#1812] +* Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] + +### Removed +* Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now + exposed directly by the `middleware` module. [#1812]: https://github.com/actix/actix-web/pull/1812 +[#1813]: https://github.com/actix/actix-web/pull/1813 [#1852]: https://github.com/actix/actix-web/pull/1852 +[#1865]: https://github.com/actix/actix-web/pull/1865 +[#1875]: https://github.com/actix/actix-web/pull/1875 ## 3.3.2 - 2020-12-01 diff --git a/MIGRATION.md b/MIGRATION.md index 5c4650194..e01702868 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,15 @@ ## Unreleased +* The default `NormalizePath` behavior now strips trailing slashes by default. This was + previously documented to be the case in v3 but the behavior now matches. The effect is that + routes defined with trailing slashes will become inaccessible when + using `NormalizePath::default()`. + + Before: `#[get("/test/")` + After: `#[get("/test")` + + Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`. + ## 3.0.0 diff --git a/examples/basic.rs b/examples/basic.rs index 8b2bf2319..e8ad5fcdb 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -18,8 +18,7 @@ async fn no_params() -> &'static str { #[actix_web::main] async fn main() -> std::io::Result<()> { - std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); - env_logger::init(); + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); HttpServer::new(|| { App::new() diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 66346b6d6..eabd1190d 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -1,41 +1,45 @@ -//! `Middleware` for enabling any middleware to be used in `Resource`, `Scope` and `Condition`. -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; +//! For middleware documentation, see [`Compat`]. -use actix_http::body::Body; -use actix_http::body::{MessageBody, ResponseBody}; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use actix_http::body::{Body, MessageBody, ResponseBody}; use actix_service::{Service, Transform}; -use futures_core::future::LocalBoxFuture; -use futures_core::ready; +use futures_core::{future::LocalBoxFuture, ready}; -use crate::error::Error; -use crate::service::ServiceResponse; +use crate::{error::Error, service::ServiceResponse}; -/// `Middleware` for enabling any middleware to be used in `Resource`, `Scope` and `Condition`. -/// -/// -/// ## Usage +/// Middleware for enabling any middleware to be used in [`Resource::wrap`](crate::Resource::wrap), +/// [`Scope::wrap`](crate::Scope::wrap) and [`Condition`](super::Condition). /// +/// # Usage /// ```rust /// use actix_web::middleware::{Logger, Compat}; /// use actix_web::{App, web}; /// /// let logger = Logger::default(); /// -/// // this would not compile -/// // let app = App::new().service(web::scope("scoped").wrap(logger)); +/// // this would not compile because of incompatible body types +/// // let app = App::new() +/// // .service(web::scope("scoped").wrap(logger)); /// -/// // by using scoped middleware we can use logger in scope. -/// let app = App::new().service(web::scope("scoped").wrap(Compat::new(logger))); +/// // by using this middleware we can use the logger on a scope +/// let app = App::new() +/// .service(web::scope("scoped").wrap(Compat::new(logger))); /// ``` pub struct Compat { transform: T, } impl Compat { - pub fn new(transform: T) -> Self { - Self { transform } + /// Wrap a middleware to give it broader compatibility. + pub fn new(middleware: T) -> Self { + Self { + transform: middleware, + } } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index b6ad2c4b5..376719ab2 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -1,45 +1,47 @@ -//! `Middleware` for compressing response body. -use std::cmp; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::str::FromStr; -use std::task::{Context, Poll}; +//! For middleware documentation, see [`Compress`]. -use actix_http::body::MessageBody; -use actix_http::encoding::Encoder; -use actix_http::http::header::{ContentEncoding, ACCEPT_ENCODING}; -use actix_http::Error; +use std::{ + cmp, + future::Future, + marker::PhantomData, + pin::Pin, + str::FromStr, + task::{Context, Poll}, +}; + +use actix_http::{ + body::MessageBody, + encoding::Encoder, + http::header::{ContentEncoding, ACCEPT_ENCODING}, + Error, +}; use actix_service::{Service, Transform}; use futures_util::future::{ok, Ready}; use pin_project::pin_project; -use crate::dev::BodyEncoding; -use crate::service::{ServiceRequest, ServiceResponse}; +use crate::{ + dev::BodyEncoding, + service::{ServiceRequest, ServiceResponse}, +}; -#[derive(Debug, Clone)] -/// `Middleware` for compressing response body. +/// Middleware for compressing response payloads. /// -/// Use `BodyEncoding` trait for overriding response compression. -/// To disable compression set encoding to `ContentEncoding::Identity` value. +/// Use `BodyEncoding` trait for overriding response compression. To disable compression set +/// encoding to `ContentEncoding::Identity`. /// +/// # Usage /// ```rust /// use actix_web::{web, middleware, App, HttpResponse}; /// -/// fn main() { -/// let app = App::new() -/// .wrap(middleware::Compress::default()) -/// .service( -/// web::resource("/test") -/// .route(web::get().to(|| HttpResponse::Ok())) -/// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) -/// ); -/// } +/// let app = App::new() +/// .wrap(middleware::Compress::default()) +/// .default_service(web::to(|| HttpResponse::NotFound())); /// ``` +#[derive(Debug, Clone)] pub struct Compress(ContentEncoding); impl Compress { - /// Create new `Compress` middleware with default encoding. + /// Create new `Compress` middleware with the specified encoding. pub fn new(encoding: ContentEncoding) -> Self { Compress(encoding) } @@ -84,9 +86,7 @@ where type Error = Error; type Future = CompressResponse; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } + actix_service::forward_ready!(service); #[allow(clippy::borrow_interior_mutable_const)] fn call(&mut self, req: ServiceRequest) -> Self::Future { @@ -109,7 +109,6 @@ where } } -#[doc(hidden)] #[pin_project] pub struct CompressResponse where diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index 04173e053..d61e7d576 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -1,35 +1,36 @@ -//! `Middleware` for conditionally enables another middleware. +//! For middleware documentation, see [`Condition`]. + use std::task::{Context, Poll}; use actix_service::{Service, Transform}; -use futures_util::future::{ok, Either, FutureExt, LocalBoxFuture}; +use futures_util::future::{Either, FutureExt, LocalBoxFuture}; -/// `Middleware` for conditionally enables another middleware. -/// The controlled middleware must not change the `Service` interfaces. +/// Middleware for conditionally enabling other middleware. /// -/// This means you cannot control such middlewares like `Logger` or `Compress` directly. -/// *. See `Compat` middleware for alternative. -/// -/// ## Usage +/// The controlled middleware must not change the `Service` interfaces. This means you cannot +/// control such middlewares like `Logger` or `Compress` directly. See the [`Compat`](super::Compat) +/// middleware for a workaround. /// +/// # Usage /// ```rust /// use actix_web::middleware::{Condition, NormalizePath}; /// use actix_web::App; /// -/// # fn main() { -/// let enable_normalize = std::env::var("NORMALIZE_PATH") == Ok("true".into()); +/// let enable_normalize = std::env::var("NORMALIZE_PATH").is_ok(); /// let app = App::new() /// .wrap(Condition::new(enable_normalize, NormalizePath::default())); -/// # } /// ``` pub struct Condition { - trans: T, + transformer: T, enable: bool, } impl Condition { - pub fn new(enable: bool, trans: T) -> Self { - Self { trans, enable } + pub fn new(enable: bool, transformer: T) -> Self { + Self { + transformer, + enable, + } } } @@ -49,16 +50,15 @@ where fn new_transform(&self, service: S) -> Self::Future { if self.enable { - let f = self.trans.new_transform(service).map(|res| { - res.map( - ConditionMiddleware::Enable as fn(T::Transform) -> Self::Transform, - ) - }); - Either::Left(f) + let fut = self.transformer.new_transform(service); + async move { + let wrapped_svc = fut.await?; + Ok(ConditionMiddleware::Enable(wrapped_svc)) + } + .boxed_local() } else { - Either::Right(ok(ConditionMiddleware::Disable(service))) + async move { Ok(ConditionMiddleware::Disable(service)) }.boxed_local() } - .boxed_local() } } @@ -77,18 +77,16 @@ where type Future = Either; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - use ConditionMiddleware::*; match self { - Enable(service) => service.poll_ready(cx), - Disable(service) => service.poll_ready(cx), + ConditionMiddleware::Enable(service) => service.poll_ready(cx), + ConditionMiddleware::Disable(service) => service.poll_ready(cx), } } fn call(&mut self, req: Req) -> Self::Future { - use ConditionMiddleware::*; match self { - Enable(service) => Either::Left(service.call(req)), - Disable(service) => Either::Right(service.call(req)), + ConditionMiddleware::Enable(service) => Either::Left(service.call(req)), + ConditionMiddleware::Disable(service) => Either::Right(service.call(req)), } } } @@ -96,14 +94,17 @@ where #[cfg(test)] mod tests { use actix_service::IntoService; + use futures_util::future::ok; use super::*; - use crate::dev::{ServiceRequest, ServiceResponse}; - use crate::error::Result; - use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; - use crate::middleware::errhandlers::*; - use crate::test::{self, TestRequest}; - use crate::HttpResponse; + use crate::{ + dev::{ServiceRequest, ServiceResponse}, + error::Result, + http::{header::CONTENT_TYPE, HeaderValue, StatusCode}, + middleware::err_handlers::*, + test::{self, TestRequest}, + HttpResponse, + }; #[allow(clippy::unnecessary_wraps)] fn render_500(mut res: ServiceResponse) -> Result> { diff --git a/src/middleware/defaultheaders.rs b/src/middleware/default_headers.rs similarity index 79% rename from src/middleware/defaultheaders.rs rename to src/middleware/default_headers.rs index d648ad70f..6f027124f 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/default_headers.rs @@ -1,24 +1,34 @@ -//! Middleware for setting default response headers -use std::convert::TryFrom; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; +//! For middleware documentation, see [`DefaultHeaders`]. -use actix_service::{Service, Transform}; -use futures_util::future::{ready, Ready}; -use futures_util::ready; +use std::{ + convert::TryFrom, + future::Future, + marker::PhantomData, + pin::Pin, + rc::Rc, + task::{Context, Poll}, +}; -use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; -use crate::http::{Error as HttpError, HeaderMap}; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::Error; +use futures_util::{ + future::{ready, Ready}, + ready, +}; -/// `Middleware` for setting default response headers. +use crate::{ + dev::{Service, Transform}, + http::{ + header::{HeaderName, HeaderValue, CONTENT_TYPE}, + Error as HttpError, HeaderMap, + }, + service::{ServiceRequest, ServiceResponse}, + Error, +}; + +/// Middleware for setting default response headers. /// -/// This middleware does not set header if response headers already contains it. +/// Headers with the same key that are already set in a response will *not* be overwritten. /// +/// # Usage /// ```rust /// use actix_web::{web, http, middleware, App, HttpResponse}; /// @@ -38,7 +48,6 @@ pub struct DefaultHeaders { } struct Inner { - ct: bool, headers: HeaderMap, } @@ -46,7 +55,6 @@ impl Default for DefaultHeaders { fn default() -> Self { DefaultHeaders { inner: Rc::new(Inner { - ct: false, headers: HeaderMap::new(), }), } @@ -54,12 +62,12 @@ impl Default for DefaultHeaders { } impl DefaultHeaders { - /// Construct `DefaultHeaders` middleware. + /// Constructs an empty `DefaultHeaders` middleware. pub fn new() -> DefaultHeaders { DefaultHeaders::default() } - /// Set a header. + /// Adds a header to the default set. #[inline] pub fn header(mut self, key: K, value: V) -> Self where @@ -84,11 +92,18 @@ impl DefaultHeaders { self } - /// Set *CONTENT-TYPE* header if response does not contain this header. - pub fn content_type(mut self) -> Self { + /// Adds a default *Content-Type* header if response does not contain one. + /// + /// Default is `application/octet-stream`. + pub fn add_content_type(mut self) -> Self { Rc::get_mut(&mut self.inner) - .expect("Multiple copies exist") - .ct = true; + .expect("Multiple `Inner` copies exist.") + .headers + .insert( + CONTENT_TYPE, + HeaderValue::from_static("application/octet-stream"), + ); + self } } @@ -126,9 +141,7 @@ where type Error = Error; type Future = DefaultHeaderFuture; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } + actix_service::forward_ready!(service); fn call(&mut self, req: ServiceRequest) -> Self::Future { let inner = self.inner.clone(); @@ -160,19 +173,14 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); let mut res = ready!(this.fut.poll(cx))?; + // set response headers for (key, value) in this.inner.headers.iter() { if !res.headers().contains_key(key) { res.headers_mut().insert(key.clone(), value.clone()); } } - // default content-type - if this.inner.ct && !res.headers().contains_key(&CONTENT_TYPE) { - res.headers_mut().insert( - CONTENT_TYPE, - HeaderValue::from_static("application/octet-stream"), - ); - } + Poll::Ready(Ok(res)) } } @@ -183,10 +191,12 @@ mod tests { use futures_util::future::ok; use super::*; - use crate::dev::ServiceRequest; - use crate::http::header::CONTENT_TYPE; - use crate::test::{ok_service, TestRequest}; - use crate::HttpResponse; + use crate::{ + dev::ServiceRequest, + http::header::CONTENT_TYPE, + test::{ok_service, TestRequest}, + HttpResponse, + }; #[actix_rt::test] async fn test_default_headers() { @@ -219,7 +229,7 @@ mod tests { let srv = |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); let mut mw = DefaultHeaders::new() - .content_type() + .add_content_type() .new_transform(srv.into_service()) .await .unwrap(); diff --git a/src/middleware/errhandlers.rs b/src/middleware/err_handlers.rs similarity index 85% rename from src/middleware/errhandlers.rs rename to src/middleware/err_handlers.rs index 8503b3e1e..dfd9a7dc5 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/err_handlers.rs @@ -1,35 +1,36 @@ -//! Custom handlers service for responses. +//! For middleware documentation, see [`ErrorHandlers`]. + use std::rc::Rc; -use std::task::{Context, Poll}; use actix_service::{Service, Transform}; use ahash::AHashMap; use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; -use crate::dev::{ServiceRequest, ServiceResponse}; -use crate::error::{Error, Result}; -use crate::http::StatusCode; +use crate::{ + dev::{ServiceRequest, ServiceResponse}, + error::{Error, Result}, + http::StatusCode, +}; -/// Error handler response +/// Return type for [`ErrorHandlers`] custom handlers. pub enum ErrorHandlerResponse { - /// New http response got generated + /// Immediate HTTP response. Response(ServiceResponse), - /// Result is a future that resolves to a new http response + + /// A future that resolves to an HTTP response. Future(LocalBoxFuture<'static, Result, Error>>), } type ErrorHandler = dyn Fn(ServiceResponse) -> Result>; -/// `Middleware` for allowing custom handlers for responses. +/// Middleware for registering custom status code based error handlers. /// -/// 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 +/// Register handlers with the `ErrorHandlers::handler()` method to register a custom error handler +/// for a given status code. Handlers can modify existing responses or create completely new ones. /// +/// # Usage /// ```rust -/// use actix_web::middleware::errhandlers::{ErrorHandlers, ErrorHandlerResponse}; +/// use actix_web::middleware::{ErrorHandlers, ErrorHandlerResponse}; /// use actix_web::{web, http, dev, App, HttpRequest, HttpResponse, Result}; /// /// fn render_500(mut res: dev::ServiceResponse) -> Result> { @@ -39,7 +40,6 @@ type ErrorHandler = dyn Fn(ServiceResponse) -> Result = dyn Fn(ServiceResponse) -> Result { handlers: Rc>>>, @@ -64,12 +63,12 @@ impl Default for ErrorHandlers { } impl ErrorHandlers { - /// Construct new `ErrorHandlers` instance + /// Construct new `ErrorHandlers` instance. pub fn new() -> Self { ErrorHandlers::default() } - /// Register error handler for specified status code + /// Register error handler for specified status code. pub fn handler(mut self, status: StatusCode, handler: F) -> Self where F: Fn(ServiceResponse) -> Result> + 'static, @@ -117,9 +116,7 @@ where type Error = Error; type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } + actix_service::forward_ready!(service); fn call(&mut self, req: ServiceRequest) -> Self::Future { let handlers = self.handlers.clone(); diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index c952aeac9..cdbd5e485 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -1,13 +1,16 @@ -//! Request logging middleware -use std::collections::HashSet; -use std::convert::TryFrom; -use std::env; -use std::fmt::{self, Display, Formatter}; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; +//! For middleware documentation, see [`Logger`]. + +use std::{ + collections::HashSet, + convert::TryFrom, + env, + fmt::{self, Display as _}, + future::Future, + marker::PhantomData, + pin::Pin, + rc::Rc, + task::{Context, Poll}, +}; use actix_service::{Service, Transform}; use bytes::Bytes; @@ -16,78 +19,69 @@ use log::debug; use regex::{Regex, RegexSet}; use time::OffsetDateTime; -use crate::dev::{BodySize, MessageBody, ResponseBody}; -use crate::error::{Error, Result}; -use crate::http::{HeaderName, StatusCode}; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::HttpResponse; +use crate::{ + dev::{BodySize, MessageBody, ResponseBody}, + error::{Error, Result}, + http::{HeaderName, StatusCode}, + service::{ServiceRequest, ServiceResponse}, + HttpResponse, +}; -/// `Middleware` for logging request and response info to the terminal. +/// Middleware for logging request and response summaries 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) +/// This middleware uses the `log` crate to output information. Enable `log`'s output for the +/// "actix_web" scope using [`env_logger`](https://docs.rs/env_logger) or similar crate. /// -/// ## Usage -/// -/// Create `Logger` middleware with the specified `format`. -/// Default `Logger` could be created with `default` method, it uses the -/// default format: +/// # Default Format +/// The [`default`](Logger::default) Logger uses the following format: /// /// ```plain /// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T +/// +/// Example Output: +/// 127.0.0.1:54278 "GET /test HTTP/1.1" 404 20 "-" "HTTPie/2.2.0" 0.001074 /// ``` /// +/// # Usage /// ```rust /// use actix_web::{middleware::Logger, App}; /// -/// std::env::set_var("RUST_LOG", "actix_web=info"); -/// env_logger::init(); +/// // access logs are printed with the INFO level so ensure it is enabled by default +/// env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); /// /// let app = App::new() -/// .wrap(Logger::default()) +/// // .wrap(Logger::default()) /// .wrap(Logger::new("%a %{User-Agent}i")); /// ``` /// -/// ## 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 (in rfc3339 format) -/// -/// `%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 -/// -/// `%U` Request URL -/// -/// `%{r}a` Real IP remote address **\*** -/// -/// `%{FOO}i` request.headers['FOO'] -/// -/// `%{FOO}o` response.headers['FOO'] -/// -/// `%{FOO}e` os.environ['FOO'] -/// -/// `%{FOO}xi` [custom request replacement](Logger::custom_request_replace) labelled "FOO" +/// # Format +/// Variable | Description +/// -------- | ----------- +/// `%%` | The percent sign +/// `%a` | Peer IP address (or IP address of reverse proxy if used) +/// `%t` | Time when the request started processing (in RFC 3339 format) +/// `%r` | First line of request (Example: `GET /test HTTP/1.1`) +/// `%s` | Response status code +/// `%b` | Size of response in bytes, including HTTP headers +/// `%T` | Time taken to serve the request, in seconds to 6 decimal places +/// `%D` | Time taken to serve the request, in milliseconds +/// `%U` | Request URL +/// `%{r}a` | "Real IP" remote address **\*** +/// `%{FOO}i` | `request.headers["FOO"]` +/// `%{FOO}o` | `response.headers["FOO"]` +/// `%{FOO}e` | `env_var["FOO"]` +/// `%{FOO}xi` | [Custom request replacement](Logger::custom_request_replace) labelled "FOO" /// /// # Security -/// **\*** It is calculated using -/// [`ConnectionInfo::realip_remote_addr()`](crate::dev::ConnectionInfo::realip_remote_addr()) +/// **\*** "Real IP" remote address is calculated using +/// [`ConnectionInfo::realip_remote_addr()`](crate::dev::ConnectionInfo::realip_remote_addr()) /// -/// If you use this value ensure that all requests come from trusted hosts, since it is trivial -/// for the remote client to simulate being another client. +/// If you use this value, ensure that all requests come from trusted hosts. Otherwise, it is +/// trivial for the remote client to falsify their source IP address. +#[derive(Debug)] pub struct Logger(Rc); +#[derive(Debug, Clone)] struct Inner { format: Format, exclude: HashSet, @@ -113,7 +107,7 @@ impl Logger { self } - /// Ignore and do not log access info for paths that match regex + /// Ignore and do not log access info for paths that match regex. pub fn exclude_regex>(mut self, path: T) -> Self { let inner = Rc::get_mut(&mut self.0).unwrap(); let mut patterns = inner.exclude_regex.patterns().to_vec(); @@ -209,7 +203,7 @@ where } } -/// Logger middleware +/// Logger middleware service. pub struct LoggerMiddleware { inner: Rc, service: S, @@ -224,9 +218,7 @@ where type Error = Error; type Future = LoggerResponse; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } + actix_service::forward_ready!(service); fn call(&mut self, req: ServiceRequest) -> Self::Future { if self.inner.exclude.contains(req.path()) @@ -255,7 +247,6 @@ where } } -#[doc(hidden)] #[pin_project::pin_project] pub struct LoggerResponse where @@ -325,7 +316,7 @@ pub struct StreamLog { impl PinnedDrop for StreamLog { fn drop(self: Pin<&mut Self>) { if let Some(ref format) = self.format { - let render = |fmt: &mut Formatter<'_>| { + let render = |fmt: &mut fmt::Formatter<'_>| { for unit in &format.0 { unit.render(fmt, self.size, self.time)?; } @@ -356,9 +347,8 @@ impl MessageBody for StreamLog { } } -/// A formatting style for the `Logger`, consisting of multiple -/// `FormatText`s concatenated into one line. -#[derive(Clone)] +/// A formatting style for the `Logger` consisting of multiple concatenated `FormatText` items. +#[derive(Debug, Clone)] struct Format(Vec); impl Default for Format { @@ -430,13 +420,12 @@ impl Format { } } -/// 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)] +/// A string of text to be logged. +/// +/// This is either one of the data fields supported by the `Logger`, or a custom `String`. #[non_exhaustive] #[derive(Debug, Clone)] -// TODO: remove pub on next breaking change -pub enum FormatText { +enum FormatText { Str(String), Percent, RequestLine, @@ -454,10 +443,8 @@ pub enum FormatText { CustomRequest(String, Option), } -// TODO: remove pub on next breaking change -#[doc(hidden)] #[derive(Clone)] -pub struct CustomRequestFn { +struct CustomRequestFn { inner_fn: Rc String>, } @@ -476,11 +463,11 @@ impl fmt::Debug for CustomRequestFn { impl FormatText { fn render( &self, - fmt: &mut Formatter<'_>, + fmt: &mut fmt::Formatter<'_>, size: usize, entry_time: OffsetDateTime, ) -> Result<(), fmt::Error> { - match *self { + match self { FormatText::Str(ref string) => fmt.write_str(string), FormatText::Percent => "%".fmt(fmt), FormatText::ResponseSize => size.fmt(fmt), @@ -506,7 +493,7 @@ impl FormatText { } fn render_response(&mut self, res: &HttpResponse) { - match *self { + match self { FormatText::ResponseStatus => { *self = FormatText::Str(format!("{}", res.status().as_u16())) } @@ -527,7 +514,7 @@ impl FormatText { } fn render_request(&mut self, now: OffsetDateTime, req: &ServiceRequest) { - match &*self { + match self { FormatText::RequestLine => { *self = if req.query_string().is_empty() { FormatText::Str(format!( @@ -594,11 +581,11 @@ impl FormatText { /// Converter to get a String from something that writes to a Formatter. pub(crate) struct FormatDisplay<'a>( - &'a dyn Fn(&mut Formatter<'_>) -> Result<(), fmt::Error>, + &'a dyn Fn(&mut fmt::Formatter<'_>) -> Result<(), fmt::Error>, ); impl<'a> fmt::Display for FormatDisplay<'a> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { (self.0)(fmt) } } @@ -675,7 +662,7 @@ mod tests { unit.render_response(&resp); } - let render = |fmt: &mut Formatter<'_>| { + let render = |fmt: &mut fmt::Formatter<'_>| { for unit in &format.0 { unit.render(fmt, 1024, now)?; } @@ -708,7 +695,7 @@ mod tests { } let entry_time = OffsetDateTime::now_utc(); - let render = |fmt: &mut Formatter<'_>| { + let render = |fmt: &mut fmt::Formatter<'_>| { for unit in &format.0 { unit.render(fmt, 1024, entry_time)?; } @@ -736,7 +723,7 @@ mod tests { unit.render_response(&resp); } - let render = |fmt: &mut Formatter<'_>| { + let render = |fmt: &mut fmt::Formatter<'_>| { for unit in &format.0 { unit.render(fmt, 1024, now)?; } @@ -769,7 +756,7 @@ mod tests { } let entry_time = OffsetDateTime::now_utc(); - let render = |fmt: &mut Formatter<'_>| { + let render = |fmt: &mut fmt::Formatter<'_>| { for unit in &format.0 { unit.render(fmt, 1024, entry_time)?; } @@ -800,7 +787,7 @@ mod tests { unit.render_request(now, &req); - let render = |fmt: &mut Formatter<'_>| unit.render(fmt, 1024, now); + let render = |fmt: &mut fmt::Formatter<'_>| unit.render(fmt, 1024, now); let log_output = FormatDisplay(&render).to_string(); assert_eq!(log_output, "custom_log"); diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index af44fd8c7..e24782f07 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -1,19 +1,20 @@ -//! Middlewares +//! Commonly used middleware. + +mod compat; +mod condition; +mod default_headers; +mod err_handlers; +mod logger; +mod normalize; + +pub use self::compat::Compat; +pub use self::condition::Condition; +pub use self::default_headers::DefaultHeaders; +pub use self::err_handlers::{ErrorHandlerResponse, ErrorHandlers}; +pub use self::logger::Logger; +pub use self::normalize::{NormalizePath, TrailingSlash}; #[cfg(feature = "compress")] mod compress; #[cfg(feature = "compress")] pub use self::compress::Compress; - -mod compat; -mod condition; -mod defaultheaders; -pub mod errhandlers; -mod logger; -pub mod normalize; - -pub use self::compat::Compat; -pub use self::condition::Condition; -pub use self::defaultheaders::DefaultHeaders; -pub use self::logger::Logger; -pub use self::normalize::NormalizePath; diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 4109364bf..89cab9073 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -1,60 +1,63 @@ //! For middleware documentation, see [`NormalizePath`]. -use std::task::{Context, Poll}; - use actix_http::http::{PathAndQuery, Uri}; use actix_service::{Service, Transform}; use bytes::Bytes; use futures_util::future::{ready, Ready}; use regex::Regex; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::Error; +use crate::{ + service::{ServiceRequest, ServiceResponse}, + Error, +}; -/// To be used when constructing `NormalizePath` to define it's behavior. +/// Determines the behavior of the [`NormalizePath`] middleware. +/// +/// The default is `TrailingSlash::Trim`. #[non_exhaustive] -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] pub enum TrailingSlash { - /// Always add a trailing slash to the end of the path. - /// This will require all routes to end in a trailing slash for them to be accessible. - Always, + /// Trim trailing slashes from the end of the path. + /// + /// Using this will require all routes to omit trailing slashes for them to be accessible. + Trim, /// Only merge any present multiple trailing slashes. /// - /// Note: This option provides the best compatibility with the v2 version of this middleware. + /// This option provides the best compatibility with behavior in actix-web v2.0. MergeOnly, - /// Trim trailing slashes from the end of the path. - Trim, + /// Always add a trailing slash to the end of the path. + /// + /// Using this will require all routes have a trailing slash for them to be accessible. + Always, } impl Default for TrailingSlash { fn default() -> Self { - TrailingSlash::Always + TrailingSlash::Trim } } -#[derive(Default, Clone, Copy)] -/// Middleware to normalize a request's path so that routes can be matched less strictly. +/// Middleware for normalizing a request's path so that routes can be matched more flexibly. /// /// # Normalization Steps -/// - Merges multiple consecutive slashes into one. (For example, `/path//one` always -/// becomes `/path/one`.) +/// - Merges consecutive slashes into one. (For example, `/path//one` always becomes `/path/one`.) /// - Appends a trailing slash if one is not present, removes one if present, or keeps trailing /// slashes as-is, depending on which [`TrailingSlash`] variant is supplied /// to [`new`](NormalizePath::new()). /// /// # Default Behavior -/// The default constructor chooses to strip trailing slashes from the end -/// ([`TrailingSlash::Trim`]), the effect is that route definitions should be defined without -/// trailing slashes or else they will be inaccessible. +/// The default constructor chooses to strip trailing slashes from the end of paths with them +/// ([`TrailingSlash::Trim`]). The implication is that route definitions should be defined without +/// trailing slashes or else they will be inaccessible (or vice versa when using the +/// `TrailingSlash::Always` behavior), as shown in the example tests below. /// -/// # Example +/// # Usage /// ```rust /// use actix_web::{web, middleware, App}; /// -/// # #[actix_rt::test] -/// # async fn normalize() { +/// # actix_web::rt::System::new("doctest").block_on(async { /// let app = App::new() /// .wrap(middleware::NormalizePath::default()) /// .route("/test", web::get().to(|| async { "test" })) @@ -80,8 +83,9 @@ impl Default for TrailingSlash { /// let req = TestRequest::with_uri("/unmatchable/").to_request(); /// let res = call_service(&mut app, req).await; /// assert_eq!(res.status(), StatusCode::NOT_FOUND); -/// # } +/// # }) /// ``` +#[derive(Debug, Clone, Copy, Default)] pub struct NormalizePath(TrailingSlash); impl NormalizePath { @@ -111,7 +115,6 @@ where } } -#[doc(hidden)] pub struct NormalizePathNormalization { service: S, merge_slash: Regex, @@ -127,9 +130,7 @@ where type Error = Error; type Future = S::Future; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } + actix_service::forward_ready!(service); fn call(&mut self, mut req: ServiceRequest) -> Self::Future { let head = req.head_mut(); @@ -198,7 +199,7 @@ mod tests { App::new() .wrap(NormalizePath::default()) .service(web::resource("/").to(HttpResponse::Ok)) - .service(web::resource("/v1/something/").to(HttpResponse::Ok)), + .service(web::resource("/v1/something").to(HttpResponse::Ok)), ) .await; @@ -306,7 +307,7 @@ mod tests { #[actix_rt::test] async fn test_in_place_normalization() { let srv = |req: ServiceRequest| { - assert_eq!("/v1/something/", req.path()); + assert_eq!("/v1/something", req.path()); ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; @@ -334,7 +335,7 @@ mod tests { #[actix_rt::test] async fn should_normalize_nothing() { - const URI: &str = "/v1/something/"; + const URI: &str = "/v1/something"; let srv = |req: ServiceRequest| { assert_eq!(URI, req.path()); @@ -353,10 +354,8 @@ mod tests { #[actix_rt::test] async fn should_normalize_no_trail() { - const URI: &str = "/v1/something"; - let srv = |req: ServiceRequest| { - assert_eq!(URI.to_string() + "/", req.path()); + assert_eq!("/v1/something", req.path()); ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; @@ -365,7 +364,7 @@ mod tests { .await .unwrap(); - let req = TestRequest::with_uri(URI).to_srv_request(); + let req = TestRequest::with_uri("/v1/something/").to_srv_request(); let res = normalize.call(req).await.unwrap(); assert!(res.status().is_success()); } diff --git a/tests/test_server.rs b/tests/test_server.rs index 2f8ce625e..43ee1230d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,7 +1,9 @@ -use std::future::Future; -use std::io::{Read, Write}; -use std::pin::Pin; -use std::task::{Context, Poll}; +use std::{ + future::Future, + io::{Read, Write}, + pin::Pin, + task::{Context, Poll}, +}; use actix_http::http::header::{ ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, @@ -9,15 +11,16 @@ use actix_http::http::header::{ }; use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::Bytes; -use flate2::read::GzDecoder; -use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder}; -use flate2::Compression; +use flate2::{ + read::GzDecoder, + write::{GzEncoder, ZlibDecoder, ZlibEncoder}, + Compression, +}; use futures_util::ready; use rand::{distributions::Alphanumeric, Rng}; use actix_web::dev::BodyEncoding; -use actix_web::middleware::normalize::TrailingSlash; -use actix_web::middleware::{Compress, NormalizePath}; +use actix_web::middleware::{Compress, NormalizePath, TrailingSlash}; use actix_web::{dev, test, web, App, Error, HttpResponse}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \