From 4519db36b25992d98061d870fe738e0c04e50afb Mon Sep 17 00:00:00 2001
From: Joshua Parkin
Date: Thu, 29 Oct 2020 18:38:49 +0000
Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-http) [](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
+[](https://crates.io/crates/actix-http)
+[](https://docs.rs/actix-http/2.1.0)
+
+[](https://deps.rs/crate/actix-http/2.1.0)
+[](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 [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/awc) [](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
+[](https://crates.io/crates/awc)
+[](https://docs.rs/awc/2.0.1)
+
+[](https://deps.rs/crate/awc/2.0.1)
+[](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 6/9] 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 @@
-[](https://crates.io/crates/actix-web)
-[](https://docs.rs/actix-web)
+[](https://crates.io/crates/actix-web)
+[](https://docs.rs/actix-web/3.2.0)
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)

+[](https://deps.rs/crate/actix-web/2.2.0)
[](https://travis-ci.org/actix/actix-web)
[](https://codecov.io/gh/actix/actix-web)
[](https://crates.io/crates/actix-web)
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[](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 7/9] 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 @@
[](https://docs.rs/actix-web/3.2.0)
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)

-[](https://deps.rs/crate/actix-web/2.2.0)
+[](https://deps.rs/crate/actix-web/3.2.0)
[](https://travis-ci.org/actix/actix-web)
[](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 8/9] 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 9/9] 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/