>> {
+ if self.is_empty() {
+ Poll::Ready(None)
+ } else {
+ let bytes = match mem::take(self.get_mut()) {
+ Cow::Borrowed(s) => Bytes::from_static(s.as_bytes()),
+ Cow::Owned(s) => Bytes::from(s.into_bytes()),
+ };
+ Poll::Ready(Some(Ok(bytes)))
+ }
+ }
+
+ #[inline]
+ fn try_into_bytes(self) -> Result {
+ match self {
+ Cow::Borrowed(s) => Ok(Bytes::from_static(s.as_bytes())),
+ Cow::Owned(s) => Ok(Bytes::from(s.into_bytes())),
+ }
+ }
+ }
+
impl MessageBody for bytestring::ByteString {
type Error = Infallible;
diff --git a/actix-http/src/body/sized_stream.rs b/actix-http/src/body/sized_stream.rs
index e5e27b287..08cd81a0d 100644
--- a/actix-http/src/body/sized_stream.rs
+++ b/actix-http/src/body/sized_stream.rs
@@ -44,7 +44,7 @@ where
#[inline]
fn size(&self) -> BodySize {
- BodySize::Sized(self.size as u64)
+ BodySize::Sized(self.size)
}
/// Attempts to pull out the next value of the underlying [`Stream`].
diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs
index 71b933835..e2693acaf 100644
--- a/actix-http/src/builder.rs
+++ b/actix-http/src/builder.rs
@@ -186,7 +186,7 @@ where
self
}
- /// Finish service configuration and create a HTTP Service for HTTP/1 protocol.
+ /// Finish service configuration and create a service for the HTTP/1 protocol.
pub fn h1(self, service: F) -> H1Service
where
B: MessageBody,
@@ -209,7 +209,7 @@ where
.on_connect_ext(self.on_connect_ext)
}
- /// Finish service configuration and create a HTTP service for HTTP/2 protocol.
+ /// Finish service configuration and create a service for the HTTP/2 protocol.
#[cfg(feature = "http2")]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
pub fn h2(self, service: F) -> crate::h2::H2Service
diff --git a/actix-http/src/h1/chunked.rs b/actix-http/src/h1/chunked.rs
index 4005ed892..fc9081b81 100644
--- a/actix-http/src/h1/chunked.rs
+++ b/actix-http/src/h1/chunked.rs
@@ -71,7 +71,7 @@ impl ChunkedState {
match size.checked_mul(radix) {
Some(n) => {
- *size = n as u64;
+ *size = n;
*size += rem as u64;
Poll::Ready(Ok(ChunkedState::Size))
diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs
index d39c5bd69..db46cdefc 100644
--- a/actix-http/src/h1/dispatcher_tests.rs
+++ b/actix-http/src/h1/dispatcher_tests.rs
@@ -932,7 +932,6 @@ fn http_msg(msg: impl AsRef) -> BytesMut {
.as_ref()
.trim()
.split('\n')
- .into_iter()
.map(|line| [line.trim_start(), "\r"].concat())
.collect::>()
.join("\n");
diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs
index 21cfd75c4..abe396ce2 100644
--- a/actix-http/src/h1/encoder.rs
+++ b/actix-http/src/h1/encoder.rs
@@ -450,7 +450,7 @@ impl TransferEncoding {
buf.extend_from_slice(&msg[..len as usize]);
- *remaining -= len as u64;
+ *remaining -= len;
Ok(*remaining == 0)
} else {
Ok(true)
diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs
index 680936f0f..3e618820e 100644
--- a/actix-http/src/h2/dispatcher.rs
+++ b/actix-http/src/h2/dispatcher.rs
@@ -29,7 +29,7 @@ use crate::{
HeaderName, HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE,
},
service::HttpFlow,
- Extensions, OnConnectData, Payload, Request, Response, ResponseHead,
+ Extensions, Method, OnConnectData, Payload, Request, Response, ResponseHead,
};
const CHUNK_SIZE: usize = 16_384;
@@ -118,6 +118,7 @@ where
let payload = crate::h2::Payload::new(body);
let pl = Payload::H2 { payload };
let mut req = Request::with_payload(pl);
+ let head_req = parts.method == Method::HEAD;
let head = req.head_mut();
head.uri = parts.uri;
@@ -135,10 +136,10 @@ where
actix_rt::spawn(async move {
// resolve service call and send response.
let res = match fut.await {
- Ok(res) => handle_response(res.into(), tx, config).await,
+ Ok(res) => handle_response(res.into(), tx, config, head_req).await,
Err(err) => {
let res: Response = err.into();
- handle_response(res, tx, config).await
+ handle_response(res, tx, config, head_req).await
}
};
@@ -206,6 +207,7 @@ async fn handle_response(
res: Response,
mut tx: SendResponse,
config: ServiceConfig,
+ head_req: bool,
) -> Result<(), DispatchError>
where
B: MessageBody,
@@ -215,14 +217,14 @@ where
// prepare response.
let mut size = body.size();
let res = prepare_response(config, res.head(), &mut size);
- let eof = size.is_eof();
+ let eof_or_head = size.is_eof() || head_req;
// send response head and return on eof.
let mut stream = tx
- .send_response(res, eof)
+ .send_response(res, eof_or_head)
.map_err(DispatchError::SendResponse)?;
- if eof {
+ if eof_or_head {
return Ok(());
}
diff --git a/actix-http/src/header/common.rs b/actix-http/src/header/common.rs
new file mode 100644
index 000000000..6942dc26a
--- /dev/null
+++ b/actix-http/src/header/common.rs
@@ -0,0 +1,53 @@
+//! Common header names not defined in [`http`].
+//!
+//! Any headers added to this file will need to be re-exported from the list at `crate::headers`.
+
+use http::header::HeaderName;
+
+/// Response header field that indicates how caches have handled that response and its corresponding
+/// request.
+///
+/// See [RFC 9211](https://www.rfc-editor.org/rfc/rfc9211) for full semantics.
+// TODO(breaking): replace with http's version
+pub const CACHE_STATUS: HeaderName = HeaderName::from_static("cache-status");
+
+/// Response header field that allows origin servers to control the behavior of CDN caches
+/// interposed between them and clients separately from other caches that might handle the response.
+///
+/// See [RFC 9213](https://www.rfc-editor.org/rfc/rfc9213) for full semantics.
+// TODO(breaking): replace with http's version
+pub const CDN_CACHE_CONTROL: HeaderName = HeaderName::from_static("cdn-cache-control");
+
+/// Response header that prevents a document from loading any cross-origin resources that don't
+/// explicitly grant the document permission (using [CORP] or [CORS]).
+///
+/// [CORP]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy_(CORP)
+/// [CORS]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
+pub const CROSS_ORIGIN_EMBEDDER_POLICY: HeaderName =
+ HeaderName::from_static("cross-origin-embedder-policy");
+
+/// Response header that allows you to ensure a top-level document does not share a browsing context
+/// group with cross-origin documents.
+pub const CROSS_ORIGIN_OPENER_POLICY: HeaderName =
+ HeaderName::from_static("cross-origin-opener-policy");
+
+/// Response header that conveys a desire that the browser blocks no-cors cross-origin/cross-site
+/// requests to the given resource.
+pub const CROSS_ORIGIN_RESOURCE_POLICY: HeaderName =
+ HeaderName::from_static("cross-origin-resource-policy");
+
+/// Response header that provides a mechanism to allow and deny the use of browser features in a
+/// document or within any `
-[](https://crates.io/crates/actix-web)
-[](https://docs.rs/actix-web/4.2.1)
-
-
-[](https://deps.rs/crate/actix-web/4.2.1)
-
-[](https://github.com/actix/actix-web/actions/workflows/ci.yml)
-[](https://codecov.io/gh/actix/actix-web)
-
-[](https://discord.gg/NWpN5mmg3x)
+[](https://crates.io/crates/actix-web) [](https://docs.rs/actix-web/4.3.1)   [](https://deps.rs/crate/actix-web/4.3.1) [](https://github.com/actix/actix-web/actions/workflows/ci.yml) [](https://codecov.io/gh/actix/actix-web)  [](https://discord.gg/NWpN5mmg3x)
diff --git a/actix-web/benches/server.rs b/actix-web/benches/server.rs
index 0d45c9403..2c9f71dc5 100644
--- a/actix-web/benches/server.rs
+++ b/actix-web/benches/server.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::uninlined_format_args)]
+
use actix_web::{web, App, HttpResponse};
use awc::Client;
use criterion::{criterion_group, criterion_main, Criterion};
diff --git a/actix-web/examples/basic.rs b/actix-web/examples/basic.rs
index 36b1cdd8f..60715f477 100644
--- a/actix-web/examples/basic.rs
+++ b/actix-web/examples/basic.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::uninlined_format_args)]
+
use actix_web::{get, middleware, web, App, HttpRequest, HttpResponse, HttpServer};
#[get("/resource1/{name}/index.html")]
diff --git a/actix-web/examples/macroless.rs b/actix-web/examples/macroless.rs
index 78ffd45c1..d3589da21 100644
--- a/actix-web/examples/macroless.rs
+++ b/actix-web/examples/macroless.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::uninlined_format_args)]
+
use actix_web::{middleware, rt, web, App, HttpRequest, HttpServer};
async fn index(req: HttpRequest) -> &'static str {
diff --git a/actix-web/examples/on-connect.rs b/actix-web/examples/on-connect.rs
index 24c6f8418..57017fcd6 100644
--- a/actix-web/examples/on-connect.rs
+++ b/actix-web/examples/on-connect.rs
@@ -4,6 +4,8 @@
//! For an example of extracting a client TLS certificate, see:
//!
+#![allow(clippy::uninlined_format_args)]
+
use std::{any::Any, io, net::SocketAddr};
use actix_web::{
diff --git a/actix-web/examples/uds.rs b/actix-web/examples/uds.rs
index cf0ffebde..15e28ba1d 100644
--- a/actix-web/examples/uds.rs
+++ b/actix-web/examples/uds.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::uninlined_format_args)]
+
use actix_web::{get, web, HttpRequest};
#[cfg(unix)]
use actix_web::{middleware, App, Error, HttpResponse, HttpServer};
@@ -39,7 +41,7 @@ async fn main() -> std::io::Result<()> {
)
.service(web::resource("/test1.html").to(|| async { "Test\r\n" }))
})
- .bind_uds("/Users/fafhrd91/uds-test")?
+ .bind_uds("/Users/me/uds-test")?
.workers(1)
.run()
.await
diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs
index e53ab8080..353b82b19 100644
--- a/actix-web/src/app.rs
+++ b/actix-web/src/app.rs
@@ -712,6 +712,7 @@ mod tests {
.route("/", web::to(|| async { "hello" }))
}
+ #[allow(clippy::let_underscore_future)]
let _ = init_service(my_app());
}
}
diff --git a/actix-web/src/app_service.rs b/actix-web/src/app_service.rs
index 0b5ba2ab6..0fc856203 100644
--- a/actix-web/src/app_service.rs
+++ b/actix-web/src/app_service.rs
@@ -21,7 +21,7 @@ use crate::{
Error, HttpResponse,
};
-/// Service factory to convert `Request` to a `ServiceRequest`.
+/// Service factory to convert [`Request`] to a [`ServiceRequest`].
///
/// It also executes data factories.
pub struct AppInit
@@ -155,7 +155,7 @@ where
app_state: Rc,
}
-/// A collection of [`AppInitService`] state that shared across `HttpRequest`s.
+/// A collection of state for [`AppInitService`] that is shared across [`HttpRequest`]s.
pub(crate) struct AppInitServiceState {
rmap: Rc,
config: AppConfig,
@@ -163,6 +163,7 @@ pub(crate) struct AppInitServiceState {
}
impl AppInitServiceState {
+ /// Constructs state collection from resource map and app config.
pub(crate) fn new(rmap: Rc, config: AppConfig) -> Rc {
Rc::new(AppInitServiceState {
rmap,
@@ -171,16 +172,19 @@ impl AppInitServiceState {
})
}
+ /// Returns a reference to the application's resource map.
#[inline]
pub(crate) fn rmap(&self) -> &ResourceMap {
&self.rmap
}
+ /// Returns a reference to the application's configuration.
#[inline]
pub(crate) fn config(&self) -> &AppConfig {
&self.config
}
+ /// Returns a reference to the application's request pool.
#[inline]
pub(crate) fn pool(&self) -> &HttpRequestPool {
&self.pool
diff --git a/actix-web/src/config.rs b/actix-web/src/config.rs
index 68bea34ca..11eaf8720 100644
--- a/actix-web/src/config.rs
+++ b/actix-web/src/config.rs
@@ -141,7 +141,7 @@ impl AppConfig {
self.secure
}
- /// Returns the socket address of the local half of this TCP connection
+ /// Returns the socket address of the local half of this TCP connection.
pub fn local_addr(&self) -> SocketAddr {
self.addr
}
diff --git a/actix-web/src/guard/host.rs b/actix-web/src/guard/host.rs
new file mode 100644
index 000000000..f05c81183
--- /dev/null
+++ b/actix-web/src/guard/host.rs
@@ -0,0 +1,209 @@
+use actix_http::{header, uri::Uri, RequestHead};
+
+use super::{Guard, GuardContext};
+
+/// Creates a guard that matches requests targetting a specific host.
+///
+/// # Matching Host
+/// This guard will:
+/// - match against the `Host` header, if present;
+/// - fall-back to matching against the request target's host, if present;
+/// - return false if host cannot be determined;
+///
+/// # Matching Scheme
+/// Optionally, this guard can match against the host's scheme. Set the scheme for matching using
+/// `Host(host).scheme(protocol)`. If the request's scheme cannot be determined, it will not prevent
+/// the guard from matching successfully.
+///
+/// # Examples
+/// The `Host` guard can be used to set up a form of [virtual hosting] within a single app.
+/// Overlapping scope prefixes are usually discouraged, but when combined with non-overlapping guard
+/// definitions they become safe to use in this way. Without these host guards, only routes under
+/// the first-to-be-defined scope would be accessible. You can test this locally using `127.0.0.1`
+/// and `localhost` as the `Host` guards.
+/// ```
+/// use actix_web::{web, http::Method, guard, App, HttpResponse};
+///
+/// App::new()
+/// .service(
+/// web::scope("")
+/// .guard(guard::Host("www.rust-lang.org"))
+/// .default_service(web::to(|| async {
+/// HttpResponse::Ok().body("marketing site")
+/// })),
+/// )
+/// .service(
+/// web::scope("")
+/// .guard(guard::Host("play.rust-lang.org"))
+/// .default_service(web::to(|| async {
+/// HttpResponse::Ok().body("playground frontend")
+/// })),
+/// );
+/// ```
+///
+/// The example below additionally guards on the host URI's scheme. This could allow routing to
+/// different handlers for `http:` vs `https:` visitors; to redirect, for example.
+/// ```
+/// use actix_web::{web, guard::Host, HttpResponse};
+///
+/// web::scope("/admin")
+/// .guard(Host("admin.rust-lang.org").scheme("https"))
+/// .default_service(web::to(|| async {
+/// HttpResponse::Ok().body("admin connection is secure")
+/// }));
+/// ```
+///
+/// [virtual hosting]: https://en.wikipedia.org/wiki/Virtual_hosting
+#[allow(non_snake_case)]
+pub fn Host(host: impl AsRef) -> HostGuard {
+ HostGuard {
+ host: host.as_ref().to_string(),
+ scheme: None,
+ }
+}
+
+fn get_host_uri(req: &RequestHead) -> Option {
+ req.headers
+ .get(header::HOST)
+ .and_then(|host_value| host_value.to_str().ok())
+ .or_else(|| req.uri.host())
+ .and_then(|host| host.parse().ok())
+}
+
+#[doc(hidden)]
+pub struct HostGuard {
+ host: String,
+ scheme: Option,
+}
+
+impl HostGuard {
+ /// Set request scheme to match
+ pub fn scheme>(mut self, scheme: H) -> HostGuard {
+ self.scheme = Some(scheme.as_ref().to_string());
+ self
+ }
+}
+
+impl Guard for HostGuard {
+ fn check(&self, ctx: &GuardContext<'_>) -> bool {
+ // parse host URI from header or request target
+ let req_host_uri = match get_host_uri(ctx.head()) {
+ Some(uri) => uri,
+
+ // no match if host cannot be determined
+ None => return false,
+ };
+
+ match req_host_uri.host() {
+ // fall through to scheme checks
+ Some(uri_host) if self.host == uri_host => {}
+
+ // Either:
+ // - request's host does not match guard's host;
+ // - It was possible that the parsed URI from request target did not contain a host.
+ _ => return false,
+ }
+
+ if let Some(ref scheme) = self.scheme {
+ if let Some(ref req_host_uri_scheme) = req_host_uri.scheme_str() {
+ return scheme == req_host_uri_scheme;
+ }
+
+ // TODO: is this the correct behavior?
+ // falls through if scheme cannot be determined
+ }
+
+ // all conditions passed
+ true
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test::TestRequest;
+
+ #[test]
+ fn host_from_header() {
+ let req = TestRequest::default()
+ .insert_header((
+ header::HOST,
+ header::HeaderValue::from_static("www.rust-lang.org"),
+ ))
+ .to_srv_request();
+
+ let host = Host("www.rust-lang.org");
+ assert!(host.check(&req.guard_ctx()));
+
+ let host = Host("www.rust-lang.org").scheme("https");
+ assert!(host.check(&req.guard_ctx()));
+
+ let host = Host("blog.rust-lang.org");
+ assert!(!host.check(&req.guard_ctx()));
+
+ let host = Host("blog.rust-lang.org").scheme("https");
+ assert!(!host.check(&req.guard_ctx()));
+
+ let host = Host("crates.io");
+ assert!(!host.check(&req.guard_ctx()));
+
+ let host = Host("localhost");
+ assert!(!host.check(&req.guard_ctx()));
+ }
+
+ #[test]
+ fn host_without_header() {
+ let req = TestRequest::default()
+ .uri("www.rust-lang.org")
+ .to_srv_request();
+
+ let host = Host("www.rust-lang.org");
+ assert!(host.check(&req.guard_ctx()));
+
+ let host = Host("www.rust-lang.org").scheme("https");
+ assert!(host.check(&req.guard_ctx()));
+
+ let host = Host("blog.rust-lang.org");
+ assert!(!host.check(&req.guard_ctx()));
+
+ let host = Host("blog.rust-lang.org").scheme("https");
+ assert!(!host.check(&req.guard_ctx()));
+
+ let host = Host("crates.io");
+ assert!(!host.check(&req.guard_ctx()));
+
+ let host = Host("localhost");
+ assert!(!host.check(&req.guard_ctx()));
+ }
+
+ #[test]
+ fn host_scheme() {
+ let req = TestRequest::default()
+ .insert_header((
+ header::HOST,
+ header::HeaderValue::from_static("https://www.rust-lang.org"),
+ ))
+ .to_srv_request();
+
+ let host = Host("www.rust-lang.org").scheme("https");
+ assert!(host.check(&req.guard_ctx()));
+
+ let host = Host("www.rust-lang.org");
+ assert!(host.check(&req.guard_ctx()));
+
+ let host = Host("www.rust-lang.org").scheme("http");
+ assert!(!host.check(&req.guard_ctx()));
+
+ let host = Host("blog.rust-lang.org");
+ assert!(!host.check(&req.guard_ctx()));
+
+ let host = Host("blog.rust-lang.org").scheme("https");
+ assert!(!host.check(&req.guard_ctx()));
+
+ let host = Host("crates.io").scheme("https");
+ assert!(!host.check(&req.guard_ctx()));
+
+ let host = Host("localhost");
+ assert!(!host.check(&req.guard_ctx()));
+ }
+}
diff --git a/actix-web/src/guard/mod.rs b/actix-web/src/guard/mod.rs
index e086f8648..164032bdc 100644
--- a/actix-web/src/guard/mod.rs
+++ b/actix-web/src/guard/mod.rs
@@ -52,12 +52,15 @@ use std::{
rc::Rc,
};
-use actix_http::{header, uri::Uri, Extensions, Method as HttpMethod, RequestHead};
+use actix_http::{header, Extensions, Method as HttpMethod, RequestHead};
use crate::{http::header::Header, service::ServiceRequest, HttpMessage as _};
mod acceptable;
+mod host;
+
pub use self::acceptable::Acceptable;
+pub use self::host::{Host, HostGuard};
/// Provides access to request parts that are useful during routing.
#[derive(Debug)]
@@ -371,124 +374,6 @@ impl Guard for HeaderGuard {
}
}
-/// Creates a guard that matches requests targetting a specific host.
-///
-/// # Matching Host
-/// This guard will:
-/// - match against the `Host` header, if present;
-/// - fall-back to matching against the request target's host, if present;
-/// - return false if host cannot be determined;
-///
-/// # Matching Scheme
-/// Optionally, this guard can match against the host's scheme. Set the scheme for matching using
-/// `Host(host).scheme(protocol)`. If the request's scheme cannot be determined, it will not prevent
-/// the guard from matching successfully.
-///
-/// # Examples
-/// The [module-level documentation](self) has an example of virtual hosting using `Host` guards.
-///
-/// The example below additionally guards on the host URI's scheme. This could allow routing to
-/// different handlers for `http:` vs `https:` visitors; to redirect, for example.
-/// ```
-/// use actix_web::{web, guard::Host, HttpResponse};
-///
-/// web::scope("/admin")
-/// .guard(Host("admin.rust-lang.org").scheme("https"))
-/// .default_service(web::to(|| async {
-/// HttpResponse::Ok().body("admin connection is secure")
-/// }));
-/// ```
-///
-/// The `Host` guard can be used to set up some form of [virtual hosting] within a single app.
-/// Overlapping scope prefixes are usually discouraged, but when combined with non-overlapping guard
-/// definitions they become safe to use in this way. Without these host guards, only routes under
-/// the first-to-be-defined scope would be accessible. You can test this locally using `127.0.0.1`
-/// and `localhost` as the `Host` guards.
-/// ```
-/// use actix_web::{web, http::Method, guard, App, HttpResponse};
-///
-/// App::new()
-/// .service(
-/// web::scope("")
-/// .guard(guard::Host("www.rust-lang.org"))
-/// .default_service(web::to(|| async {
-/// HttpResponse::Ok().body("marketing site")
-/// })),
-/// )
-/// .service(
-/// web::scope("")
-/// .guard(guard::Host("play.rust-lang.org"))
-/// .default_service(web::to(|| async {
-/// HttpResponse::Ok().body("playground frontend")
-/// })),
-/// );
-/// ```
-///
-/// [virtual hosting]: https://en.wikipedia.org/wiki/Virtual_hosting
-#[allow(non_snake_case)]
-pub fn Host(host: impl AsRef) -> HostGuard {
- HostGuard {
- host: host.as_ref().to_string(),
- scheme: None,
- }
-}
-
-fn get_host_uri(req: &RequestHead) -> Option {
- req.headers
- .get(header::HOST)
- .and_then(|host_value| host_value.to_str().ok())
- .or_else(|| req.uri.host())
- .and_then(|host| host.parse().ok())
-}
-
-#[doc(hidden)]
-pub struct HostGuard {
- host: String,
- scheme: Option,
-}
-
-impl HostGuard {
- /// Set request scheme to match
- pub fn scheme>(mut self, scheme: H) -> HostGuard {
- self.scheme = Some(scheme.as_ref().to_string());
- self
- }
-}
-
-impl Guard for HostGuard {
- fn check(&self, ctx: &GuardContext<'_>) -> bool {
- // parse host URI from header or request target
- let req_host_uri = match get_host_uri(ctx.head()) {
- Some(uri) => uri,
-
- // no match if host cannot be determined
- None => return false,
- };
-
- match req_host_uri.host() {
- // fall through to scheme checks
- Some(uri_host) if self.host == uri_host => {}
-
- // Either:
- // - request's host does not match guard's host;
- // - It was possible that the parsed URI from request target did not contain a host.
- _ => return false,
- }
-
- if let Some(ref scheme) = self.scheme {
- if let Some(ref req_host_uri_scheme) = req_host_uri.scheme_str() {
- return scheme == req_host_uri_scheme;
- }
-
- // TODO: is this the correct behavior?
- // falls through if scheme cannot be determined
- }
-
- // all conditions passed
- true
- }
-}
-
#[cfg(test)]
mod tests {
use actix_http::{header, Method};
@@ -515,90 +400,6 @@ mod tests {
assert!(!hdr.check(&req.guard_ctx()));
}
- #[test]
- fn host_from_header() {
- let req = TestRequest::default()
- .insert_header((
- header::HOST,
- header::HeaderValue::from_static("www.rust-lang.org"),
- ))
- .to_srv_request();
-
- let host = Host("www.rust-lang.org");
- assert!(host.check(&req.guard_ctx()));
-
- let host = Host("www.rust-lang.org").scheme("https");
- assert!(host.check(&req.guard_ctx()));
-
- let host = Host("blog.rust-lang.org");
- assert!(!host.check(&req.guard_ctx()));
-
- let host = Host("blog.rust-lang.org").scheme("https");
- assert!(!host.check(&req.guard_ctx()));
-
- let host = Host("crates.io");
- assert!(!host.check(&req.guard_ctx()));
-
- let host = Host("localhost");
- assert!(!host.check(&req.guard_ctx()));
- }
-
- #[test]
- fn host_without_header() {
- let req = TestRequest::default()
- .uri("www.rust-lang.org")
- .to_srv_request();
-
- let host = Host("www.rust-lang.org");
- assert!(host.check(&req.guard_ctx()));
-
- let host = Host("www.rust-lang.org").scheme("https");
- assert!(host.check(&req.guard_ctx()));
-
- let host = Host("blog.rust-lang.org");
- assert!(!host.check(&req.guard_ctx()));
-
- let host = Host("blog.rust-lang.org").scheme("https");
- assert!(!host.check(&req.guard_ctx()));
-
- let host = Host("crates.io");
- assert!(!host.check(&req.guard_ctx()));
-
- let host = Host("localhost");
- assert!(!host.check(&req.guard_ctx()));
- }
-
- #[test]
- fn host_scheme() {
- let req = TestRequest::default()
- .insert_header((
- header::HOST,
- header::HeaderValue::from_static("https://www.rust-lang.org"),
- ))
- .to_srv_request();
-
- let host = Host("www.rust-lang.org").scheme("https");
- assert!(host.check(&req.guard_ctx()));
-
- let host = Host("www.rust-lang.org");
- assert!(host.check(&req.guard_ctx()));
-
- let host = Host("www.rust-lang.org").scheme("http");
- assert!(!host.check(&req.guard_ctx()));
-
- let host = Host("blog.rust-lang.org");
- assert!(!host.check(&req.guard_ctx()));
-
- let host = Host("blog.rust-lang.org").scheme("https");
- assert!(!host.check(&req.guard_ctx()));
-
- let host = Host("crates.io").scheme("https");
- assert!(!host.check(&req.guard_ctx()));
-
- let host = Host("localhost");
- assert!(!host.check(&req.guard_ctx()));
- }
-
#[test]
fn method_guards() {
let get_req = TestRequest::get().to_srv_request();
diff --git a/actix-web/src/info.rs b/actix-web/src/info.rs
index 7c685406e..c5d9638f4 100644
--- a/actix-web/src/info.rs
+++ b/actix-web/src/info.rs
@@ -76,7 +76,6 @@ impl ConnectionInfo {
for (name, val) in req
.headers
.get_all(&header::FORWARDED)
- .into_iter()
.filter_map(|hdr| hdr.to_str().ok())
// "for=1.2.3.4, for=5.6.7.8; scheme=https"
.flat_map(|val| val.split(';'))
diff --git a/actix-web/src/lib.rs b/actix-web/src/lib.rs
index 338541208..6a94976c5 100644
--- a/actix-web/src/lib.rs
+++ b/actix-web/src/lib.rs
@@ -69,6 +69,7 @@
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
+#![allow(clippy::uninlined_format_args)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_cfg))]
diff --git a/actix-web/src/middleware/authors-guide.md b/actix-web/src/middleware/authors-guide.md
index a8d1edea4..64bad15c2 100644
--- a/actix-web/src/middleware/authors-guide.md
+++ b/actix-web/src/middleware/authors-guide.md
@@ -13,4 +13,5 @@
## When To (Not) Use Middleware
## Author's References
+
- `EitherBody` + when is middleware appropriate: https://discord.com/channels/771444961383153695/952016890723729428
diff --git a/actix-web/src/middleware/err_handlers.rs b/actix-web/src/middleware/err_handlers.rs
index 4ddbc6318..5522cc021 100644
--- a/actix-web/src/middleware/err_handlers.rs
+++ b/actix-web/src/middleware/err_handlers.rs
@@ -50,16 +50,24 @@ type DefaultHandler = Option>>;
/// will pass by unchanged by this middleware.
///
/// # Examples
-/// ```
-/// use actix_web::http::{header, StatusCode};
-/// use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers};
-/// use actix_web::{dev, web, App, HttpResponse, Result};
///
-/// fn add_error_header(mut res: dev::ServiceResponse) -> Result> {
+/// Adding a header:
+///
+/// ```
+/// use actix_web::{
+/// dev::ServiceResponse,
+/// http::{header, StatusCode},
+/// middleware::{ErrorHandlerResponse, ErrorHandlers},
+/// web, App, HttpResponse, Result,
+/// };
+///
+/// fn add_error_header(mut res: ServiceResponse) -> Result> {
/// res.response_mut().headers_mut().insert(
/// header::CONTENT_TYPE,
/// header::HeaderValue::from_static("Error"),
/// );
+///
+/// // body is unchanged, map to "left" slot
/// Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
/// }
///
@@ -67,24 +75,63 @@ type DefaultHandler = Option>>;
/// .wrap(ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, add_error_header))
/// .service(web::resource("/").route(web::get().to(HttpResponse::InternalServerError)));
/// ```
-/// ## Registering default handler
+///
+/// Modifying response body:
+///
/// ```
-/// # use actix_web::http::{header, StatusCode};
-/// # use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers};
-/// # use actix_web::{dev, web, App, HttpResponse, Result};
-/// fn add_error_header(mut res: dev::ServiceResponse) -> Result> {
+/// use actix_web::{
+/// dev::ServiceResponse,
+/// http::{header, StatusCode},
+/// middleware::{ErrorHandlerResponse, ErrorHandlers},
+/// web, App, HttpResponse, Result,
+/// };
+///
+/// fn add_error_body(res: ServiceResponse) -> Result> {
+/// // split service response into request and response components
+/// let (req, res) = res.into_parts();
+///
+/// // set body of response to modified body
+/// let res = res.set_body("An error occurred.");
+///
+/// // modified bodies need to be boxed and placed in the "right" slot
+/// let res = ServiceResponse::new(req, res)
+/// .map_into_boxed_body()
+/// .map_into_right_body();
+///
+/// Ok(ErrorHandlerResponse::Response(res))
+/// }
+///
+/// let app = App::new()
+/// .wrap(ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, add_error_body))
+/// .service(web::resource("/").route(web::get().to(HttpResponse::InternalServerError)));
+/// ```
+///
+/// Registering default handler:
+///
+/// ```
+/// # use actix_web::{
+/// # dev::ServiceResponse,
+/// # http::{header, StatusCode},
+/// # middleware::{ErrorHandlerResponse, ErrorHandlers},
+/// # web, App, HttpResponse, Result,
+/// # };
+/// fn add_error_header(mut res: ServiceResponse) -> Result> {
/// res.response_mut().headers_mut().insert(
/// header::CONTENT_TYPE,
/// header::HeaderValue::from_static("Error"),
/// );
+///
+/// // body is unchanged, map to "left" slot
/// Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
/// }
///
-/// fn handle_bad_request(mut res: dev::ServiceResponse) -> Result> {
+/// fn handle_bad_request(mut res: ServiceResponse) -> Result> {
/// res.response_mut().headers_mut().insert(
/// header::CONTENT_TYPE,
/// header::HeaderValue::from_static("Bad Request Error"),
/// );
+///
+/// // body is unchanged, map to "left" slot
/// Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
/// }
///
@@ -98,20 +145,24 @@ type DefaultHandler = Option>>;
/// )
/// .service(web::resource("/").route(web::get().to(HttpResponse::InternalServerError)));
/// ```
-/// Alternatively, you can set default handlers for only client or only server errors:
///
-/// ```rust
-/// # use actix_web::http::{header, StatusCode};
-/// # use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers};
-/// # use actix_web::{dev, web, App, HttpResponse, Result};
-/// # fn add_error_header(mut res: dev::ServiceResponse) -> Result> {
+/// You can set default handlers for all client (4xx) or all server (5xx) errors:
+///
+/// ```
+/// # use actix_web::{
+/// # dev::ServiceResponse,
+/// # http::{header, StatusCode},
+/// # middleware::{ErrorHandlerResponse, ErrorHandlers},
+/// # web, App, HttpResponse, Result,
+/// # };
+/// # fn add_error_header(mut res: ServiceResponse) -> Result> {
/// # res.response_mut().headers_mut().insert(
/// # header::CONTENT_TYPE,
/// # header::HeaderValue::from_static("Error"),
/// # );
/// # Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
/// # }
-/// # fn handle_bad_request(mut res: dev::ServiceResponse) -> Result> {
+/// # fn handle_bad_request(mut res: ServiceResponse) -> Result> {
/// # res.response_mut().headers_mut().insert(
/// # header::CONTENT_TYPE,
/// # header::HeaderValue::from_static("Bad Request Error"),
diff --git a/actix-web/src/request.rs b/actix-web/src/request.rs
index 7bd265fa8..96463159f 100644
--- a/actix-web/src/request.rs
+++ b/actix-web/src/request.rs
@@ -260,7 +260,7 @@ impl HttpRequest {
Ref::map(self.extensions(), |data| data.get().unwrap())
}
- /// App config
+ /// Returns a reference to the application's connection configuration.
#[inline]
pub fn app_config(&self) -> &AppConfig {
self.app_state().config()
diff --git a/actix-web/src/response/responder.rs b/actix-web/src/response/responder.rs
index da8091981..965163a1f 100644
--- a/actix-web/src/response/responder.rs
+++ b/actix-web/src/response/responder.rs
@@ -21,7 +21,7 @@ use crate::{Error, HttpRequest, HttpResponse};
/// - `HttpResponse` and `HttpResponseBuilder`
/// - `Option` where `R: Responder`
/// - `Result` where `R: Responder` and [`E: ResponseError`](crate::ResponseError)
-/// - `(R, StatusCode) where `R: Responder`
+/// - `(R, StatusCode)` where `R: Responder`
/// - `&'static str`, `String`, `&'_ String`, `Cow<'_, str>`, [`ByteString`](bytestring::ByteString)
/// - `&'static [u8]`, `Vec`, `Bytes`, `BytesMut`
/// - [`Json`](crate::web::Json) and [`Form`](crate::web::Form) where `T: Serialize`
diff --git a/actix-web/src/service.rs b/actix-web/src/service.rs
index ea23f09f5..f7692ce16 100644
--- a/actix-web/src/service.rs
+++ b/actix-web/src/service.rs
@@ -238,11 +238,7 @@ impl ServiceRequest {
self.req.connection_info()
}
- /// Returns reference to the Path parameters.
- ///
- /// Params is a container for URL parameters. A variable segment is specified in the form
- /// `{identifier}`, where the identifier can be used later in a request handler to access the
- /// matched value for that segment.
+ /// Counterpart to [`HttpRequest::match_info`].
#[inline]
pub fn match_info(&self) -> &Path {
self.req.match_info()
@@ -267,12 +263,13 @@ impl ServiceRequest {
}
/// Returns a reference to the application's resource map.
+ /// Counterpart to [`HttpRequest::resource_map`].
#[inline]
pub fn resource_map(&self) -> &ResourceMap {
self.req.resource_map()
}
- /// Returns a reference to the application's configuration.
+ /// Counterpart to [`HttpRequest::app_config`].
#[inline]
pub fn app_config(&self) -> &AppConfig {
self.req.app_config()
diff --git a/actix-web/src/test/mod.rs b/actix-web/src/test/mod.rs
index 9c6121151..5d9367b82 100644
--- a/actix-web/src/test/mod.rs
+++ b/actix-web/src/test/mod.rs
@@ -10,12 +10,16 @@
//! # Calling Test Service
//! - [`TestRequest`]
//! - [`call_service`]
+//! - [`try_call_service`]
//! - [`call_and_read_body`]
//! - [`call_and_read_body_json`]
+//! - [`try_call_and_read_body_json`]
//!
//! # Reading Response Payloads
//! - [`read_body`]
+//! - [`try_read_body`]
//! - [`read_body_json`]
+//! - [`try_read_body_json`]
// TODO: more docs on generally how testing works with these parts
@@ -31,7 +35,8 @@ pub use self::test_services::{default_service, ok_service, simple_service, statu
#[allow(deprecated)]
pub use self::test_utils::{
call_and_read_body, call_and_read_body_json, call_service, init_service, read_body,
- read_body_json, read_response, read_response_json,
+ read_body_json, read_response, read_response_json, try_call_and_read_body_json,
+ try_call_service, try_read_body, try_read_body_json,
};
#[cfg(test)]
diff --git a/actix-web/src/test/test_utils.rs b/actix-web/src/test/test_utils.rs
index 6f0926f35..b985c3b36 100644
--- a/actix-web/src/test/test_utils.rs
+++ b/actix-web/src/test/test_utils.rs
@@ -100,6 +100,15 @@ where
.expect("test service call returned error")
}
+/// Fallible version of [`call_service`] that allows testing response completion errors.
+pub async fn try_call_service(app: &S, req: R) -> Result
+where
+ S: Service, Error = E>,
+ E: std::fmt::Debug,
+{
+ app.call(req).await
+}
+
/// Helper function that returns a response body of a TestRequest
///
/// # Examples
@@ -185,13 +194,23 @@ pub async fn read_body(res: ServiceResponse) -> Bytes
where
B: MessageBody,
{
- let body = res.into_body();
- body::to_bytes(body)
+ try_read_body(res)
.await
.map_err(Into::>::into)
.expect("error reading test response body")
}
+/// Fallible version of [`read_body`] that allows testing MessageBody reading errors.
+pub async fn try_read_body(
+ res: ServiceResponse,
+) -> Result::Error>
+where
+ B: MessageBody,
+{
+ let body = res.into_body();
+ body::to_bytes(body).await
+}
+
/// Helper function that returns a deserialized response body of a ServiceResponse.
///
/// # Examples
@@ -240,18 +259,27 @@ where
B: MessageBody,
T: DeserializeOwned,
{
- let body = read_body(res).await;
-
- serde_json::from_slice(&body).unwrap_or_else(|err| {
+ try_read_body_json(res).await.unwrap_or_else(|err| {
panic!(
- "could not deserialize body into a {}\nerr: {}\nbody: {:?}",
+ "could not deserialize body into a {}\nerr: {}",
std::any::type_name::(),
err,
- body,
)
})
}
+/// Fallible version of [`read_body_json`] that allows testing response deserialization errors.
+pub async fn try_read_body_json(res: ServiceResponse) -> Result>
+where
+ B: MessageBody,
+ T: DeserializeOwned,
+{
+ let body = try_read_body(res)
+ .await
+ .map_err(Into::>::into)?;
+ serde_json::from_slice(&body).map_err(Into::>::into)
+}
+
/// Helper function that returns a deserialized response body of a TestRequest
///
/// # Examples
@@ -299,8 +327,23 @@ where
B: MessageBody,
T: DeserializeOwned,
{
- let res = call_service(app, req).await;
- read_body_json(res).await
+ try_call_and_read_body_json(app, req).await.unwrap()
+}
+
+/// Fallible version of [`call_and_read_body_json`] that allows testing service call errors.
+pub async fn try_call_and_read_body_json(
+ app: &S,
+ req: Request,
+) -> Result>
+where
+ S: Service, Error = Error>,
+ B: MessageBody,
+ T: DeserializeOwned,
+{
+ let res = try_call_service(app, req)
+ .await
+ .map_err(Into::>::into)?;
+ try_read_body_json(res).await
}
#[doc(hidden)]
@@ -358,7 +401,7 @@ mod tests {
assert_eq!(result, Bytes::from_static(b"delete!"));
}
- #[derive(Serialize, Deserialize)]
+ #[derive(Serialize, Deserialize, Debug)]
pub struct Person {
id: String,
name: String,
@@ -383,6 +426,26 @@ mod tests {
assert_eq!(&result.id, "12345");
}
+ #[actix_rt::test]
+ async fn test_try_response_json_error() {
+ let app = init_service(App::new().service(web::resource("/people").route(
+ web::post().to(|person: web::Json| HttpResponse::Ok().json(person)),
+ )))
+ .await;
+
+ let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
+
+ let req = TestRequest::post()
+ .uri("/animals") // Not registered to ensure an error occurs.
+ .insert_header((header::CONTENT_TYPE, "application/json"))
+ .set_payload(payload)
+ .to_request();
+
+ let result: Result> =
+ try_call_and_read_body_json(&app, req).await;
+ assert!(result.is_err());
+ }
+
#[actix_rt::test]
async fn test_body_json() {
let app = init_service(App::new().service(web::resource("/people").route(
@@ -403,6 +466,27 @@ mod tests {
assert_eq!(&result.name, "User name");
}
+ #[actix_rt::test]
+ async fn test_try_body_json_error() {
+ let app = init_service(App::new().service(web::resource("/people").route(
+ web::post().to(|person: web::Json| HttpResponse::Ok().json(person)),
+ )))
+ .await;
+
+ // Use a number for id to cause a deserialization error.
+ let payload = r#"{"id":12345,"name":"User name"}"#.as_bytes();
+
+ let res = TestRequest::post()
+ .uri("/people")
+ .insert_header((header::CONTENT_TYPE, "application/json"))
+ .set_payload(payload)
+ .send_request(&app)
+ .await;
+
+ let result: Result> = try_read_body_json(res).await;
+ assert!(result.is_err());
+ }
+
#[actix_rt::test]
async fn test_request_response_form() {
let app = init_service(App::new().service(web::resource("/people").route(
diff --git a/actix-web/tests/test_httpserver.rs b/actix-web/tests/test_httpserver.rs
index 86e0575f3..861d76d93 100644
--- a/actix-web/tests/test_httpserver.rs
+++ b/actix-web/tests/test_httpserver.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::uninlined_format_args)]
+
#[cfg(feature = "openssl")]
extern crate tls_openssl as openssl;
diff --git a/awc/CHANGES.md b/awc/CHANGES.md
index 7892d9339..03cbf61d4 100644
--- a/awc/CHANGES.md
+++ b/awc/CHANGES.md
@@ -1,22 +1,35 @@
# Changes
-## Unreleased - 2022-xx-xx
+## Unreleased - 2023-xx-xx
+
+## 3.1.1 - 2023-02-26
+
### Changed
+
+- `client::Connect` is now public to allow tunneling connection with `client::Connector`.
+
+## 3.1.0 - 2023-01-21
+
+### Changed
+
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
-
## 3.0.1 - 2022-08-25
+
### Changed
+
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
### Fixed
+
- Fixed handling of redirection requests that begin with `//`. [#2840]
[#2840]: https://github.com/actix/actix-web/pull/2840
-
## 3.0.0 - 2022-03-07
+
### Dependencies
+
- Updated `actix-*` to Tokio v1-based versions. [#1813]
- Updated `bytes` to `1.0`. [#1813]
- Updated `cookie` to `0.16`. [#2555]
@@ -25,6 +38,7 @@
- Updated `tokio` to `1`.
### Added
+
- `trust-dns` crate feature to enable `trust-dns-resolver` as client DNS resolver; disabled by default. [#1969]
- `cookies` crate feature; enabled by default. [#2619]
- `compress-brotli` crate feature; enabled by default. [#2250]
@@ -41,6 +55,7 @@
- `ClientBuilder::add_default_header()` (and deprecate `ClientBuilder::header()`). [#2510]
### Changed
+
- `client::Connector` type now only has one generic type for `actix_service::Service`. [#2063]
- `client::error::ConnectError` Resolver variant contains `Box` type. [#1905]
- `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905]
@@ -58,6 +73,7 @@
- Minimum supported Rust version (MSRV) is now 1.54.
### Fixed
+
- Send headers along with redirected requests. [#2310]
- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503]
- Remove unnecessary `Unpin` bounds on `*::send_stream`. [#2553]
@@ -66,6 +82,7 @@
- `impl Stream` for `ClientResponse` no longer requires the body type be `Unpin`. [#2546]
### Removed
+
- `compress` crate feature. [#2250]
- `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869]
- `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869]
@@ -75,10 +92,10 @@
- `ClientBuilder::default` function [#2008]
### Security
+
- `cookie` upgrade addresses [`RUSTSEC-2020-0071`].
-[`RUSTSEC-2020-0071`]: https://rustsec.org/advisories/RUSTSEC-2020-0071.html
-
+[`rustsec-2020-0071`]: https://rustsec.org/advisories/RUSTSEC-2020-0071.html
[#1813]: https://github.com/actix/actix-web/pull/1813
[#1869]: https://github.com/actix/actix-web/pull/1869
[#1905]: https://github.com/actix/actix-web/pull/1905
@@ -108,46 +125,48 @@
[#2553]: https://github.com/actix/actix-web/pull/2553
[#2555]: https://github.com/actix/actix-web/pull/2555
-
3.0.0 Pre-Releases
## 3.0.0-beta.21 - 2022-02-16
+
- No significant changes since `3.0.0-beta.20`.
-
## 3.0.0-beta.20 - 2022-01-31
+
- No significant changes since `3.0.0-beta.19`.
-
## 3.0.0-beta.19 - 2022-01-21
+
- No significant changes since `3.0.0-beta.18`.
-
## 3.0.0-beta.18 - 2022-01-04
+
- Minimum supported Rust version (MSRV) is now 1.54.
-
## 3.0.0-beta.17 - 2021-12-29
+
### Changed
+
- Update `cookie` dependency (re-exported) to `0.16`. [#2555]
### Security
+
- `cookie` upgrade addresses [`RUSTSEC-2020-0071`].
[#2555]: https://github.com/actix/actix-web/pull/2555
-[`RUSTSEC-2020-0071`]: https://rustsec.org/advisories/RUSTSEC-2020-0071.html
-
+[`rustsec-2020-0071`]: https://rustsec.org/advisories/RUSTSEC-2020-0071.html
## 3.0.0-beta.16 - 2021-12-29
+
- `*::send_json` and `*::send_form` methods now receive `impl Serialize`. [#2553]
- `FrozenClientRequest::extra_header` now uses receives an `impl TryIntoHeaderPair`. [#2553]
- Remove unnecessary `Unpin` bounds on `*::send_stream`. [#2553]
[#2553]: https://github.com/actix/actix-web/pull/2553
-
## 3.0.0-beta.15 - 2021-12-27
+
- Rename `Connector::{ssl => openssl}`. [#2503]
- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503]
- `ClientRequest::send_body` now takes an `impl MessageBody`. [#2546]
@@ -159,89 +178,96 @@
[#2503]: https://github.com/actix/actix-web/pull/2503
[#2546]: https://github.com/actix/actix-web/pull/2546
-
## 3.0.0-beta.14 - 2021-12-17
+
- Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510]
[#2510]: https://github.com/actix/actix-web/pull/2510
-
## 3.0.0-beta.13 - 2021-12-11
+
- No significant changes since `3.0.0-beta.12`.
-
## 3.0.0-beta.12 - 2021-11-30
+
- Update `actix-tls` to `3.0.0-rc.1`. [#2474]
[#2474]: https://github.com/actix/actix-web/pull/2474
-
## 3.0.0-beta.11 - 2021-11-22
+
- No significant changes from `3.0.0-beta.10`.
-
## 3.0.0-beta.10 - 2021-11-15
+
- No significant changes from `3.0.0-beta.9`.
-
## 3.0.0-beta.9 - 2021-10-20
+
- Updated rustls to v0.20. [#2414]
[#2414]: https://github.com/actix/actix-web/pull/2414
-
## 3.0.0-beta.8 - 2021-09-09
+
### Changed
+
- Send headers within the redirect requests. [#2310]
[#2310]: https://github.com/actix/actix-web/pull/2310
-
## 3.0.0-beta.7 - 2021-06-26
+
### Changed
+
- Change compression algorithm features flags. [#2250]
[#2250]: https://github.com/actix/actix-web/pull/2250
-
## 3.0.0-beta.6 - 2021-06-17
+
- No significant changes since 3.0.0-beta.5.
-
## 3.0.0-beta.5 - 2021-04-17
+
### Removed
+
- Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148]
[#2148]: https://github.com/actix/actix-web/pull/2148
-
## 3.0.0-beta.4 - 2021-04-02
+
### Added
+
- Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114]
### Changed
+
- `ConnectorService` type is renamed to `BoxConnectorService`. [#2081]
- Fix http/https encoding when enabling `compress` feature. [#2116]
-- Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header
- methods now take `TryIntoHeaderPair` tuples. [#2094]
+- Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header methods now take `TryIntoHeaderPair` tuples. [#2094]
[#2081]: https://github.com/actix/actix-web/pull/2081
[#2094]: https://github.com/actix/actix-web/pull/2094
[#2114]: https://github.com/actix/actix-web/pull/2114
[#2116]: https://github.com/actix/actix-web/pull/2116
-
## 3.0.0-beta.3 - 2021-03-08
+
### Added
+
- `ClientResponse::timeout` for set the timeout of collecting response body. [#1931]
- `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024]
### Changed
+
- Feature `cookies` is now optional and enabled by default. [#1981]
- `ClientBuilder::connector` method would take `actix_http::client::Connector` type. [#2008]
- Basic auth password now takes blank passwords as an empty string instead of Option. [#2050]
### Removed
+
- `ClientBuilder::default` function [#2008]
[#1931]: https://github.com/actix/actix-web/pull/1931
@@ -250,17 +276,20 @@
[#2024]: https://github.com/actix/actix-web/pull/2024
[#2050]: https://github.com/actix/actix-web/pull/2050
-
## 3.0.0-beta.2 - 2021-02-10
+
### Added
+
- `ClientRequest::insert_header` method which allows using typed headers. [#1869]
- `ClientRequest::append_header` method which allows using typed headers. [#1869]
- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
### Changed
+
- Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905]
### Removed
+
- `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869]
- `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869]
- `ClientRequest::set_header_if_none`; use `ClientRequest::insert_header_if_none`. [#1869]
@@ -270,9 +299,10 @@
[#1905]: https://github.com/actix/actix-web/pull/1905
[#1969]: https://github.com/actix/actix-web/pull/1969
-
## 3.0.0-beta.1 - 2021-01-07
+
### Changed
+
- Update `rand` to `0.8`
- Update `bytes` to `1.0`. [#1813]
- Update `rust-tls` to `0.19`. [#1813]
@@ -282,53 +312,62 @@
## 2.0.3 - 2020-11-29
+
### Fixed
+
- Ensure `actix-http` dependency uses same `serde_urlencoded`.
-
## 2.0.2 - 2020-11-25
+
### Changed
+
- Upgrade `serde_urlencoded` to `0.7`. [#1773]
[#1773]: https://github.com/actix/actix-web/pull/1773
-
## 2.0.1 - 2020-10-30
+
### Changed
+
- 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
+
### Changed
+
- `Client::build` was renamed to `Client::builder`.
-
## 2.0.0-beta.4 - 2020-09-09
+
### Changed
+
- Update actix-codec & actix-tls dependencies.
-
## 2.0.0-beta.3 - 2020-08-17
+
### Changed
+
- Update `rustls` to 0.18
-
## 2.0.0-beta.2 - 2020-07-21
+
### Changed
+
- Update `actix-http` dependency to 2.0.0-beta.2
-
## [2.0.0-beta.1] - 2020-07-14
+
### Changed
+
- Update `actix-http` dependency to 2.0.0-beta.1
## [2.0.0-alpha.2] - 2020-05-21
@@ -360,26 +399,22 @@
- Migrate to `std::future`
-
## [0.2.8] - 2019-11-06
- Add support for setting query from Serialize type for client request.
-
## [0.2.7] - 2019-09-25
### Added
- Remaining getter methods for `ClientRequest`'s private `head` field #1101
-
## [0.2.6] - 2019-09-12
### Added
- Export frozen request related types.
-
## [0.2.5] - 2019-09-11
### Added
@@ -390,7 +425,6 @@
- Ensure that the `Host` header is set when initiating a WebSocket client connection.
-
## [0.2.4] - 2019-08-13
### Changed
@@ -399,14 +433,12 @@
- Update serde_urlencoded to "0.6.1"
-
## [0.2.3] - 2019-08-01
### Added
- Add `rustls` support
-
## [0.2.2] - 2019-07-01
### Changed
@@ -415,7 +447,6 @@
- Upgrade `rand` dependency version to 0.7
-
## [0.2.1] - 2019-06-05
### Added
@@ -432,7 +463,6 @@
- Upgrade actix-http dependency.
-
## [0.1.1] - 2019-04-19
### Added
@@ -443,19 +473,16 @@
- `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref
-
## [0.1.0] - 2019-04-16
- No changes
-
## [0.1.0-alpha.6] - 2019-04-14
### Changed
- Do not set default headers for websocket request
-
## [0.1.0-alpha.5] - 2019-04-12
### Changed
@@ -466,14 +493,12 @@
- Add Debug impl for BoxedSocket
-
## [0.1.0-alpha.4] - 2019-04-08
### Changed
- Update actix-http dependency
-
## [0.1.0-alpha.3] - 2019-04-02
### Added
@@ -482,7 +507,6 @@
- `ClientResponse::json()` - Loads and parse `application/json` encoded body
-
### Changed
- `ClientRequest::json()` accepts reference instead of object.
@@ -491,7 +515,6 @@
- Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()`
-
## [0.1.0-alpha.2] - 2019-03-29
### Added
@@ -504,14 +527,12 @@
- Re-export `actix_http::client::Connector`.
-
### Changed
- Allow to override request's uri
- Export `ws` sub-module with websockets related types
-
## [0.1.0-alpha.1] - 2019-03-28
- Initial impl
diff --git a/awc/Cargo.toml b/awc/Cargo.toml
index cf64eed49..8a75e28f7 100644
--- a/awc/Cargo.toml
+++ b/awc/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "awc"
-version = "3.0.1"
+version = "3.1.1"
authors = ["Nikolay Kim "]
description = "Async HTTP and WebSocket client library"
keywords = ["actix", "http", "framework", "async", "web"]
@@ -57,13 +57,12 @@ dangerous-h2c = []
[dependencies]
actix-codec = "0.5"
actix-service = "2"
-actix-http = { version = "3", features = ["http2", "ws"] }
+actix-http = { version = "3.3", features = ["http2", "ws"] }
actix-rt = { version = "2.1", default-features = false }
actix-tls = { version = "3", features = ["connect", "uri"] }
actix-utils = "3"
-ahash = "0.7"
-base64 = "0.13"
+base64 = "0.21"
bytes = "1"
cfg-if = "1"
derive_more = "0.99.5"
@@ -80,7 +79,7 @@ rand = "0.8"
serde = "1.0"
serde_json = "1.0"
serde_urlencoded = "0.7"
-tokio = { version = "1.8.4", features = ["sync"] }
+tokio = { version = "1.24.2", features = ["sync"] }
cookie = { version = "0.16", features = ["percent-encode"], optional = true }
@@ -99,14 +98,14 @@ actix-utils = "3"
actix-web = { version = "4", features = ["openssl"] }
brotli = "3.3.3"
-const-str = "0.4"
+const-str = "0.3"
env_logger = "0.9"
flate2 = "1.0.13"
futures-util = { version = "0.3.17", default-features = false }
static_assertions = "1.1"
rcgen = "0.9"
rustls-pemfile = "1"
-tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] }
+tokio = { version = "1.24.2", features = ["rt-multi-thread", "macros"] }
zstd = "0.12"
[[example]]
diff --git a/awc/README.md b/awc/README.md
index 9f47e663b..a9d411067 100644
--- a/awc/README.md
+++ b/awc/README.md
@@ -3,16 +3,16 @@
> Async HTTP and WebSocket client library.
[](https://crates.io/crates/awc)
-[](https://docs.rs/awc/3.0.1)
+[](https://docs.rs/awc/3.1.1)

-[](https://deps.rs/crate/awc/3.0.1)
+[](https://deps.rs/crate/awc/3.1.1)
[](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources
- [API Documentation](https://docs.rs/awc)
- [Example Project](https://github.com/actix/examples/tree/master/https-tls/awc-https)
-- Minimum Supported Rust Version (MSRV): 1.54
+- Minimum Supported Rust Version (MSRV): 1.59
## Example
diff --git a/awc/examples/client.rs b/awc/examples/client.rs
index 16ad330b8..26edcfd62 100644
--- a/awc/examples/client.rs
+++ b/awc/examples/client.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::uninlined_format_args)]
+
use std::error::Error as StdError;
#[tokio::main]
diff --git a/awc/src/builder.rs b/awc/src/builder.rs
index c101d18f0..79838a3f6 100644
--- a/awc/src/builder.rs
+++ b/awc/src/builder.rs
@@ -1,5 +1,7 @@
use std::{convert::TryFrom, fmt, net::IpAddr, rc::Rc, time::Duration};
+use base64::prelude::*;
+
use actix_http::{
error::HttpError,
header::{self, HeaderMap, HeaderName, TryIntoHeaderPair},
@@ -210,7 +212,7 @@ where
};
self.add_default_header((
header::AUTHORIZATION,
- format!("Basic {}", base64::encode(&auth)),
+ format!("Basic {}", BASE64_STANDARD.encode(auth)),
))
}
diff --git a/awc/src/client/pool.rs b/awc/src/client/pool.rs
index 47c1fdd67..632608c45 100644
--- a/awc/src/client/pool.rs
+++ b/awc/src/client/pool.rs
@@ -2,7 +2,7 @@
use std::{
cell::RefCell,
- collections::VecDeque,
+ collections::{HashMap, VecDeque},
future::Future,
io,
ops::Deref,
@@ -17,7 +17,6 @@ use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
use actix_http::Protocol;
use actix_rt::time::{sleep, Sleep};
use actix_service::Service;
-use ahash::AHashMap;
use futures_core::future::LocalBoxFuture;
use futures_util::FutureExt as _;
use http::uri::Authority;
@@ -62,7 +61,7 @@ where
{
fn new(config: ConnectorConfig) -> Self {
let permits = Arc::new(Semaphore::new(config.limit));
- let available = RefCell::new(AHashMap::default());
+ let available = RefCell::new(HashMap::default());
Self(Rc::new(ConnectionPoolInnerPriv {
config,
@@ -124,7 +123,7 @@ where
Io: AsyncWrite + Unpin + 'static,
{
config: ConnectorConfig,
- available: RefCell>>>,
+ available: RefCell>>>,
permits: Arc,
}
diff --git a/awc/src/lib.rs b/awc/src/lib.rs
index 412ccbe61..42f029669 100644
--- a/awc/src/lib.rs
+++ b/awc/src/lib.rs
@@ -105,7 +105,8 @@
#![allow(
clippy::type_complexity,
clippy::borrow_interior_mutable_const,
- clippy::needless_doctest_main
+ clippy::needless_doctest_main,
+ clippy::uninlined_format_args
)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
@@ -138,7 +139,7 @@ pub mod http {
}
pub use self::builder::ClientBuilder;
-pub use self::client::{Client, Connector};
+pub use self::client::{Client, Connect, Connector};
pub use self::connect::{BoxConnectorService, BoxedSocket, ConnectRequest, ConnectResponse};
pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder};
pub use self::request::ClientRequest;
diff --git a/awc/src/request.rs b/awc/src/request.rs
index 102db3c16..d3a4eda8c 100644
--- a/awc/src/request.rs
+++ b/awc/src/request.rs
@@ -1,5 +1,6 @@
use std::{convert::TryFrom, fmt, net, rc::Rc, time::Duration};
+use base64::prelude::*;
use bytes::Bytes;
use futures_core::Stream;
use serde::Serialize;
@@ -238,7 +239,7 @@ impl ClientRequest {
self.insert_header((
header::AUTHORIZATION,
- format!("Basic {}", base64::encode(&auth)),
+ format!("Basic {}", BASE64_STANDARD.encode(auth)),
))
}
@@ -565,6 +566,8 @@ mod tests {
assert_eq!(req.head.version, Version::HTTP_2);
let _ = req.headers_mut();
+
+ #[allow(clippy::let_underscore_future)]
let _ = req.send_body("");
}
diff --git a/awc/src/ws.rs b/awc/src/ws.rs
index 4ef2e2b36..406368e62 100644
--- a/awc/src/ws.rs
+++ b/awc/src/ws.rs
@@ -28,6 +28,8 @@
use std::{convert::TryFrom, fmt, net::SocketAddr, str};
+use base64::prelude::*;
+
use actix_codec::Framed;
use actix_http::{ws, Payload, RequestHead};
use actix_rt::time::timeout;
@@ -236,7 +238,10 @@ impl WebsocketsRequest {
Some(password) => format!("{}:{}", username, password),
None => format!("{}:", username),
};
- self.header(AUTHORIZATION, format!("Basic {}", base64::encode(&auth)))
+ self.header(
+ AUTHORIZATION,
+ format!("Basic {}", BASE64_STANDARD.encode(auth)),
+ )
}
/// Set HTTP bearer authentication header
@@ -321,7 +326,7 @@ impl WebsocketsRequest {
// Generate a random key for the `Sec-WebSocket-Key` header which is a base64-encoded
// (see RFC 4648 §4) value that, when decoded, is 16 bytes in length (RFC 6455 §1.3).
let sec_key: [u8; 16] = rand::random();
- let key = base64::encode(sec_key);
+ let key = BASE64_STANDARD.encode(sec_key);
self.head.headers.insert(
header::SEC_WEBSOCKET_KEY,
@@ -503,6 +508,8 @@ mod tests {
.unwrap(),
"Bearer someS3cr3tAutht0k3n"
);
+
+ #[allow(clippy::let_underscore_future)]
let _ = req.connect();
}
diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs
index db987fdfa..9c3543ff0 100644
--- a/awc/tests/test_client.rs
+++ b/awc/tests/test_client.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::uninlined_format_args)]
+
use std::{
collections::HashMap,
convert::Infallible,
@@ -11,6 +13,7 @@ use std::{
};
use actix_utils::future::ok;
+use base64::prelude::*;
use bytes::Bytes;
use cookie::Cookie;
use futures_util::stream;
@@ -781,7 +784,7 @@ async fn client_basic_auth() {
.unwrap()
.to_str()
.unwrap()
- == format!("Basic {}", base64::encode("username:password"))
+ == format!("Basic {}", BASE64_STANDARD.encode("username:password"))
{
HttpResponse::Ok()
} else {
diff --git a/scripts/bump b/scripts/bump
index 33ea52010..40d43d429 100755
--- a/scripts/bump
+++ b/scripts/bump
@@ -51,7 +51,6 @@ cat "$CHANGELOG_FILE" |
if [ "$(wc -w "$CHANGE_CHUNK_FILE" | awk '{ print $1 }')" = "0" ]; then
echo "- No significant changes since \`$CURRENT_VERSION\`." >"$CHANGE_CHUNK_FILE"
echo >>"$CHANGE_CHUNK_FILE"
- echo >>"$CHANGE_CHUNK_FILE"
fi
if [ -n "${2-}" ]; then
@@ -82,7 +81,6 @@ sed -i.bak -E "s/^version ?= ?\"[^\"]+\"$/version = \"$NEW_VERSION\"/" "$CARGO_M
(
sed '/Unreleased/ q' "$CHANGELOG_FILE" # up to unreleased heading
echo # blank line
- echo # blank line
echo "## $NEW_VERSION - $DATE" # new version heading
cat "$CHANGE_CHUNK_FILE" # previously unreleased changes
sed "/$CURRENT_VERSION/ q" "$CHANGELOG_FILE" | tail -n 1 # the previous version heading
@@ -90,6 +88,9 @@ sed -i.bak -E "s/^version ?= ?\"[^\"]+\"$/version = \"$NEW_VERSION\"/" "$CARGO_M
) >"$CHANGELOG_FILE.bak"
mv "$CHANGELOG_FILE.bak" "$CHANGELOG_FILE"
+# format CHANGELOG file according to prettier
+npx -y prettier --write "$CHANGELOG_FILE" || true
+
# done; remove backup files
rm -f $CARGO_MANIFEST.bak
rm -f $CHANGELOG_FILE.bak
@@ -139,12 +140,14 @@ GIT_TAG="$(echo $SHORT_PACKAGE_NAME-v$NEW_VERSION)"
RELEASE_TITLE="$(echo $PACKAGE_NAME: v$NEW_VERSION)"
if [ "$(echo $NEW_VERSION | grep beta)" ] || [ "$(echo $NEW_VERSION | grep rc)" ] || [ "$(echo $NEW_VERSION | grep alpha)" ]; then
- PRERELEASE="--prerelease"
+ FLAGS="--prerelease"
+else
+ FLAGS="--latest"
fi
echo
echo "GitHub release command:"
-GH_CMD="gh release create \"$GIT_TAG\" --draft --title \"$RELEASE_TITLE\" --notes-file \"$CHANGE_CHUNK_FILE\" ${PRERELEASE:-}"
+GH_CMD="gh release create \"$GIT_TAG\" --draft --title \"$RELEASE_TITLE\" --notes-file \"$CHANGE_CHUNK_FILE\" ${FLAGS:-}"
echo "$GH_CMD"
read -p "Submit draft GH release: (y/N) " GH_RELEASE