mirror of https://github.com/fafhrd91/actix-web
Merge branch 'master' into asonix/tokio-uring-04
This commit is contained in:
commit
c7e526ed3b
|
@ -29,7 +29,7 @@ actix-web = { version = "4", default-features = false }
|
|||
bitflags = "1"
|
||||
bytes = "1"
|
||||
derive_more = "0.99.5"
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
http-range = "0.1.4"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible, missing_docs, missing_debug_implementations)]
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use actix_service::boxed::{BoxService, BoxServiceFactory};
|
||||
use actix_web::{
|
||||
|
|
|
@ -37,9 +37,8 @@ actix-rt = "2.2"
|
|||
actix-server = "2"
|
||||
awc = { version = "3", default-features = false }
|
||||
|
||||
base64 = "0.13"
|
||||
bytes = "1"
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
futures-core = { version = "0.3.17", default-features = false }
|
||||
http = "0.2.5"
|
||||
log = "0.4"
|
||||
socket2 = "0.4"
|
||||
|
@ -48,7 +47,7 @@ serde_json = "1.0"
|
|||
slab = "0.4"
|
||||
serde_urlencoded = "0.7"
|
||||
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
||||
tokio = { version = "1.8.4", features = ["sync"] }
|
||||
tokio = { version = "1.18.4", features = ["sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-web = { version = "4", default-features = false, features = ["cookies"] }
|
||||
|
|
|
@ -2,6 +2,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")]
|
||||
|
||||
|
@ -87,6 +88,7 @@ pub async fn test_server_with_addr<F: ServerServiceFactory<TcpStream>>(
|
|||
|
||||
// notify TestServer that server and system have shut down
|
||||
// all thread managed resources should be dropped at this point
|
||||
#[allow(clippy::let_underscore_future)]
|
||||
let _ = thread_stop_tx.send(());
|
||||
});
|
||||
|
||||
|
@ -294,6 +296,7 @@ impl Drop for TestServer {
|
|||
// without needing to await anything
|
||||
|
||||
// signal server to stop
|
||||
#[allow(clippy::let_underscore_future)]
|
||||
let _ = self.server.stop(true);
|
||||
|
||||
// signal system to stop
|
||||
|
|
|
@ -2,14 +2,35 @@
|
|||
|
||||
## Unreleased - 2022-xx-xx
|
||||
### Added
|
||||
- Implement `MessageBody` for `Cow<'static, str>` and `Cow<'static, [u8]>`. [#2959]
|
||||
- Implement `MessageBody` for `&mut B` where `B: MessageBody + Unpin`. [#2868]
|
||||
- Implement `MessageBody` for `Pin<B>` where `B::Target: MessageBody`. [#2868]
|
||||
- Automatic h2c detection via new service finalizer `HttpService::tcp_auto_h2c()`. [#2957]
|
||||
- `HeaderMap::retain()` [#2955].
|
||||
- Header name constants in `header` module. [#2956] [#2968]
|
||||
- `CACHE_STATUS`
|
||||
- `CDN_CACHE_CONTROL`
|
||||
- `CROSS_ORIGIN_EMBEDDER_POLICY`
|
||||
- `CROSS_ORIGIN_OPENER_POLICY`
|
||||
- `PERMISSIONS_POLICY`
|
||||
- `X_FORWARDED_FOR`
|
||||
- `X_FORWARDED_HOST`
|
||||
- `X_FORWARDED_PROTO`
|
||||
|
||||
### Fixed
|
||||
- Fix non-empty body of HTTP/2 HEAD responses. [#2920]
|
||||
|
||||
### Performance
|
||||
- Improve overall performance of operations on `Extensions`. [#2890]
|
||||
|
||||
[#2959]: https://github.com/actix/actix-web/pull/2959
|
||||
[#2868]: https://github.com/actix/actix-web/pull/2868
|
||||
[#2890]: https://github.com/actix/actix-web/pull/2890
|
||||
[#2920]: https://github.com/actix/actix-web/pull/2920
|
||||
[#2957]: https://github.com/actix/actix-web/pull/2957
|
||||
[#2955]: https://github.com/actix/actix-web/pull/2955
|
||||
[#2956]: https://github.com/actix/actix-web/pull/2956
|
||||
[#2968]: https://github.com/actix/actix-web/pull/2968
|
||||
|
||||
|
||||
## 3.2.2 - 2022-09-11
|
||||
|
|
|
@ -67,7 +67,7 @@ bytes = "1"
|
|||
bytestring = "1"
|
||||
derive_more = "0.99.5"
|
||||
encoding_rs = "0.8"
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
http = "0.2.5"
|
||||
httparse = "1.5.1"
|
||||
httpdate = "1.0.1"
|
||||
|
@ -77,7 +77,7 @@ mime = "0.3"
|
|||
percent-encoding = "2.1"
|
||||
pin-project-lite = "0.2"
|
||||
smallvec = "1.6.1"
|
||||
tokio = { version = "1.13.1", features = [] }
|
||||
tokio = { version = "1.18.4", features = [] }
|
||||
tokio-util = { version = "0.7", features = ["io", "codec"] }
|
||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
|
||||
|
@ -86,7 +86,7 @@ h2 = { version = "0.3.9", optional = true }
|
|||
|
||||
# websockets
|
||||
local-channel = { version = "0.1", optional = true }
|
||||
base64 = { version = "0.13", optional = true }
|
||||
base64 = { version = "0.21", optional = true }
|
||||
rand = { version = "0.8", optional = true }
|
||||
sha1 = { version = "0.10", optional = true }
|
||||
|
||||
|
@ -96,7 +96,7 @@ actix-tls = { version = "3", default-features = false, optional = true }
|
|||
# compress-*
|
||||
brotli = { version = "3.3.3", optional = true }
|
||||
flate2 = { version = "1.0.13", optional = true }
|
||||
zstd = { version = "0.11", optional = true }
|
||||
zstd = { version = "0.12", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-http-test = { version = "3", features = ["openssl"] }
|
||||
|
@ -105,9 +105,9 @@ actix-tls = { version = "3", features = ["openssl"] }
|
|||
actix-web = "4"
|
||||
|
||||
async-stream = "0.3"
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
criterion = { version = "0.4", features = ["html_reports"] }
|
||||
env_logger = "0.9"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
memchr = "2.4"
|
||||
once_cell = "1.9"
|
||||
rcgen = "0.9"
|
||||
|
@ -119,7 +119,7 @@ serde_json = "1.0"
|
|||
static_assertions = "1"
|
||||
tls-openssl = { package = "openssl", version = "0.10.9" }
|
||||
tls-rustls = { package = "rustls", version = "0.20.0" }
|
||||
tokio = { version = "1.8.4", features = ["net", "rt", "macros"] }
|
||||
tokio = { version = "1.18.4", features = ["net", "rt", "macros"] }
|
||||
|
||||
[[example]]
|
||||
name = "ws"
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
|
||||
const CODES: &[u16] = &[0, 1000, 201, 800, 550];
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
//! An example that supports automatic selection of plaintext h1/h2c connections.
|
||||
//!
|
||||
//! Notably, both the following commands will work.
|
||||
//! ```console
|
||||
//! $ curl --http1.1 'http://localhost:8080/'
|
||||
//! $ curl --http2-prior-knowledge 'http://localhost:8080/'
|
||||
//! ```
|
||||
|
||||
use std::{convert::Infallible, io};
|
||||
|
||||
use actix_http::{HttpService, Request, Response, StatusCode};
|
||||
use actix_server::Server;
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> io::Result<()> {
|
||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||
|
||||
Server::build()
|
||||
.bind("h2c-detect", ("127.0.0.1", 8080), || {
|
||||
HttpService::build()
|
||||
.finish(|_req: Request| async move {
|
||||
Ok::<_, Infallible>(Response::build(StatusCode::OK).body("Hello!"))
|
||||
})
|
||||
.tcp_auto_h2c()
|
||||
})?
|
||||
.workers(2)
|
||||
.run()
|
||||
.await
|
||||
}
|
|
@ -120,7 +120,7 @@ pub trait MessageBody {
|
|||
}
|
||||
|
||||
mod foreign_impls {
|
||||
use std::ops::DerefMut;
|
||||
use std::{borrow::Cow, ops::DerefMut};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -324,6 +324,39 @@ mod foreign_impls {
|
|||
}
|
||||
}
|
||||
|
||||
impl MessageBody for Cow<'static, [u8]> {
|
||||
type Error = Infallible;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
let bytes = match mem::take(self.get_mut()) {
|
||||
Cow::Borrowed(b) => Bytes::from_static(b),
|
||||
Cow::Owned(b) => Bytes::from(b),
|
||||
};
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||
match self {
|
||||
Cow::Borrowed(b) => Ok(Bytes::from_static(b)),
|
||||
Cow::Owned(b) => Ok(Bytes::from(b)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for &'static str {
|
||||
type Error = Infallible;
|
||||
|
||||
|
@ -379,6 +412,39 @@ mod foreign_impls {
|
|||
}
|
||||
}
|
||||
|
||||
impl MessageBody for Cow<'static, str> {
|
||||
type Error = Infallible;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
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<Bytes, Self> {
|
||||
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;
|
||||
|
||||
|
|
|
@ -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`].
|
||||
|
|
|
@ -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<F, B>(self, service: F) -> H1Service<T, S, B, X, U>
|
||||
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<F, B>(self, service: F) -> crate::h2::H2Service<T, S, B>
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -64,7 +64,7 @@ fn drop_payload_service(
|
|||
fn echo_payload_service() -> impl Service<Request, Response = Response<Bytes>, Error = Error> {
|
||||
fn_service(|mut req: Request| {
|
||||
Box::pin(async move {
|
||||
use futures_util::stream::StreamExt as _;
|
||||
use futures_util::StreamExt as _;
|
||||
|
||||
let mut pl = req.take_payload();
|
||||
let mut body = BytesMut::new();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<BoxBody> = err.into();
|
||||
handle_response(res, tx, config).await
|
||||
handle_response(res, tx, config, head_req).await
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -206,6 +207,7 @@ async fn handle_response<B>(
|
|||
res: Response<B>,
|
||||
mut tx: SendResponse<Bytes>,
|
||||
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(());
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
//! 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.
|
||||
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.
|
||||
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 `<iframe>` elements in the document.
|
||||
pub const PERMISSIONS_POLICY: HeaderName = HeaderName::from_static("permissions-policy");
|
||||
|
||||
/// Request header (de-facto standard) for identifying the originating IP address of a client
|
||||
/// connecting to a web server through a proxy server.
|
||||
pub const X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for");
|
||||
|
||||
/// Request header (de-facto standard) for identifying the original host requested by the client in
|
||||
/// the `Host` HTTP request header.
|
||||
pub const X_FORWARDED_HOST: HeaderName = HeaderName::from_static("x-forwarded-host");
|
||||
|
||||
/// Request header (de-facto standard) for identifying the protocol that a client used to connect to
|
||||
/// your proxy or load balancer.
|
||||
pub const X_FORWARDED_PROTO: HeaderName = HeaderName::from_static("x-forwarded-proto");
|
|
@ -150,9 +150,7 @@ impl HeaderMap {
|
|||
/// assert_eq!(map.len(), 3);
|
||||
/// ```
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner
|
||||
.iter()
|
||||
.fold(0, |acc, (_, values)| acc + values.len())
|
||||
self.inner.values().map(|vals| vals.len()).sum()
|
||||
}
|
||||
|
||||
/// Returns the number of _keys_ stored in the map.
|
||||
|
@ -552,6 +550,39 @@ impl HeaderMap {
|
|||
Keys(self.inner.keys())
|
||||
}
|
||||
|
||||
/// Retains only the headers specified by the predicate.
|
||||
///
|
||||
/// In other words, removes all headers `(name, val)` for which `retain_fn(&name, &mut val)`
|
||||
/// returns false.
|
||||
///
|
||||
/// The order in which headers are visited should be considered arbitrary.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
///
|
||||
/// map.append(header::HOST, HeaderValue::from_static("duck.com"));
|
||||
/// map.append(header::SET_COOKIE, HeaderValue::from_static("one=1"));
|
||||
/// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2"));
|
||||
///
|
||||
/// map.retain(|name, val| val.as_bytes().starts_with(b"one"));
|
||||
///
|
||||
/// assert_eq!(map.len(), 1);
|
||||
/// assert!(map.contains_key(&header::SET_COOKIE));
|
||||
/// ```
|
||||
pub fn retain<F>(&mut self, mut retain_fn: F)
|
||||
where
|
||||
F: FnMut(&HeaderName, &mut HeaderValue) -> bool,
|
||||
{
|
||||
self.inner.retain(|name, vals| {
|
||||
vals.inner.retain(|val| retain_fn(name, val));
|
||||
|
||||
// invariant: make sure newly empty value lists are removed
|
||||
!vals.is_empty()
|
||||
})
|
||||
}
|
||||
|
||||
/// Clears the map, returning all name-value sets as an iterator.
|
||||
///
|
||||
/// Header names will only be yielded for the first value in each set. All items that are
|
||||
|
@ -943,6 +974,55 @@ mod tests {
|
|||
assert!(map.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retain() {
|
||||
let mut map = HeaderMap::new();
|
||||
|
||||
map.append(header::LOCATION, HeaderValue::from_static("/test"));
|
||||
map.append(header::HOST, HeaderValue::from_static("duck.com"));
|
||||
map.append(header::COOKIE, HeaderValue::from_static("one=1"));
|
||||
map.append(header::COOKIE, HeaderValue::from_static("two=2"));
|
||||
|
||||
assert_eq!(map.len(), 4);
|
||||
|
||||
// by value
|
||||
map.retain(|_, val| !val.as_bytes().contains(&b'/'));
|
||||
assert_eq!(map.len(), 3);
|
||||
|
||||
// by name
|
||||
map.retain(|name, _| name.as_str() != "cookie");
|
||||
assert_eq!(map.len(), 1);
|
||||
|
||||
// keep but mutate value
|
||||
map.retain(|_, val| {
|
||||
*val = HeaderValue::from_static("replaced");
|
||||
true
|
||||
});
|
||||
assert_eq!(map.len(), 1);
|
||||
assert_eq!(map.get("host").unwrap(), "replaced");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retain_removes_empty_value_lists() {
|
||||
let mut map = HeaderMap::with_capacity(3);
|
||||
|
||||
map.append(header::HOST, HeaderValue::from_static("duck.com"));
|
||||
map.append(header::HOST, HeaderValue::from_static("duck.com"));
|
||||
|
||||
assert_eq!(map.len(), 2);
|
||||
assert_eq!(map.len_keys(), 1);
|
||||
assert_eq!(map.inner.len(), 1);
|
||||
assert_eq!(map.capacity(), 3);
|
||||
|
||||
// remove everything
|
||||
map.retain(|_n, _v| false);
|
||||
|
||||
assert_eq!(map.len(), 0);
|
||||
assert_eq!(map.len_keys(), 0);
|
||||
assert_eq!(map.inner.len(), 0);
|
||||
assert_eq!(map.capacity(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entries_into_iter() {
|
||||
let mut map = HeaderMap::new();
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
//! Pre-defined `HeaderName`s, traits for parsing and conversion, and other header utility methods.
|
||||
|
||||
// declaring new header consts will yield this error
|
||||
#![allow(clippy::declare_interior_mutable_const)]
|
||||
|
||||
use percent_encoding::{AsciiSet, CONTROLS};
|
||||
|
||||
// re-export from http except header map related items
|
||||
pub use http::header::{
|
||||
pub use ::http::header::{
|
||||
HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, ToStrError,
|
||||
};
|
||||
|
||||
// re-export const header names
|
||||
pub use http::header::{
|
||||
// re-export const header names, list is explicit so that any updates to `common` module do not
|
||||
// conflict with this set
|
||||
pub use ::http::header::{
|
||||
ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES,
|
||||
ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS,
|
||||
ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS,
|
||||
|
@ -30,22 +34,30 @@ pub use http::header::{
|
|||
use crate::{error::ParseError, HttpMessage};
|
||||
|
||||
mod as_name;
|
||||
mod common;
|
||||
mod into_pair;
|
||||
mod into_value;
|
||||
pub mod map;
|
||||
mod shared;
|
||||
mod utils;
|
||||
|
||||
pub use self::as_name::AsHeaderName;
|
||||
pub use self::into_pair::TryIntoHeaderPair;
|
||||
pub use self::into_value::TryIntoHeaderValue;
|
||||
pub use self::map::HeaderMap;
|
||||
pub use self::shared::{
|
||||
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, LanguageTag,
|
||||
Quality, QualityItem,
|
||||
pub use self::{
|
||||
as_name::AsHeaderName,
|
||||
into_pair::TryIntoHeaderPair,
|
||||
into_value::TryIntoHeaderValue,
|
||||
map::HeaderMap,
|
||||
shared::{
|
||||
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate,
|
||||
LanguageTag, Quality, QualityItem,
|
||||
},
|
||||
utils::{fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode},
|
||||
};
|
||||
pub use self::utils::{
|
||||
fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode,
|
||||
|
||||
// re-export list is explicit so that any updates to `http` do not conflict with this set
|
||||
pub use self::common::{
|
||||
CACHE_STATUS, CDN_CACHE_CONTROL, CROSS_ORIGIN_EMBEDDER_POLICY, CROSS_ORIGIN_OPENER_POLICY,
|
||||
CROSS_ORIGIN_RESOURCE_POLICY, PERMISSIONS_POLICY, X_FORWARDED_FOR, X_FORWARDED_HOST,
|
||||
X_FORWARDED_PROTO,
|
||||
};
|
||||
|
||||
/// An interface for types that already represent a valid header.
|
||||
|
|
|
@ -21,7 +21,8 @@
|
|||
#![allow(
|
||||
clippy::type_complexity,
|
||||
clippy::too_many_arguments,
|
||||
clippy::borrow_interior_mutable_const
|
||||
clippy::borrow_interior_mutable_const,
|
||||
clippy::uninlined_format_args
|
||||
)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
|
|
@ -24,7 +24,39 @@ use crate::{
|
|||
h1, ConnectCallback, OnConnectData, Protocol, Request, Response, ServiceConfig,
|
||||
};
|
||||
|
||||
/// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol.
|
||||
/// A [`ServiceFactory`] for HTTP/1.1 and HTTP/2 connections.
|
||||
///
|
||||
/// Use [`build`](Self::build) to begin constructing service. Also see [`HttpServiceBuilder`].
|
||||
///
|
||||
/// # Automatic HTTP Version Selection
|
||||
/// There are two ways to select the HTTP version of an incoming connection:
|
||||
/// - One is to rely on the ALPN information that is provided when using a TLS (HTTPS); both
|
||||
/// versions are supported automatically when using either of the `.rustls()` or `.openssl()`
|
||||
/// finalizing methods.
|
||||
/// - The other is to read the first few bytes of the TCP stream. This is the only viable approach
|
||||
/// for supporting H2C, which allows the HTTP/2 protocol to work over plaintext connections. Use
|
||||
/// the `.tcp_auto_h2c()` finalizing method to enable this behavior.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::convert::Infallible;
|
||||
/// use actix_http::{HttpService, Request, Response, StatusCode};
|
||||
///
|
||||
/// // this service would constructed in an actix_server::Server
|
||||
///
|
||||
/// # actix_rt::System::new().block_on(async {
|
||||
/// HttpService::build()
|
||||
/// // the builder finalizing method, other finalizers would not return an `HttpService`
|
||||
/// .finish(|_req: Request| async move {
|
||||
/// Ok::<_, Infallible>(
|
||||
/// Response::build(StatusCode::OK).body("Hello!")
|
||||
/// )
|
||||
/// })
|
||||
/// // the service finalizing method method
|
||||
/// // you can use `.tcp_auto_h2c()`, `.rustls()`, or `.openssl()` instead of `.tcp()`
|
||||
/// .tcp();
|
||||
/// # })
|
||||
/// ```
|
||||
pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler> {
|
||||
srv: S,
|
||||
cfg: ServiceConfig,
|
||||
|
@ -163,7 +195,9 @@ where
|
|||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create simple tcp stream service
|
||||
/// Creates TCP stream service from HTTP service.
|
||||
///
|
||||
/// The resulting service only supports HTTP/1.x.
|
||||
pub fn tcp(
|
||||
self,
|
||||
) -> impl ServiceFactory<
|
||||
|
@ -179,6 +213,42 @@ where
|
|||
})
|
||||
.and_then(self)
|
||||
}
|
||||
|
||||
/// Creates TCP stream service from HTTP service that automatically selects HTTP/1.x or HTTP/2
|
||||
/// on plaintext connections.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn tcp_auto_h2c(
|
||||
self,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = DispatchError,
|
||||
InitError = (),
|
||||
> {
|
||||
fn_service(move |io: TcpStream| async move {
|
||||
// subset of HTTP/2 preface defined by RFC 9113 §3.4
|
||||
// this subset was chosen to maximize likelihood that peeking only once will allow us to
|
||||
// reliably determine version or else it should fallback to h1 and fail quickly if data
|
||||
// on the wire is junk
|
||||
const H2_PREFACE: &[u8] = b"PRI * HTTP/2";
|
||||
|
||||
let mut buf = [0; 12];
|
||||
|
||||
io.peek(&mut buf).await?;
|
||||
|
||||
let proto = if buf == H2_PREFACE {
|
||||
Protocol::Http2
|
||||
} else {
|
||||
Protocol::Http1
|
||||
};
|
||||
|
||||
let peer_addr = io.peer_addr().ok();
|
||||
Ok((io, proto, peer_addr))
|
||||
})
|
||||
.and_then(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration options used when accepting TLS connection.
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::{
|
|||
fmt,
|
||||
};
|
||||
|
||||
use base64::prelude::*;
|
||||
use tracing::error;
|
||||
|
||||
/// Operation codes defined in [RFC 6455 §11.8].
|
||||
|
@ -244,7 +245,7 @@ pub fn hash_key(key: &[u8]) -> [u8; 28] {
|
|||
};
|
||||
|
||||
let mut hash_b64 = [0; 28];
|
||||
let n = base64::encode_config_slice(hash, base64::STANDARD, &mut hash_b64);
|
||||
let n = BASE64_STANDARD.encode_slice(hash, &mut hash_b64).unwrap();
|
||||
assert_eq!(n, 28);
|
||||
|
||||
hash_b64
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#![cfg(feature = "openssl")]
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
extern crate tls_openssl as openssl;
|
||||
|
||||
|
@ -16,7 +17,7 @@ use actix_utils::future::{err, ok, ready};
|
|||
use bytes::{Bytes, BytesMut};
|
||||
use derive_more::{Display, Error};
|
||||
use futures_core::Stream;
|
||||
use futures_util::stream::{once, StreamExt as _};
|
||||
use futures_util::{stream::once, StreamExt as _};
|
||||
use openssl::{
|
||||
pkey::PKey,
|
||||
ssl::{SslAcceptor, SslMethod},
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#![cfg(feature = "rustls")]
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
extern crate tls_rustls as rustls;
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
io::{Read, Write},
|
||||
|
@ -7,18 +9,15 @@ use std::{
|
|||
|
||||
use actix_http::{
|
||||
body::{self, BodyStream, BoxBody, SizedStream},
|
||||
header, Error, HttpService, KeepAlive, Request, Response, StatusCode,
|
||||
header, Error, HttpService, KeepAlive, Request, Response, StatusCode, Version,
|
||||
};
|
||||
use actix_http_test::test_server;
|
||||
use actix_rt::time::sleep;
|
||||
use actix_rt::{net::TcpStream, time::sleep};
|
||||
use actix_service::fn_service;
|
||||
use actix_utils::future::{err, ok, ready};
|
||||
use bytes::Bytes;
|
||||
use derive_more::{Display, Error};
|
||||
use futures_util::{
|
||||
stream::{once, StreamExt as _},
|
||||
FutureExt as _,
|
||||
};
|
||||
use futures_util::{stream::once, FutureExt as _, StreamExt as _};
|
||||
use regex::Regex;
|
||||
|
||||
#[actix_rt::test]
|
||||
|
@ -858,3 +857,44 @@ async fn not_modified_spec_h1() {
|
|||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn h2c_auto() {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.keep_alive(KeepAlive::Disabled)
|
||||
.finish(|req: Request| {
|
||||
let body = match req.version() {
|
||||
Version::HTTP_11 => "h1",
|
||||
Version::HTTP_2 => "h2",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
ok::<_, Infallible>(Response::ok().set_body(body))
|
||||
})
|
||||
.tcp_auto_h2c()
|
||||
})
|
||||
.await;
|
||||
|
||||
let req = srv.get("/");
|
||||
assert_eq!(req.get_version(), &Version::HTTP_11);
|
||||
let mut res = req.send().await.unwrap();
|
||||
assert!(res.status().is_success());
|
||||
assert_eq!(res.body().await.unwrap(), &b"h1"[..]);
|
||||
|
||||
// awc doesn't support forcing the version to http/2 so use h2 manually
|
||||
|
||||
let tcp = TcpStream::connect(srv.addr()).await.unwrap();
|
||||
let (h2, connection) = h2::client::handshake(tcp).await.unwrap();
|
||||
tokio::spawn(async move { connection.await.unwrap() });
|
||||
let mut h2 = h2.ready().await.unwrap();
|
||||
|
||||
let request = ::http::Request::new(());
|
||||
let (response, _) = h2.send_request(request, true).unwrap();
|
||||
let (head, mut body) = response.await.unwrap().into_parts();
|
||||
let body = body.data().await.unwrap().unwrap();
|
||||
|
||||
assert!(head.status.is_success());
|
||||
assert_eq!(body, &b"h2"[..]);
|
||||
|
||||
srv.stop().await;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use std::{
|
||||
cell::Cell,
|
||||
convert::Infallible,
|
||||
|
|
|
@ -19,7 +19,7 @@ actix-web = { version = "4", default-features = false }
|
|||
|
||||
bytes = "1"
|
||||
derive_more = "0.99.5"
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
httparse = "1.3"
|
||||
local-waker = "0.1"
|
||||
log = "0.4"
|
||||
|
@ -29,6 +29,6 @@ memchr = "2.5"
|
|||
[dev-dependencies]
|
||||
actix-rt = "2.2"
|
||||
actix-http = "3"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
tokio = { version = "1.8.4", features = ["sync"] }
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
tokio = { version = "1.18.4", features = ["sync"] }
|
||||
tokio-stream = "0.1"
|
||||
|
|
|
@ -14,7 +14,7 @@ use crate::server::Multipart;
|
|||
/// ```
|
||||
/// use actix_web::{web, HttpResponse, Error};
|
||||
/// use actix_multipart::Multipart;
|
||||
/// use futures_util::stream::StreamExt as _;
|
||||
/// use futures_util::StreamExt as _;
|
||||
///
|
||||
/// async fn index(mut payload: Multipart) -> Result<HttpResponse, Error> {
|
||||
/// // iterate over multipart stream
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![allow(clippy::borrow_interior_mutable_const)]
|
||||
#![allow(clippy::borrow_interior_mutable_const, clippy::uninlined_format_args)]
|
||||
|
||||
mod error;
|
||||
mod extractor;
|
||||
|
|
|
@ -868,7 +868,7 @@ mod tests {
|
|||
use actix_web::test::TestRequest;
|
||||
use actix_web::FromRequest;
|
||||
use bytes::Bytes;
|
||||
use futures_util::{future::lazy, StreamExt};
|
||||
use futures_util::{future::lazy, StreamExt as _};
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
|
|
@ -27,7 +27,7 @@ serde = "1"
|
|||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
criterion = { version = "0.4", features = ["html_reports"] }
|
||||
http = "0.2.5"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
percent-encoding = "2.1"
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
|
|
@ -2,6 +2,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")]
|
||||
|
||||
|
|
|
@ -37,12 +37,12 @@ actix-utils = "3"
|
|||
actix-web = { version = "4", default-features = false, features = ["cookies"] }
|
||||
awc = { version = "3", default-features = false, features = ["cookies"] }
|
||||
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
|
||||
futures-util = { version = "0.3.7", default-features = false, features = [] }
|
||||
futures-core = { version = "0.3.17", default-features = false, features = ["std"] }
|
||||
futures-util = { version = "0.3.17", default-features = false, features = [] }
|
||||
log = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_urlencoded = "0.7"
|
||||
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
|
||||
tls-rustls = { package = "rustls", version = "0.20.0", optional = true }
|
||||
tokio = { version = "1.8.4", features = ["sync"] }
|
||||
tokio = { version = "1.18.4", features = ["sync"] }
|
||||
|
|
|
@ -321,6 +321,7 @@ where
|
|||
// all thread managed resources should be dropped at this point
|
||||
});
|
||||
|
||||
#[allow(clippy::let_underscore_future)]
|
||||
let _ = thread_stop_tx.send(());
|
||||
});
|
||||
|
||||
|
@ -567,6 +568,7 @@ impl Drop for TestServer {
|
|||
// without needing to await anything
|
||||
|
||||
// signal server to stop
|
||||
#[allow(clippy::let_underscore_future)]
|
||||
let _ = self.server.stop(true);
|
||||
|
||||
// signal system to stop
|
||||
|
|
|
@ -21,9 +21,9 @@ actix-web = { version = "4", default-features = false }
|
|||
|
||||
bytes = "1"
|
||||
bytestring = "1"
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
futures-core = { version = "0.3.17", default-features = false }
|
||||
pin-project-lite = "0.2"
|
||||
tokio = { version = "1.13.1", features = ["sync"] }
|
||||
tokio = { version = "1.18.4", features = ["sync"] }
|
||||
tokio-util = { version = "0.7", features = ["codec"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -35,4 +35,4 @@ actix-web = { version = "4", features = ["macros"] }
|
|||
mime = "0.3"
|
||||
|
||||
env_logger = "0.9"
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
mod context;
|
||||
pub mod ws;
|
||||
|
|
|
@ -3,7 +3,7 @@ use actix_http::ws::Codec;
|
|||
use actix_web::{web, App, HttpRequest};
|
||||
use actix_web_actors::ws;
|
||||
use bytes::Bytes;
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use futures_util::{SinkExt as _, StreamExt as _};
|
||||
|
||||
struct Ws;
|
||||
|
||||
|
|
|
@ -27,6 +27,6 @@ actix-test = "0.1"
|
|||
actix-utils = "3"
|
||||
actix-web = "4"
|
||||
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
trybuild = "1"
|
||||
rustversion = "1"
|
||||
|
|
|
@ -155,7 +155,7 @@ impl Args {
|
|||
if !methods.insert(method) {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&nv.lit,
|
||||
&format!(
|
||||
format!(
|
||||
"HTTP method defined more than once: `{}`",
|
||||
lit.value()
|
||||
),
|
||||
|
|
|
@ -7,13 +7,18 @@
|
|||
- Add `Logger::custom_response_replace()`. [#2631]
|
||||
- Add rudimentary redirection service at `web::redirect()` / `web::Redirect`. [#1961]
|
||||
- Add `guard::Acceptable` for matching against `Accept` header mime types. [#2265]
|
||||
- Add fallible versions of test helpers: `try_call_service`, `try_call_and_read_body_json`, `try_read_body`, and `try_read_body_json`. [#2961]
|
||||
|
||||
### Fixed
|
||||
- Add `Allow` header to `Resource`'s default responses when no routes are matched. [#2949]
|
||||
|
||||
[#1961]: https://github.com/actix/actix-web/pull/1961
|
||||
[#2265]: https://github.com/actix/actix-web/pull/2265
|
||||
[#2631]: https://github.com/actix/actix-web/pull/2631
|
||||
[#2784]: https://github.com/actix/actix-web/pull/2784
|
||||
[#2867]: https://github.com/actix/actix-web/pull/2867
|
||||
|
||||
[#2949]: https://github.com/actix/actix-web/pull/2949
|
||||
[#2961]: https://github.com/actix/actix-web/pull/2961
|
||||
|
||||
## 4.2.1 - 2022-09-12
|
||||
### Fixed
|
||||
|
|
|
@ -38,10 +38,7 @@ compress-gzip = ["actix-http/compress-gzip", "__compress"]
|
|||
compress-zstd = ["actix-http/compress-zstd", "__compress"]
|
||||
|
||||
# Routing and runtime proc macros
|
||||
macros = [
|
||||
"actix-macros",
|
||||
"actix-web-codegen",
|
||||
]
|
||||
macros = ["actix-macros", "actix-web-codegen"]
|
||||
|
||||
# Cookies support
|
||||
cookies = ["cookie"]
|
||||
|
@ -82,8 +79,8 @@ cfg-if = "1"
|
|||
cookie = { version = "0.16", features = ["percent-encode"], optional = true }
|
||||
derive_more = "0.99.8"
|
||||
encoding_rs = "0.8"
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
futures-core = { version = "0.3.17", default-features = false }
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
http = "0.2.8"
|
||||
itoa = "1"
|
||||
language-tags = "0.3"
|
||||
|
@ -107,10 +104,10 @@ awc = { version = "3", features = ["openssl"] }
|
|||
|
||||
brotli = "3.3.3"
|
||||
const-str = "0.4"
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
criterion = { version = "0.4", features = ["html_reports"] }
|
||||
env_logger = "0.9"
|
||||
flate2 = "1.0.13"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["std"] }
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
|
||||
rand = "0.8"
|
||||
rcgen = "0.9"
|
||||
rustls-pemfile = "1"
|
||||
|
@ -118,8 +115,8 @@ serde = { version = "1.0", features = ["derive"] }
|
|||
static_assertions = "1"
|
||||
tls-openssl = { package = "openssl", version = "0.10.9" }
|
||||
tls-rustls = { package = "rustls", version = "0.20.0" }
|
||||
tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] }
|
||||
zstd = "0.11"
|
||||
tokio = { version = "1.18.4", features = ["rt-multi-thread", "macros"] }
|
||||
zstd = "0.12"
|
||||
|
||||
[[test]]
|
||||
name = "test_server"
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use actix_web::{get, middleware, web, App, HttpRequest, HttpResponse, HttpServer};
|
||||
|
||||
#[get("/resource1/{name}/index.html")]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
//! For an example of extracting a client TLS certificate, see:
|
||||
//! <https://github.com/actix/examples/tree/master/https-tls/rustls-client-cert>
|
||||
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use std::{any::Any, io, net::SocketAddr};
|
||||
|
||||
use actix_web::{
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -5,7 +5,7 @@ use actix_service::{
|
|||
apply, apply_fn_factory, boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt,
|
||||
Transform,
|
||||
};
|
||||
use futures_util::future::FutureExt as _;
|
||||
use futures_util::FutureExt as _;
|
||||
|
||||
use crate::{
|
||||
app_service::{AppEntry, AppInit, AppRoutingFactory},
|
||||
|
@ -712,6 +712,7 @@ mod tests {
|
|||
.route("/", web::to(|| async { "hello" }))
|
||||
}
|
||||
|
||||
#[allow(clippy::let_underscore_future)]
|
||||
let _ = init_service(my_app());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -286,11 +286,25 @@ pub fn Method(method: HttpMethod) -> impl Guard {
|
|||
MethodGuard(method)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct RegisteredMethods(pub(crate) Vec<HttpMethod>);
|
||||
|
||||
/// HTTP method guard.
|
||||
struct MethodGuard(HttpMethod);
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct MethodGuard(HttpMethod);
|
||||
|
||||
impl Guard for MethodGuard {
|
||||
fn check(&self, ctx: &GuardContext<'_>) -> bool {
|
||||
let registered = ctx.req_data_mut().remove::<RegisteredMethods>();
|
||||
|
||||
if let Some(mut methods) = registered {
|
||||
methods.0.push(self.0.clone());
|
||||
ctx.req_data_mut().insert(methods);
|
||||
} else {
|
||||
ctx.req_data_mut()
|
||||
.insert(RegisteredMethods(vec![self.0.clone()]));
|
||||
}
|
||||
|
||||
ctx.head().method == self.0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))]
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
|||
};
|
||||
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use futures_util::future::FutureExt as _;
|
||||
use futures_util::FutureExt as _;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::{
|
||||
|
|
|
@ -351,7 +351,7 @@ mod tests {
|
|||
use actix_service::IntoService;
|
||||
use actix_utils::future::ok;
|
||||
use bytes::Bytes;
|
||||
use futures_util::future::FutureExt as _;
|
||||
use futures_util::FutureExt as _;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
|
|
|
@ -13,8 +13,9 @@ use crate::{
|
|||
body::MessageBody,
|
||||
data::Data,
|
||||
dev::{ensure_leading_slash, AppService, ResourceDef},
|
||||
guard::Guard,
|
||||
guard::{self, Guard},
|
||||
handler::Handler,
|
||||
http::header,
|
||||
route::{Route, RouteService},
|
||||
service::{
|
||||
BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, ServiceRequest,
|
||||
|
@ -40,8 +41,11 @@ use crate::{
|
|||
/// .route(web::get().to(|| HttpResponse::Ok())));
|
||||
/// ```
|
||||
///
|
||||
/// If no matching route could be found, *405* response code get returned. Default behavior could be
|
||||
/// overridden with `default_resource()` method.
|
||||
/// If no matching route is found, [a 405 response is returned with an appropriate Allow header][RFC
|
||||
/// 9110 §15.5.6]. This default behavior can be overridden using
|
||||
/// [`default_service()`](Self::default_service).
|
||||
///
|
||||
/// [RFC 9110 §15.5.6]: https://www.rfc-editor.org/rfc/rfc9110.html#section-15.5.6
|
||||
pub struct Resource<T = ResourceEndpoint> {
|
||||
endpoint: T,
|
||||
rdef: Patterns,
|
||||
|
@ -66,7 +70,19 @@ impl Resource {
|
|||
guards: Vec::new(),
|
||||
app_data: None,
|
||||
default: boxed::factory(fn_service(|req: ServiceRequest| async {
|
||||
Ok(req.into_response(HttpResponse::MethodNotAllowed()))
|
||||
use crate::HttpMessage as _;
|
||||
|
||||
let allowed = req.extensions().get::<guard::RegisteredMethods>().cloned();
|
||||
|
||||
if let Some(methods) = allowed {
|
||||
Ok(req.into_response(
|
||||
HttpResponse::MethodNotAllowed()
|
||||
.insert_header(header::Allow(methods.0))
|
||||
.finish(),
|
||||
))
|
||||
} else {
|
||||
Ok(req.into_response(HttpResponse::MethodNotAllowed()))
|
||||
}
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
@ -309,13 +325,28 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Default service to be used if no matching route could be found.
|
||||
/// Sets the default service to be used if no matching route is found.
|
||||
///
|
||||
/// You can use a [`Route`] as default service.
|
||||
/// Unlike [`Scope`]s, a `Resource` does _not_ inherit its parent's default service. You can
|
||||
/// use a [`Route`] as default service.
|
||||
///
|
||||
/// If a default service is not registered, an empty `405 Method Not Allowed` response will be
|
||||
/// sent to the client instead. Unlike [`Scope`](crate::Scope)s, a [`Resource`] does **not**
|
||||
/// inherit its parent's default service.
|
||||
/// If a custom default service is not registered, an empty `405 Method Not Allowed` response
|
||||
/// with an appropriate Allow header will be sent instead.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::{App, HttpResponse, web};
|
||||
///
|
||||
/// let resource = web::resource("/test")
|
||||
/// .route(web::get().to(HttpResponse::Ok))
|
||||
/// .default_service(web::to(|| {
|
||||
/// HttpResponse::BadRequest()
|
||||
/// }));
|
||||
///
|
||||
/// App::new().service(resource);
|
||||
/// ```
|
||||
///
|
||||
/// [`Scope`]: crate::Scope
|
||||
pub fn default_service<F, U>(mut self, f: F) -> Self
|
||||
where
|
||||
F: IntoServiceFactory<U, ServiceRequest>,
|
||||
|
@ -606,7 +637,11 @@ mod tests {
|
|||
async fn test_default_resource() {
|
||||
let srv = init_service(
|
||||
App::new()
|
||||
.service(web::resource("/test").route(web::get().to(HttpResponse::Ok)))
|
||||
.service(
|
||||
web::resource("/test")
|
||||
.route(web::get().to(HttpResponse::Ok))
|
||||
.route(web::delete().to(HttpResponse::Ok)),
|
||||
)
|
||||
.default_service(|r: ServiceRequest| {
|
||||
ok(r.into_response(HttpResponse::BadRequest()))
|
||||
}),
|
||||
|
@ -621,6 +656,10 @@ mod tests {
|
|||
.to_request();
|
||||
let resp = call_service(&srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::ALLOW).unwrap().as_bytes(),
|
||||
b"GET, DELETE"
|
||||
);
|
||||
|
||||
let srv = init_service(
|
||||
App::new().service(
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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<S, R, B, E>(app: &S, req: R) -> Result<S::Response, E>
|
||||
where
|
||||
S: Service<R, Response = ServiceResponse<B>, 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<B>(res: ServiceResponse<B>) -> Bytes
|
|||
where
|
||||
B: MessageBody,
|
||||
{
|
||||
let body = res.into_body();
|
||||
body::to_bytes(body)
|
||||
try_read_body(res)
|
||||
.await
|
||||
.map_err(Into::<Box<dyn StdError>>::into)
|
||||
.expect("error reading test response body")
|
||||
}
|
||||
|
||||
/// Fallible version of [`read_body`] that allows testing MessageBody reading errors.
|
||||
pub async fn try_read_body<B>(
|
||||
res: ServiceResponse<B>,
|
||||
) -> Result<Bytes, <B as MessageBody>::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::<T>(),
|
||||
err,
|
||||
body,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Fallible version of [`read_body_json`] that allows testing response deserialzation errors.
|
||||
pub async fn try_read_body_json<T, B>(res: ServiceResponse<B>) -> Result<T, Box<dyn StdError>>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let body = try_read_body(res)
|
||||
.await
|
||||
.map_err(Into::<Box<dyn StdError>>::into)?;
|
||||
serde_json::from_slice(&body).map_err(Into::<Box<dyn StdError>>::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<S, B, T>(
|
||||
app: &S,
|
||||
req: Request,
|
||||
) -> Result<T, Box<dyn StdError>>
|
||||
where
|
||||
S: Service<Request, Response = ServiceResponse<B>, Error = Error>,
|
||||
B: MessageBody,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let res = try_call_service(app, req)
|
||||
.await
|
||||
.map_err(Into::<Box<dyn StdError>>::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<Person>| 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<Person, Box<dyn StdError>> =
|
||||
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<Person>| 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<Person, Box<dyn StdError>> = 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(
|
||||
|
|
|
@ -27,7 +27,7 @@ use crate::{
|
|||
/// # Examples
|
||||
/// ```
|
||||
/// use std::future::Future;
|
||||
/// use futures_util::stream::StreamExt as _;
|
||||
/// use futures_util::StreamExt as _;
|
||||
/// use actix_web::{post, web};
|
||||
///
|
||||
/// // `body: web::Payload` parameter extracts raw payload stream from request
|
||||
|
|
|
@ -177,7 +177,7 @@ where
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use futures_util::stream::StreamExt as _;
|
||||
use futures_util::StreamExt as _;
|
||||
|
||||
use super::*;
|
||||
use crate::test::TestRequest;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
extern crate tls_openssl as openssl;
|
||||
|
||||
|
|
|
@ -63,12 +63,12 @@ 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"
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] }
|
||||
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["alloc", "sink"] }
|
||||
h2 = "0.3.9"
|
||||
http = "0.2.5"
|
||||
itoa = "1"
|
||||
|
@ -80,14 +80,14 @@ rand = "0.8"
|
|||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_urlencoded = "0.7"
|
||||
tokio = { version = "1.8.4", features = ["sync"] }
|
||||
tokio = { version = "1.18.4", features = ["sync"] }
|
||||
|
||||
cookie = { version = "0.16", features = ["percent-encode"], optional = true }
|
||||
|
||||
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
|
||||
tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features = ["dangerous_configuration"] }
|
||||
|
||||
trust-dns-resolver = { version = "0.21", optional = true }
|
||||
trust-dns-resolver = { version = "0.22", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-http = { version = "3", features = ["openssl"] }
|
||||
|
@ -102,12 +102,12 @@ brotli = "3.3.3"
|
|||
const-str = "0.4"
|
||||
env_logger = "0.9"
|
||||
flate2 = "1.0.13"
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
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"] }
|
||||
zstd = "0.11"
|
||||
tokio = { version = "1.18.4", features = ["rt-multi-thread", "macros"] }
|
||||
zstd = "0.12"
|
||||
|
||||
[[example]]
|
||||
name = "client"
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use std::error::Error as StdError;
|
||||
|
||||
#[tokio::main]
|
||||
|
|
|
@ -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)),
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ use actix_rt::time::{sleep, Sleep};
|
|||
use actix_service::Service;
|
||||
use ahash::AHashMap;
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use futures_util::FutureExt;
|
||||
use futures_util::FutureExt as _;
|
||||
use http::uri::Authority;
|
||||
use pin_project_lite::pin_project;
|
||||
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
//! ```no_run
|
||||
//! # #[actix_rt::main]
|
||||
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! use futures_util::{sink::SinkExt as _, stream::StreamExt as _};
|
||||
//! use futures_util::{SinkExt as _, StreamExt as _};
|
||||
//!
|
||||
//! let (_resp, mut connection) = awc::Client::new()
|
||||
//! .ws("ws://echo.websocket.org")
|
||||
|
@ -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")]
|
||||
|
|
|
@ -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("");
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
//!
|
||||
//! ```no_run
|
||||
//! use awc::{Client, ws};
|
||||
//! use futures_util::{sink::SinkExt as _, stream::StreamExt as _};
|
||||
//! use futures_util::{SinkExt as _, StreamExt as _};
|
||||
//!
|
||||
//! #[actix_rt::main]
|
||||
//! async fn main() {
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -139,7 +142,7 @@ async fn timeout_override() {
|
|||
|
||||
#[actix_rt::test]
|
||||
async fn response_timeout() {
|
||||
use futures_util::stream::{once, StreamExt as _};
|
||||
use futures_util::{stream::once, StreamExt as _};
|
||||
|
||||
let srv = actix_test::start(|| {
|
||||
App::new().service(web::resource("/").route(web::to(|| async {
|
||||
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue