mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-23 09:18:20 +02:00
Compare commits
9 Commits
on-connect
...
improve-ty
Author | SHA1 | Date | |
---|---|---|---|
|
30da2e5618 | ||
|
43a0e8102f | ||
|
183fbdfd74 | ||
|
57ee49a618 | ||
|
a75212695a | ||
|
2f9c97461a | ||
|
a0eb0d22be | ||
|
303843dcda | ||
|
75b026b740 |
@@ -5,10 +5,8 @@ lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dcli
|
||||
# lib checking
|
||||
ci-check-min = "hack --workspace check --no-default-features"
|
||||
ci-check-default = "hack --workspace check"
|
||||
ci-check-default-tests = "check --workspace --tests"
|
||||
ci-check-all-feature-powerset="hack --workspace --feature-powerset --skip=__compress,io-uring check"
|
||||
ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --skip=__compress check"
|
||||
|
||||
# testing
|
||||
ci-doctest-default = "test --workspace --doc --no-fail-fast -- --nocapture"
|
||||
ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
|
||||
|
19
CHANGES.md
19
CHANGES.md
@@ -3,34 +3,15 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
### Added
|
||||
* Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480]
|
||||
* `AcceptEncoding` typed header. [#2482]
|
||||
* `Range` typed header. [#2485]
|
||||
* `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468]
|
||||
* `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468]
|
||||
* `HttpServer::on_connect` now receives a `CloneableExtensions` object. [#2327]
|
||||
|
||||
[#2325]: https://github.com/actix/actix-web/pull/2325
|
||||
[#2327]: https://github.com/actix/actix-web/pull/2327
|
||||
|
||||
### Changed
|
||||
* Rename `Accept::{mime_precedence => ranked}`. [#2480]
|
||||
* Rename `Accept::{mime_preference => preference}`. [#2480]
|
||||
* Un-deprecate `App::data_factory`. [#2484]
|
||||
* `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430]
|
||||
* `HttpServer::on_connect` now receives a `CloneableExtensions` object. [#2327]
|
||||
|
||||
### Fixed
|
||||
* Accept wildcard `*` items in `AcceptLanguage`. [#2480]
|
||||
* Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468]
|
||||
* Typed headers containing lists that require one or more items now enforce this minimum. [#2482]
|
||||
|
||||
[#2327]: https://github.com/actix/actix-web/pull/2327
|
||||
[#2430]: https://github.com/actix/actix-web/pull/2430
|
||||
[#2468]: https://github.com/actix/actix-web/pull/2468
|
||||
[#2480]: https://github.com/actix/actix-web/pull/2480
|
||||
[#2482]: https://github.com/actix/actix-web/pull/2482
|
||||
[#2484]: https://github.com/actix/actix-web/pull/2484
|
||||
[#2485]: https://github.com/actix/actix-web/pull/2485
|
||||
|
||||
|
||||
## 4.0.0-beta.13 - 2021-11-30
|
||||
|
@@ -72,7 +72,7 @@ experimental-io-uring = ["actix-server/io-uring"]
|
||||
actix-codec = "0.4.1"
|
||||
actix-macros = "0.2.3"
|
||||
actix-rt = "2.3"
|
||||
actix-server = "2.0.0-rc.1"
|
||||
actix-server = "2.0.0-beta.9"
|
||||
actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true }
|
||||
|
@@ -6,7 +6,8 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_web::{error::Error, web::Bytes};
|
||||
use actix_web::error::Error;
|
||||
use bytes::Bytes;
|
||||
use futures_core::{ready, Stream};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
|
@@ -10,19 +10,18 @@ use std::{
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use actix_http::body::AnyBody;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_web::{
|
||||
body::{self, BoxBody, SizedStream},
|
||||
dev::{
|
||||
AppService, BodyEncoding, HttpServiceFactory, ResourceDef, ServiceRequest,
|
||||
ServiceResponse,
|
||||
ServiceResponse, SizedStream,
|
||||
},
|
||||
http::{
|
||||
header::{
|
||||
self, Charset, ContentDisposition, ContentEncoding, DispositionParam,
|
||||
DispositionType, ExtendedValue,
|
||||
self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue,
|
||||
},
|
||||
StatusCode,
|
||||
ContentEncoding, StatusCode,
|
||||
},
|
||||
Error, HttpMessage, HttpRequest, HttpResponse, Responder,
|
||||
};
|
||||
@@ -114,8 +113,6 @@ pub(crate) use std::fs::File;
|
||||
#[cfg(feature = "experimental-io-uring")]
|
||||
pub(crate) use tokio_uring::fs::File;
|
||||
|
||||
use super::chunked;
|
||||
|
||||
impl NamedFile {
|
||||
/// Creates an instance from a previously opened file.
|
||||
///
|
||||
@@ -397,7 +394,7 @@ impl NamedFile {
|
||||
}
|
||||
|
||||
/// Creates an `HttpResponse` with file as a streaming body.
|
||||
pub fn into_response(self, req: &HttpRequest) -> HttpResponse<BoxBody> {
|
||||
pub fn into_response(self, req: &HttpRequest) -> HttpResponse {
|
||||
if self.status_code != StatusCode::OK {
|
||||
let mut res = HttpResponse::build(self.status_code);
|
||||
|
||||
@@ -419,7 +416,7 @@ impl NamedFile {
|
||||
res.encoding(current_encoding);
|
||||
}
|
||||
|
||||
let reader = chunked::new_chunked_read(self.md.len(), 0, self.file);
|
||||
let reader = super::chunked::new_chunked_read(self.md.len(), 0, self.file);
|
||||
|
||||
return res.streaming(reader);
|
||||
}
|
||||
@@ -530,13 +527,10 @@ impl NamedFile {
|
||||
if precondition_failed {
|
||||
return resp.status(StatusCode::PRECONDITION_FAILED).finish();
|
||||
} else if not_modified {
|
||||
return resp
|
||||
.status(StatusCode::NOT_MODIFIED)
|
||||
.body(body::None::new())
|
||||
.map_into_boxed_body();
|
||||
return resp.status(StatusCode::NOT_MODIFIED).body(AnyBody::None);
|
||||
}
|
||||
|
||||
let reader = chunked::new_chunked_read(length, offset, self.file);
|
||||
let reader = super::chunked::new_chunked_read(length, offset, self.file);
|
||||
|
||||
if offset != 0 || length != self.md.len() {
|
||||
resp.status(StatusCode::PARTIAL_CONTENT);
|
||||
@@ -601,9 +595,7 @@ impl DerefMut for NamedFile {
|
||||
}
|
||||
|
||||
impl Responder for NamedFile {
|
||||
type Body = BoxBody;
|
||||
|
||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||
self.into_response(req)
|
||||
}
|
||||
}
|
||||
|
@@ -34,7 +34,7 @@ actix-codec = "0.4.1"
|
||||
actix-tls = "3.0.0-rc.1"
|
||||
actix-utils = "3.0.0"
|
||||
actix-rt = "2.2"
|
||||
actix-server = "2.0.0-rc.1"
|
||||
actix-server = "2.0.0-beta.9"
|
||||
awc = { version = "3.0.0-beta.11", default-features = false }
|
||||
|
||||
base64 = "0.13"
|
||||
|
@@ -13,8 +13,7 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_rt::{net::TcpStream, System};
|
||||
use actix_server::{Server, ServiceFactory};
|
||||
use awc::{
|
||||
error::PayloadError, http::header::HeaderMap, ws, Client, ClientRequest, ClientResponse,
|
||||
Connector,
|
||||
error::PayloadError, http::HeaderMap, ws, Client, ClientRequest, ClientResponse, Connector,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures_core::stream::Stream;
|
||||
|
@@ -1,47 +1,6 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
### Added
|
||||
* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483]
|
||||
* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483]
|
||||
* `Response::map_into_boxed_body`. [#2468]
|
||||
* `body::EitherBody` enum. [#2468]
|
||||
* `body::None` struct. [#2468]
|
||||
* Impl `MessageBody` for `bytestring::ByteString`. [#2468]
|
||||
* `impl Clone for ws::HandshakeError`. [#2468]
|
||||
* `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920]
|
||||
* `impl Default ` for `ws::Codec`. [#1920]
|
||||
* `header::QualityItem::{max, min}`. [#2486]
|
||||
* `header::Quality::{MAX, MIN}`. [#2486]
|
||||
* `impl Display` for `header::Quality`. [#2486]
|
||||
* `CloneableExtensions` object for use in `on_connect` handlers. [#2327]
|
||||
|
||||
### Changed
|
||||
* Rename `body::BoxBody::{from_body => new}`. [#2468]
|
||||
* Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468]
|
||||
* The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468]
|
||||
* Error types using in service builders now require `Into<Response<BoxBody>>`. [#2468]
|
||||
* `From` implementations on error types now return a `Response<BoxBody>`. [#2468]
|
||||
* `ResponseBuilder::body(B)` now returns `Response<EitherBody<B>>`. [#2468]
|
||||
* `ResponseBuilder::finish()` now returns `Response<EitherBody<()>>`. [#2468]
|
||||
* `on_connect_ext` methods now receive a `CloneableExtensions` object. [#2327]
|
||||
|
||||
### Removed
|
||||
* `ResponseBuilder::streaming`. [#2468]
|
||||
* `impl Future` for `ResponseBuilder`. [#2468]
|
||||
* Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468]
|
||||
* Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468]
|
||||
* `impl Copy` for `ws::Codec`. [#1920]
|
||||
* `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486]
|
||||
* `impl TryFrom<u16>` for `header::Quality`. [#2486]
|
||||
* `http` module. Most everything it contained is exported at the crate root. [#2488]
|
||||
|
||||
[#2327]: https://github.com/actix/actix-web/pull/2327
|
||||
[#2483]: https://github.com/actix/actix-web/pull/2483
|
||||
[#2468]: https://github.com/actix/actix-web/pull/2468
|
||||
[#1920]: https://github.com/actix/actix-web/pull/1920
|
||||
[#2486]: https://github.com/actix/actix-web/pull/2486
|
||||
[#2488]: https://github.com/actix/actix-web/pull/2488
|
||||
|
||||
|
||||
## 3.0.0-beta.14 - 2021-11-30
|
||||
|
@@ -81,7 +81,7 @@ flate2 = { version = "1.0.13", optional = true }
|
||||
zstd = { version = "0.9", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-server = "2.0.0-rc.1"
|
||||
actix-server = "2.0.0-beta.9"
|
||||
actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] }
|
||||
actix-tls = { version = "3.0.0-rc.1", features = ["openssl"] }
|
||||
async-stream = "0.3"
|
||||
@@ -112,7 +112,3 @@ harness = false
|
||||
[[bench]]
|
||||
name = "uninit-headers"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "quality-value"
|
||||
harness = false
|
||||
|
@@ -1,90 +0,0 @@
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
|
||||
const CODES: &[u16] = &[0, 1000, 201, 800, 550];
|
||||
|
||||
fn bench_quality_display_impls(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("quality value display impls");
|
||||
|
||||
for i in CODES.iter() {
|
||||
group.bench_with_input(BenchmarkId::new("New (fast?)", i), i, |b, &i| {
|
||||
b.iter(|| _new::Quality(i).to_string())
|
||||
});
|
||||
|
||||
group.bench_with_input(BenchmarkId::new("Naive", i), i, |b, &i| {
|
||||
b.iter(|| _naive::Quality(i).to_string())
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_quality_display_impls);
|
||||
criterion_main!(benches);
|
||||
|
||||
mod _new {
|
||||
use std::fmt;
|
||||
|
||||
pub struct Quality(pub(crate) u16);
|
||||
|
||||
impl fmt::Display for Quality {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.0 {
|
||||
0 => f.write_str("0"),
|
||||
1000 => f.write_str("1"),
|
||||
|
||||
// some number in the range 1–999
|
||||
x => {
|
||||
f.write_str("0.")?;
|
||||
|
||||
// this implementation avoids string allocation otherwise required
|
||||
// for `.trim_end_matches('0')`
|
||||
|
||||
if x < 10 {
|
||||
f.write_str("00")?;
|
||||
// 0 is handled so it's not possible to have a trailing 0, we can just return
|
||||
itoa::fmt(f, x)
|
||||
} else if x < 100 {
|
||||
f.write_str("0")?;
|
||||
if x % 10 == 0 {
|
||||
// trailing 0, divide by 10 and write
|
||||
itoa::fmt(f, x / 10)
|
||||
} else {
|
||||
itoa::fmt(f, x)
|
||||
}
|
||||
} else {
|
||||
// x is in range 101–999
|
||||
|
||||
if x % 100 == 0 {
|
||||
// two trailing 0s, divide by 100 and write
|
||||
itoa::fmt(f, x / 100)
|
||||
} else if x % 10 == 0 {
|
||||
// one trailing 0, divide by 10 and write
|
||||
itoa::fmt(f, x / 10)
|
||||
} else {
|
||||
itoa::fmt(f, x)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod _naive {
|
||||
use std::fmt;
|
||||
|
||||
pub struct Quality(pub(crate) u16);
|
||||
|
||||
impl fmt::Display for Quality {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.0 {
|
||||
0 => f.write_str("0"),
|
||||
1000 => f.write_str("1"),
|
||||
|
||||
x => {
|
||||
write!(f, "{}", format!("{:03}", x).trim_end_matches('0'))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
use std::io;
|
||||
|
||||
use actix_http::{Error, HttpService, Request, Response, StatusCode};
|
||||
use actix_http::{http::StatusCode, Error, HttpService, Request, Response};
|
||||
use actix_server::Server;
|
||||
use bytes::BytesMut;
|
||||
use futures_util::StreamExt as _;
|
||||
|
@@ -1,14 +1,12 @@
|
||||
use std::io;
|
||||
|
||||
use actix_http::{
|
||||
body::MessageBody, header::HeaderValue, Error, HttpService, Request, Response,
|
||||
StatusCode,
|
||||
};
|
||||
use actix_http::{body::AnyBody, http::HeaderValue, http::StatusCode};
|
||||
use actix_http::{Error, HttpService, Request, Response};
|
||||
use actix_server::Server;
|
||||
use bytes::BytesMut;
|
||||
use futures_util::StreamExt as _;
|
||||
|
||||
async fn handle_request(mut req: Request) -> Result<Response<impl MessageBody>, Error> {
|
||||
async fn handle_request(mut req: Request) -> Result<Response<AnyBody>, Error> {
|
||||
let mut body = BytesMut::new();
|
||||
while let Some(item) = req.payload().next().await {
|
||||
body.extend_from_slice(&item?)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use std::{convert::Infallible, io};
|
||||
|
||||
use actix_http::{HttpService, Response, StatusCode};
|
||||
use actix_http::{http::StatusCode, HttpService, Response};
|
||||
use actix_server::Server;
|
||||
use http::header::HeaderValue;
|
||||
|
||||
|
333
actix-http/src/body/body.rs
Normal file
333
actix-http/src/body/body.rs
Normal file
@@ -0,0 +1,333 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
error::Error as StdError,
|
||||
fmt, mem,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures_core::Stream;
|
||||
use pin_project::pin_project;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
use super::{BodySize, BodyStream, MessageBody, MessageBodyMapErr, SizedStream};
|
||||
|
||||
#[deprecated(since = "4.0.0", note = "Renamed to `AnyBody`.")]
|
||||
pub type Body = AnyBody;
|
||||
|
||||
/// Represents various types of HTTP message body.
|
||||
#[pin_project(project = AnyBodyProj)]
|
||||
#[derive(Clone)]
|
||||
pub enum AnyBody<B = BoxBody> {
|
||||
/// Empty response. `Content-Length` header is not set.
|
||||
None,
|
||||
|
||||
/// Complete, in-memory response body.
|
||||
Bytes(Bytes),
|
||||
|
||||
/// Generic / Other message body.
|
||||
Body(#[pin] B),
|
||||
}
|
||||
|
||||
impl AnyBody {
|
||||
/// Constructs a "body" representing an empty response.
|
||||
pub fn none() -> Self {
|
||||
Self::None
|
||||
}
|
||||
|
||||
/// Constructs a new, 0-length body.
|
||||
pub fn empty() -> Self {
|
||||
Self::Bytes(Bytes::new())
|
||||
}
|
||||
|
||||
/// Create boxed body from generic message body.
|
||||
pub fn new_boxed<B>(body: B) -> Self
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError + 'static>>,
|
||||
{
|
||||
Self::Body(BoxBody::from_body(body))
|
||||
}
|
||||
|
||||
/// Constructs new `AnyBody` instance from a slice of bytes by copying it.
|
||||
///
|
||||
/// If your bytes container is owned, it may be cheaper to use a `From` impl.
|
||||
pub fn copy_from_slice(s: &[u8]) -> Self {
|
||||
Self::Bytes(Bytes::copy_from_slice(s))
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "4.0.0", note = "Renamed to `copy_from_slice`.")]
|
||||
pub fn from_slice(s: &[u8]) -> Self {
|
||||
Self::Bytes(Bytes::copy_from_slice(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> AnyBody<B>
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError + 'static>>,
|
||||
{
|
||||
/// Create body from generic message body.
|
||||
pub fn new(body: B) -> Self {
|
||||
Self::Body(body)
|
||||
}
|
||||
|
||||
pub fn into_boxed(self) -> AnyBody {
|
||||
match self {
|
||||
Self::None => AnyBody::None,
|
||||
Self::Bytes(bytes) => AnyBody::Bytes(bytes),
|
||||
Self::Body(body) => AnyBody::new_boxed(body),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> MessageBody for AnyBody<B>
|
||||
where
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>> + 'static,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
match self {
|
||||
AnyBody::None => BodySize::None,
|
||||
AnyBody::Bytes(ref bin) => BodySize::Sized(bin.len() as u64),
|
||||
AnyBody::Body(ref body) => body.size(),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
match self.project() {
|
||||
AnyBodyProj::None => Poll::Ready(None),
|
||||
AnyBodyProj::Bytes(bin) => {
|
||||
let len = bin.len();
|
||||
if len == 0 {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(mem::take(bin))))
|
||||
}
|
||||
}
|
||||
|
||||
AnyBodyProj::Body(body) => body
|
||||
.poll_next(cx)
|
||||
.map_err(|err| Error::new_body().with_cause(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AnyBody {
|
||||
fn eq(&self, other: &AnyBody) -> bool {
|
||||
match *self {
|
||||
AnyBody::None => matches!(*other, AnyBody::None),
|
||||
AnyBody::Bytes(ref b) => match *other {
|
||||
AnyBody::Bytes(ref b2) => b == b2,
|
||||
_ => false,
|
||||
},
|
||||
AnyBody::Body(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: fmt::Debug> fmt::Debug for AnyBody<S> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
AnyBody::None => write!(f, "AnyBody::None"),
|
||||
AnyBody::Bytes(ref bytes) => write!(f, "AnyBody::Bytes({:?})", bytes),
|
||||
AnyBody::Body(ref stream) => write!(f, "AnyBody::Message({:?})", stream),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<&'static str> for AnyBody<B> {
|
||||
fn from(string: &'static str) -> Self {
|
||||
Self::Bytes(Bytes::from_static(string.as_ref()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<&'static [u8]> for AnyBody<B> {
|
||||
fn from(bytes: &'static [u8]) -> Self {
|
||||
Self::Bytes(Bytes::from_static(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<Vec<u8>> for AnyBody<B> {
|
||||
fn from(vec: Vec<u8>) -> Self {
|
||||
Self::Bytes(Bytes::from(vec))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<String> for AnyBody<B> {
|
||||
fn from(string: String) -> Self {
|
||||
Self::Bytes(Bytes::from(string))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<&'_ String> for AnyBody<B> {
|
||||
fn from(string: &String) -> Self {
|
||||
Self::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&string)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<Cow<'_, str>> for AnyBody<B> {
|
||||
fn from(string: Cow<'_, str>) -> Self {
|
||||
match string {
|
||||
Cow::Owned(s) => Self::from(s),
|
||||
Cow::Borrowed(s) => {
|
||||
Self::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<Bytes> for AnyBody<B> {
|
||||
fn from(bytes: Bytes) -> Self {
|
||||
Self::Bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<BytesMut> for AnyBody<B> {
|
||||
fn from(bytes: BytesMut) -> Self {
|
||||
Self::Bytes(bytes.freeze())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, E> From<SizedStream<S>> for AnyBody<SizedStream<S>>
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||
E: Into<Box<dyn StdError>> + 'static,
|
||||
{
|
||||
fn from(stream: SizedStream<S>) -> Self {
|
||||
AnyBody::new(stream)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, E> From<SizedStream<S>> for AnyBody
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||
E: Into<Box<dyn StdError>> + 'static,
|
||||
{
|
||||
fn from(stream: SizedStream<S>) -> Self {
|
||||
AnyBody::new_boxed(stream)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, E> From<BodyStream<S>> for AnyBody<BodyStream<S>>
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||
E: Into<Box<dyn StdError>> + 'static,
|
||||
{
|
||||
fn from(stream: BodyStream<S>) -> Self {
|
||||
AnyBody::new(stream)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, E> From<BodyStream<S>> for AnyBody
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||
E: Into<Box<dyn StdError>> + 'static,
|
||||
{
|
||||
fn from(stream: BodyStream<S>) -> Self {
|
||||
AnyBody::new_boxed(stream)
|
||||
}
|
||||
}
|
||||
|
||||
/// A boxed message body with boxed errors.
|
||||
pub struct BoxBody(Pin<Box<dyn MessageBody<Error = Box<dyn StdError>>>>);
|
||||
|
||||
impl BoxBody {
|
||||
/// Boxes a `MessageBody` and any errors it generates.
|
||||
pub fn from_body<B>(body: B) -> Self
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError + 'static>>,
|
||||
{
|
||||
let body = MessageBodyMapErr::new(body, Into::into);
|
||||
Self(Box::pin(body))
|
||||
}
|
||||
|
||||
/// Returns a mutable pinned reference to the inner message body type.
|
||||
pub fn as_pin_mut(
|
||||
&mut self,
|
||||
) -> Pin<&mut (dyn MessageBody<Error = Box<dyn StdError>>)> {
|
||||
self.0.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for BoxBody {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("BoxAnyBody(dyn MessageBody)")
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for BoxBody {
|
||||
type Error = Error;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
self.0.size()
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
self.0
|
||||
.as_mut()
|
||||
.poll_next(cx)
|
||||
.map_err(|err| Error::new_body().with_cause(err))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::marker::PhantomPinned;
|
||||
|
||||
use static_assertions::{assert_impl_all, assert_not_impl_all};
|
||||
|
||||
use super::*;
|
||||
use crate::body::to_bytes;
|
||||
|
||||
struct PinType(PhantomPinned);
|
||||
|
||||
impl MessageBody for PinType {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
assert_impl_all!(AnyBody<()>: MessageBody, fmt::Debug, Send, Sync, Unpin);
|
||||
assert_impl_all!(AnyBody<AnyBody<()>>: MessageBody, fmt::Debug, Send, Sync, Unpin);
|
||||
assert_impl_all!(AnyBody<Bytes>: MessageBody, fmt::Debug, Send, Sync, Unpin);
|
||||
assert_impl_all!(AnyBody: MessageBody, fmt::Debug, Unpin);
|
||||
assert_impl_all!(BoxBody: MessageBody, fmt::Debug, Unpin);
|
||||
assert_impl_all!(AnyBody<PinType>: MessageBody);
|
||||
|
||||
assert_not_impl_all!(AnyBody: Send, Sync, Unpin);
|
||||
assert_not_impl_all!(BoxBody: Send, Sync, Unpin);
|
||||
assert_not_impl_all!(AnyBody<PinType>: Send, Sync, Unpin);
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn nested_boxed_body() {
|
||||
let body = AnyBody::copy_from_slice(&[1, 2, 3]);
|
||||
let boxed_body = BoxBody::from_body(BoxBody::from_body(body));
|
||||
|
||||
assert_eq!(
|
||||
to_bytes(boxed_body).await.unwrap(),
|
||||
Bytes::from(vec![1, 2, 3]),
|
||||
);
|
||||
}
|
||||
}
|
@@ -20,8 +20,6 @@ pin_project! {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: from_infallible method
|
||||
|
||||
impl<S, E> BodyStream<S>
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, E>>,
|
||||
@@ -77,7 +75,6 @@ mod tests {
|
||||
use derive_more::{Display, Error};
|
||||
use futures_core::ready;
|
||||
use futures_util::{stream, FutureExt as _};
|
||||
use pin_project_lite::pin_project;
|
||||
use static_assertions::{assert_impl_all, assert_not_impl_all};
|
||||
|
||||
use super::*;
|
||||
@@ -169,14 +166,12 @@ mod tests {
|
||||
BodyStream::new(stream::iter(vec![Ok(Bytes::from("1")), Err(StreamErr)]));
|
||||
assert!(matches!(to_bytes(body).await, Err(StreamErr)));
|
||||
|
||||
pin_project! {
|
||||
#[derive(Debug)]
|
||||
#[project = TimeDelayStreamProj]
|
||||
enum TimeDelayStream {
|
||||
Start,
|
||||
Sleep { delay: Pin<Box<Sleep>> },
|
||||
Done,
|
||||
}
|
||||
#[pin_project::pin_project(project = TimeDelayStreamProj)]
|
||||
#[derive(Debug)]
|
||||
enum TimeDelayStream {
|
||||
Start,
|
||||
Sleep(Pin<Box<Sleep>>),
|
||||
Done,
|
||||
}
|
||||
|
||||
impl Stream for TimeDelayStream {
|
||||
@@ -189,14 +184,12 @@ mod tests {
|
||||
match self.as_mut().get_mut() {
|
||||
TimeDelayStream::Start => {
|
||||
let sleep = sleep(Duration::from_millis(1));
|
||||
self.as_mut().set(TimeDelayStream::Sleep {
|
||||
delay: Box::pin(sleep),
|
||||
});
|
||||
self.as_mut().set(TimeDelayStream::Sleep(Box::pin(sleep)));
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
|
||||
TimeDelayStream::Sleep { ref mut delay } => {
|
||||
TimeDelayStream::Sleep(ref mut delay) => {
|
||||
ready!(delay.poll_unpin(cx));
|
||||
self.set(TimeDelayStream::Done);
|
||||
cx.waker().wake_by_ref();
|
||||
|
@@ -1,80 +0,0 @@
|
||||
use std::{
|
||||
error::Error as StdError,
|
||||
fmt,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use bytes::Bytes;
|
||||
|
||||
use super::{BodySize, MessageBody, MessageBodyMapErr};
|
||||
use crate::Error;
|
||||
|
||||
/// A boxed message body with boxed errors.
|
||||
pub struct BoxBody(Pin<Box<dyn MessageBody<Error = Box<dyn StdError>>>>);
|
||||
|
||||
impl BoxBody {
|
||||
/// Boxes a `MessageBody` and any errors it generates.
|
||||
pub fn new<B>(body: B) -> Self
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
let body = MessageBodyMapErr::new(body, Into::into);
|
||||
Self(Box::pin(body))
|
||||
}
|
||||
|
||||
/// Returns a mutable pinned reference to the inner message body type.
|
||||
pub fn as_pin_mut(
|
||||
&mut self,
|
||||
) -> Pin<&mut (dyn MessageBody<Error = Box<dyn StdError>>)> {
|
||||
self.0.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for BoxBody {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("BoxBody(dyn MessageBody)")
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for BoxBody {
|
||||
type Error = Error;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
self.0.size()
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
self.0
|
||||
.as_mut()
|
||||
.poll_next(cx)
|
||||
.map_err(|err| Error::new_body().with_cause(err))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use static_assertions::{assert_impl_all, assert_not_impl_all};
|
||||
|
||||
use super::*;
|
||||
use crate::body::to_bytes;
|
||||
|
||||
assert_impl_all!(BoxBody: MessageBody, fmt::Debug, Unpin);
|
||||
|
||||
assert_not_impl_all!(BoxBody: Send, Sync, Unpin);
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn nested_boxed_body() {
|
||||
let body = Bytes::from_static(&[1, 2, 3]);
|
||||
let boxed_body = BoxBody::new(BoxBody::new(body));
|
||||
|
||||
assert_eq!(
|
||||
to_bytes(boxed_body).await.unwrap(),
|
||||
Bytes::from(vec![1, 2, 3]),
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,83 +0,0 @@
|
||||
use std::{
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use bytes::Bytes;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::{BodySize, BoxBody, MessageBody};
|
||||
use crate::Error;
|
||||
|
||||
pin_project! {
|
||||
#[project = EitherBodyProj]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EitherBody<L, R = BoxBody> {
|
||||
/// A body of type `L`.
|
||||
Left { #[pin] body: L },
|
||||
|
||||
/// A body of type `R`.
|
||||
Right { #[pin] body: R },
|
||||
}
|
||||
}
|
||||
|
||||
impl<L> EitherBody<L, BoxBody> {
|
||||
/// Creates new `EitherBody` using left variant and boxed right variant.
|
||||
pub fn new(body: L) -> Self {
|
||||
Self::Left { body }
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> EitherBody<L, R> {
|
||||
/// Creates new `EitherBody` using left variant.
|
||||
pub fn left(body: L) -> Self {
|
||||
Self::Left { body }
|
||||
}
|
||||
|
||||
/// Creates new `EitherBody` using right variant.
|
||||
pub fn right(body: R) -> Self {
|
||||
Self::Right { body }
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> MessageBody for EitherBody<L, R>
|
||||
where
|
||||
L: MessageBody + 'static,
|
||||
R: MessageBody + 'static,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
match self {
|
||||
EitherBody::Left { body } => body.size(),
|
||||
EitherBody::Right { body } => body.size(),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
match self.project() {
|
||||
EitherBodyProj::Left { body } => body
|
||||
.poll_next(cx)
|
||||
.map_err(|err| Error::new_body().with_cause(err)),
|
||||
EitherBodyProj::Right { body } => body
|
||||
.poll_next(cx)
|
||||
.map_err(|err| Error::new_body().with_cause(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn type_parameter_inference() {
|
||||
let _body: EitherBody<(), _> = EitherBody::new(());
|
||||
|
||||
let _body: EitherBody<_, ()> = EitherBody::left(());
|
||||
let _body: EitherBody<(), _> = EitherBody::right(());
|
||||
}
|
||||
}
|
@@ -2,7 +2,6 @@
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
error::Error as StdError,
|
||||
mem,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
@@ -14,12 +13,9 @@ use pin_project_lite::pin_project;
|
||||
|
||||
use super::BodySize;
|
||||
|
||||
/// An interface types that can converted to bytes and used as response bodies.
|
||||
// TODO: examples
|
||||
/// An interface for response bodies.
|
||||
pub trait MessageBody {
|
||||
// TODO: consider this bound to only fmt::Display since the error type is not really used
|
||||
// and there is an impl for Into<Box<StdError>> on String
|
||||
type Error: Into<Box<dyn StdError>>;
|
||||
type Error;
|
||||
|
||||
/// Body size hint.
|
||||
fn size(&self) -> BodySize;
|
||||
@@ -31,218 +27,152 @@ pub trait MessageBody {
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>>;
|
||||
}
|
||||
|
||||
mod foreign_impls {
|
||||
use super::*;
|
||||
impl MessageBody for () {
|
||||
type Error = Infallible;
|
||||
|
||||
impl MessageBody for Infallible {
|
||||
type Error = Infallible;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
match *self {}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
match *self {}
|
||||
}
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(0)
|
||||
}
|
||||
|
||||
impl MessageBody for () {
|
||||
type Error = Infallible;
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
Poll::Ready(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(0)
|
||||
}
|
||||
impl<B> MessageBody for Box<B>
|
||||
where
|
||||
B: MessageBody + Unpin,
|
||||
{
|
||||
type Error = B::Error;
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
fn size(&self) -> BodySize {
|
||||
self.as_ref().size()
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
Pin::new(self.get_mut().as_mut()).poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> MessageBody for Pin<Box<B>>
|
||||
where
|
||||
B: MessageBody,
|
||||
{
|
||||
type Error = B::Error;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
self.as_ref().size()
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
self.as_mut().poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for Bytes {
|
||||
type Error = Infallible;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(mem::take(self.get_mut()))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> MessageBody for Box<B>
|
||||
where
|
||||
B: MessageBody + Unpin,
|
||||
{
|
||||
type Error = B::Error;
|
||||
impl MessageBody for BytesMut {
|
||||
type Error = Infallible;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
self.as_ref().size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
Pin::new(self.get_mut().as_mut()).poll_next(cx)
|
||||
}
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
impl<B> MessageBody for Pin<Box<B>>
|
||||
where
|
||||
B: MessageBody,
|
||||
{
|
||||
type Error = B::Error;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
self.as_ref().size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
self.as_mut().poll_next(cx)
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(mem::take(self.get_mut()).freeze())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for &'static [u8] {
|
||||
type Error = Infallible;
|
||||
impl MessageBody for &'static str {
|
||||
type Error = Infallible;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
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 = mem::take(self.get_mut());
|
||||
let bytes = Bytes::from_static(bytes);
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
}
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
impl MessageBody for Bytes {
|
||||
type Error = Infallible;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
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 = mem::take(self.get_mut());
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(Bytes::from_static(
|
||||
mem::take(self.get_mut()).as_ref(),
|
||||
))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for BytesMut {
|
||||
type Error = Infallible;
|
||||
impl MessageBody for Vec<u8> {
|
||||
type Error = Infallible;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
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 = mem::take(self.get_mut()).freeze();
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
}
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
impl MessageBody for Vec<u8> {
|
||||
type Error = Infallible;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
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 = mem::take(self.get_mut());
|
||||
Poll::Ready(Some(Ok(Bytes::from(bytes))))
|
||||
}
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(Bytes::from(mem::take(self.get_mut())))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for &'static str {
|
||||
type Error = Infallible;
|
||||
impl MessageBody for String {
|
||||
type Error = Infallible;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
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 string = mem::take(self.get_mut());
|
||||
let bytes = Bytes::from_static(string.as_bytes());
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
}
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
impl MessageBody for String {
|
||||
type Error = Infallible;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
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 string = mem::take(self.get_mut());
|
||||
Poll::Ready(Some(Ok(Bytes::from(string))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for bytestring::ByteString {
|
||||
type Error = Infallible;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
let string = mem::take(self.get_mut());
|
||||
Poll::Ready(Some(Ok(string.into_bytes())))
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(Bytes::from(
|
||||
mem::take(self.get_mut()).into_bytes(),
|
||||
))))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -272,7 +202,6 @@ impl<B, F, E> MessageBody for MessageBodyMapErr<B, F>
|
||||
where
|
||||
B: MessageBody,
|
||||
F: FnOnce(B::Error) -> E,
|
||||
E: Into<Box<dyn StdError>>,
|
||||
{
|
||||
type Error = E;
|
||||
|
||||
@@ -297,129 +226,3 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_rt::pin;
|
||||
use actix_utils::future::poll_fn;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
||||
use super::*;
|
||||
|
||||
macro_rules! assert_poll_next {
|
||||
($pin:expr, $exp:expr) => {
|
||||
assert_eq!(
|
||||
poll_fn(|cx| $pin.as_mut().poll_next(cx))
|
||||
.await
|
||||
.unwrap() // unwrap option
|
||||
.unwrap(), // unwrap result
|
||||
$exp
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! assert_poll_next_none {
|
||||
($pin:expr) => {
|
||||
assert!(poll_fn(|cx| $pin.as_mut().poll_next(cx)).await.is_none());
|
||||
};
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn boxing_equivalence() {
|
||||
assert_eq!(().size(), BodySize::Sized(0));
|
||||
assert_eq!(().size(), Box::new(()).size());
|
||||
assert_eq!(().size(), Box::pin(()).size());
|
||||
|
||||
let pl = Box::new(());
|
||||
pin!(pl);
|
||||
assert_poll_next_none!(pl);
|
||||
|
||||
let mut pl = Box::pin(());
|
||||
assert_poll_next_none!(pl);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_unit() {
|
||||
let pl = ();
|
||||
assert_eq!(pl.size(), BodySize::Sized(0));
|
||||
pin!(pl);
|
||||
assert_poll_next_none!(pl);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_static_str() {
|
||||
assert_eq!("".size(), BodySize::Sized(0));
|
||||
assert_eq!("test".size(), BodySize::Sized(4));
|
||||
|
||||
let pl = "test";
|
||||
pin!(pl);
|
||||
assert_poll_next!(pl, Bytes::from("test"));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_static_bytes() {
|
||||
assert_eq!(b"".as_ref().size(), BodySize::Sized(0));
|
||||
assert_eq!(b"test".as_ref().size(), BodySize::Sized(4));
|
||||
|
||||
let pl = b"test".as_ref();
|
||||
pin!(pl);
|
||||
assert_poll_next!(pl, Bytes::from("test"));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_vec() {
|
||||
assert_eq!(vec![0; 0].size(), BodySize::Sized(0));
|
||||
assert_eq!(Vec::from("test").size(), BodySize::Sized(4));
|
||||
|
||||
let pl = Vec::from("test");
|
||||
pin!(pl);
|
||||
assert_poll_next!(pl, Bytes::from("test"));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_bytes() {
|
||||
assert_eq!(Bytes::new().size(), BodySize::Sized(0));
|
||||
assert_eq!(Bytes::from_static(b"test").size(), BodySize::Sized(4));
|
||||
|
||||
let pl = Bytes::from_static(b"test");
|
||||
pin!(pl);
|
||||
assert_poll_next!(pl, Bytes::from("test"));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_bytes_mut() {
|
||||
assert_eq!(BytesMut::new().size(), BodySize::Sized(0));
|
||||
assert_eq!(BytesMut::from(b"test".as_ref()).size(), BodySize::Sized(4));
|
||||
|
||||
let pl = BytesMut::from("test");
|
||||
pin!(pl);
|
||||
assert_poll_next!(pl, Bytes::from("test"));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_string() {
|
||||
assert_eq!(String::new().size(), BodySize::Sized(0));
|
||||
assert_eq!("test".to_owned().size(), BodySize::Sized(4));
|
||||
|
||||
let pl = "test".to_owned();
|
||||
pin!(pl);
|
||||
assert_poll_next!(pl, Bytes::from("test"));
|
||||
}
|
||||
|
||||
// down-casting used to be done with a method on MessageBody trait
|
||||
// test is kept to demonstrate equivalence of Any trait
|
||||
#[actix_rt::test]
|
||||
async fn test_body_casting() {
|
||||
let mut body = String::from("hello cast");
|
||||
// let mut resp_body: &mut dyn MessageBody<Error = Error> = &mut body;
|
||||
let resp_body: &mut dyn std::any::Any = &mut body;
|
||||
let body = resp_body.downcast_ref::<String>().unwrap();
|
||||
assert_eq!(body, "hello cast");
|
||||
let body = &mut resp_body.downcast_mut::<String>().unwrap();
|
||||
body.push('!');
|
||||
let body = resp_body.downcast_ref::<String>().unwrap();
|
||||
assert_eq!(body, "hello cast!");
|
||||
let not_body = resp_body.downcast_ref::<()>();
|
||||
assert!(not_body.is_none());
|
||||
}
|
||||
}
|
||||
|
@@ -1,20 +1,272 @@
|
||||
//! Traits and structures to aid consuming and writing HTTP payloads.
|
||||
|
||||
use std::task::Poll;
|
||||
|
||||
use actix_rt::pin;
|
||||
use actix_utils::future::poll_fn;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures_core::ready;
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
mod body;
|
||||
mod body_stream;
|
||||
mod boxed;
|
||||
mod either;
|
||||
mod message_body;
|
||||
mod none;
|
||||
mod size;
|
||||
mod sized_stream;
|
||||
mod utils;
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub use self::body::{AnyBody, Body, BoxBody};
|
||||
pub use self::body_stream::BodyStream;
|
||||
pub use self::boxed::BoxBody;
|
||||
pub use self::either::EitherBody;
|
||||
pub use self::message_body::MessageBody;
|
||||
pub(crate) use self::message_body::MessageBodyMapErr;
|
||||
pub use self::none::None;
|
||||
pub use self::size::BodySize;
|
||||
pub use self::sized_stream::SizedStream;
|
||||
pub use self::utils::to_bytes;
|
||||
|
||||
/// Collects the body produced by a `MessageBody` implementation into `Bytes`.
|
||||
///
|
||||
/// Any errors produced by the body stream are returned immediately.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_http::body::{AnyBody, to_bytes};
|
||||
/// use bytes::Bytes;
|
||||
///
|
||||
/// # async fn test_to_bytes() {
|
||||
/// let body = AnyBody::none();
|
||||
/// let bytes = to_bytes(body).await.unwrap();
|
||||
/// assert!(bytes.is_empty());
|
||||
///
|
||||
/// let body = AnyBody::copy_from_slice(b"123");
|
||||
/// let bytes = to_bytes(body).await.unwrap();
|
||||
/// assert_eq!(bytes, b"123"[..]);
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn to_bytes<B: MessageBody>(body: B) -> Result<Bytes, B::Error> {
|
||||
let cap = match body.size() {
|
||||
BodySize::None | BodySize::Sized(0) => return Ok(Bytes::new()),
|
||||
BodySize::Sized(size) => size as usize,
|
||||
// good enough first guess for chunk size
|
||||
BodySize::Stream => 32_768,
|
||||
};
|
||||
|
||||
let mut buf = BytesMut::with_capacity(cap);
|
||||
|
||||
pin!(body);
|
||||
|
||||
poll_fn(|cx| loop {
|
||||
let body = body.as_mut();
|
||||
|
||||
match ready!(body.poll_next(cx)) {
|
||||
Some(Ok(bytes)) => buf.extend_from_slice(&*bytes),
|
||||
None => return Poll::Ready(Ok(())),
|
||||
Some(Err(err)) => return Poll::Ready(Err(err)),
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(buf.freeze())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::pin::Pin;
|
||||
|
||||
use actix_rt::pin;
|
||||
use actix_utils::future::poll_fn;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
||||
use super::{to_bytes, AnyBody as TestAnyBody, BodySize, MessageBody as _};
|
||||
|
||||
impl AnyBody {
|
||||
pub(crate) fn get_ref(&self) -> &[u8] {
|
||||
match *self {
|
||||
AnyBody::Bytes(ref bin) => bin,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// AnyBody alias because rustc does not (can not?) infer the default type parameter.
|
||||
type AnyBody = TestAnyBody;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_static_str() {
|
||||
assert_eq!(AnyBody::from("").size(), BodySize::Sized(0));
|
||||
assert_eq!(AnyBody::from("test").size(), BodySize::Sized(4));
|
||||
assert_eq!(AnyBody::from("test").get_ref(), b"test");
|
||||
|
||||
assert_eq!("test".size(), BodySize::Sized(4));
|
||||
assert_eq!(
|
||||
poll_fn(|cx| Pin::new(&mut "test").poll_next(cx))
|
||||
.await
|
||||
.unwrap()
|
||||
.ok(),
|
||||
Some(Bytes::from("test"))
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_static_bytes() {
|
||||
assert_eq!(AnyBody::from(b"test".as_ref()).size(), BodySize::Sized(4));
|
||||
assert_eq!(AnyBody::from(b"test".as_ref()).get_ref(), b"test");
|
||||
assert_eq!(
|
||||
AnyBody::copy_from_slice(b"test".as_ref()).size(),
|
||||
BodySize::Sized(4)
|
||||
);
|
||||
assert_eq!(
|
||||
AnyBody::copy_from_slice(b"test".as_ref()).get_ref(),
|
||||
b"test"
|
||||
);
|
||||
let sb = Bytes::from(&b"test"[..]);
|
||||
pin!(sb);
|
||||
|
||||
assert_eq!(sb.size(), BodySize::Sized(4));
|
||||
assert_eq!(
|
||||
poll_fn(|cx| sb.as_mut().poll_next(cx)).await.unwrap().ok(),
|
||||
Some(Bytes::from("test"))
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_vec() {
|
||||
assert_eq!(AnyBody::from(Vec::from("test")).size(), BodySize::Sized(4));
|
||||
assert_eq!(AnyBody::from(Vec::from("test")).get_ref(), b"test");
|
||||
let test_vec = Vec::from("test");
|
||||
pin!(test_vec);
|
||||
|
||||
assert_eq!(test_vec.size(), BodySize::Sized(4));
|
||||
assert_eq!(
|
||||
poll_fn(|cx| test_vec.as_mut().poll_next(cx))
|
||||
.await
|
||||
.unwrap()
|
||||
.ok(),
|
||||
Some(Bytes::from("test"))
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_bytes() {
|
||||
let b = Bytes::from("test");
|
||||
assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4));
|
||||
assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test");
|
||||
pin!(b);
|
||||
|
||||
assert_eq!(b.size(), BodySize::Sized(4));
|
||||
assert_eq!(
|
||||
poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(),
|
||||
Some(Bytes::from("test"))
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_bytes_mut() {
|
||||
let b = BytesMut::from("test");
|
||||
assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4));
|
||||
assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test");
|
||||
pin!(b);
|
||||
|
||||
assert_eq!(b.size(), BodySize::Sized(4));
|
||||
assert_eq!(
|
||||
poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(),
|
||||
Some(Bytes::from("test"))
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_string() {
|
||||
let b = "test".to_owned();
|
||||
assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4));
|
||||
assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test");
|
||||
assert_eq!(AnyBody::from(&b).size(), BodySize::Sized(4));
|
||||
assert_eq!(AnyBody::from(&b).get_ref(), b"test");
|
||||
pin!(b);
|
||||
|
||||
assert_eq!(b.size(), BodySize::Sized(4));
|
||||
assert_eq!(
|
||||
poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(),
|
||||
Some(Bytes::from("test"))
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_unit() {
|
||||
assert_eq!(().size(), BodySize::Sized(0));
|
||||
assert!(poll_fn(|cx| Pin::new(&mut ()).poll_next(cx))
|
||||
.await
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_box_and_pin() {
|
||||
let val = Box::new(());
|
||||
pin!(val);
|
||||
assert_eq!(val.size(), BodySize::Sized(0));
|
||||
assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none());
|
||||
|
||||
let mut val = Box::pin(());
|
||||
assert_eq!(val.size(), BodySize::Sized(0));
|
||||
assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_body_eq() {
|
||||
assert!(
|
||||
AnyBody::Bytes(Bytes::from_static(b"1"))
|
||||
== AnyBody::Bytes(Bytes::from_static(b"1"))
|
||||
);
|
||||
assert!(AnyBody::Bytes(Bytes::from_static(b"1")) != AnyBody::None);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_body_debug() {
|
||||
assert!(format!("{:?}", AnyBody::None).contains("Body::None"));
|
||||
assert!(format!("{:?}", AnyBody::from(Bytes::from_static(b"1"))).contains('1'));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_serde_json() {
|
||||
use serde_json::{json, Value};
|
||||
assert_eq!(
|
||||
AnyBody::from(
|
||||
serde_json::to_vec(&Value::String("test".to_owned())).unwrap()
|
||||
)
|
||||
.size(),
|
||||
BodySize::Sized(6)
|
||||
);
|
||||
assert_eq!(
|
||||
AnyBody::from(
|
||||
serde_json::to_vec(&json!({"test-key":"test-value"})).unwrap()
|
||||
)
|
||||
.size(),
|
||||
BodySize::Sized(25)
|
||||
);
|
||||
}
|
||||
|
||||
// down-casting used to be done with a method on MessageBody trait
|
||||
// test is kept to demonstrate equivalence of Any trait
|
||||
#[actix_rt::test]
|
||||
async fn test_body_casting() {
|
||||
let mut body = String::from("hello cast");
|
||||
// let mut resp_body: &mut dyn MessageBody<Error = Error> = &mut body;
|
||||
let resp_body: &mut dyn std::any::Any = &mut body;
|
||||
let body = resp_body.downcast_ref::<String>().unwrap();
|
||||
assert_eq!(body, "hello cast");
|
||||
let body = &mut resp_body.downcast_mut::<String>().unwrap();
|
||||
body.push('!');
|
||||
let body = resp_body.downcast_ref::<String>().unwrap();
|
||||
assert_eq!(body, "hello cast!");
|
||||
let not_body = resp_body.downcast_ref::<()>();
|
||||
assert!(not_body.is_none());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_to_bytes() {
|
||||
let body = AnyBody::empty();
|
||||
let bytes = to_bytes(body).await.unwrap();
|
||||
assert!(bytes.is_empty());
|
||||
|
||||
let body = AnyBody::copy_from_slice(b"123");
|
||||
let bytes = to_bytes(body).await.unwrap();
|
||||
assert_eq!(bytes, b"123"[..]);
|
||||
}
|
||||
}
|
||||
|
@@ -1,43 +0,0 @@
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use bytes::Bytes;
|
||||
|
||||
use super::{BodySize, MessageBody};
|
||||
|
||||
/// Body type for responses that forbid payloads.
|
||||
///
|
||||
/// Distinct from an empty response which would contain a Content-Length header.
|
||||
///
|
||||
/// For an "empty" body, use `()` or `Bytes::new()`.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct None;
|
||||
|
||||
impl None {
|
||||
/// Constructs new "none" body.
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for None {
|
||||
type Error = Infallible;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
Poll::Ready(Option::None)
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@ pub enum BodySize {
|
||||
}
|
||||
|
||||
impl BodySize {
|
||||
/// Returns true if size hint indicates omitted or empty body.
|
||||
/// Returns true if size hint indicates no or empty body.
|
||||
///
|
||||
/// Streams will return false because it cannot be known without reading the stream.
|
||||
///
|
||||
|
@@ -32,8 +32,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: from_infallible method
|
||||
|
||||
impl<S, E> MessageBody for SizedStream<S>
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, E>>,
|
||||
|
@@ -1,78 +0,0 @@
|
||||
use std::task::Poll;
|
||||
|
||||
use actix_rt::pin;
|
||||
use actix_utils::future::poll_fn;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures_core::ready;
|
||||
|
||||
use super::{BodySize, MessageBody};
|
||||
|
||||
/// Collects the body produced by a `MessageBody` implementation into `Bytes`.
|
||||
///
|
||||
/// Any errors produced by the body stream are returned immediately.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_http::body::{self, to_bytes};
|
||||
/// use bytes::Bytes;
|
||||
///
|
||||
/// # async fn test_to_bytes() {
|
||||
/// let body = body::None::new();
|
||||
/// let bytes = to_bytes(body).await.unwrap();
|
||||
/// assert!(bytes.is_empty());
|
||||
///
|
||||
/// let body = Bytes::from_static(b"123");
|
||||
/// let bytes = to_bytes(body).await.unwrap();
|
||||
/// assert_eq!(bytes, b"123"[..]);
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn to_bytes<B: MessageBody>(body: B) -> Result<Bytes, B::Error> {
|
||||
let cap = match body.size() {
|
||||
BodySize::None | BodySize::Sized(0) => return Ok(Bytes::new()),
|
||||
BodySize::Sized(size) => size as usize,
|
||||
// good enough first guess for chunk size
|
||||
BodySize::Stream => 32_768,
|
||||
};
|
||||
|
||||
let mut buf = BytesMut::with_capacity(cap);
|
||||
|
||||
pin!(body);
|
||||
|
||||
poll_fn(|cx| loop {
|
||||
let body = body.as_mut();
|
||||
|
||||
match ready!(body.poll_next(cx)) {
|
||||
Some(Ok(bytes)) => buf.extend_from_slice(&*bytes),
|
||||
None => return Poll::Ready(Ok(())),
|
||||
Some(Err(err)) => return Poll::Ready(Err(err)),
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(buf.freeze())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use futures_util::{stream, StreamExt as _};
|
||||
|
||||
use super::*;
|
||||
use crate::{body::BodyStream, Error};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_to_bytes() {
|
||||
let bytes = to_bytes(()).await.unwrap();
|
||||
assert!(bytes.is_empty());
|
||||
|
||||
let body = Bytes::from_static(b"123");
|
||||
let bytes = to_bytes(body).await.unwrap();
|
||||
assert_eq!(bytes, b"123"[..]);
|
||||
|
||||
let stream =
|
||||
stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")])
|
||||
.map(Ok::<_, Error>);
|
||||
let body = BodyStream::new(stream);
|
||||
let bytes = to_bytes(body).await.unwrap();
|
||||
assert_eq!(bytes, b"123abc"[..]);
|
||||
}
|
||||
}
|
@@ -1,16 +1,15 @@
|
||||
use std::{fmt, marker::PhantomData, net, rc::Rc};
|
||||
use std::{error::Error as StdError, fmt, marker::PhantomData, net, rc::Rc};
|
||||
|
||||
use actix_codec::Framed;
|
||||
use actix_service::{IntoServiceFactory, Service, ServiceFactory};
|
||||
|
||||
use crate::{
|
||||
body::{BoxBody, MessageBody},
|
||||
body::{AnyBody, MessageBody},
|
||||
config::{KeepAlive, ServiceConfig},
|
||||
extensions::CloneableExtensions,
|
||||
h1::{self, ExpectHandler, H1Service, UpgradeHandler},
|
||||
h2::H2Service,
|
||||
service::HttpService,
|
||||
ConnectCallback, Request, Response,
|
||||
ConnectCallback, Extensions, Request, Response,
|
||||
};
|
||||
|
||||
/// A HTTP service builder
|
||||
@@ -32,7 +31,7 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
|
||||
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
{
|
||||
@@ -55,11 +54,11 @@ where
|
||||
impl<T, S, X, U> HttpServiceBuilder<T, S, X, U>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
X::InitError: fmt::Debug,
|
||||
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
@@ -121,7 +120,7 @@ where
|
||||
where
|
||||
F: IntoServiceFactory<X1, Request>,
|
||||
X1: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X1::Error: Into<Response<BoxBody>>,
|
||||
X1::Error: Into<Response<AnyBody>>,
|
||||
X1::InitError: fmt::Debug,
|
||||
{
|
||||
HttpServiceBuilder {
|
||||
@@ -168,7 +167,7 @@ where
|
||||
/// and handlers.
|
||||
pub fn on_connect_ext<F>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Fn(&T, &mut CloneableExtensions) + 'static,
|
||||
F: Fn(&T, &mut Extensions) + 'static,
|
||||
{
|
||||
self.on_connect_ext = Some(Rc::new(f));
|
||||
self
|
||||
@@ -179,7 +178,7 @@ where
|
||||
where
|
||||
B: MessageBody,
|
||||
F: IntoServiceFactory<S, Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
{
|
||||
@@ -201,11 +200,12 @@ where
|
||||
pub fn h2<F, B>(self, service: F) -> H2Service<T, S, B>
|
||||
where
|
||||
F: IntoServiceFactory<S, Request>,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
let cfg = ServiceConfig::new(
|
||||
self.keep_alive,
|
||||
@@ -223,11 +223,12 @@ where
|
||||
pub fn finish<F, B>(self, service: F) -> HttpService<T, S, B, X, U>
|
||||
where
|
||||
F: IntoServiceFactory<S, Request>,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
let cfg = ServiceConfig::new(
|
||||
self.keep_alive,
|
||||
|
@@ -23,7 +23,7 @@ use zstd::stream::write::Decoder as ZstdDecoder;
|
||||
use crate::{
|
||||
encoding::Writer,
|
||||
error::{BlockingError, PayloadError},
|
||||
header::{ContentEncoding, HeaderMap, CONTENT_ENCODING},
|
||||
http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING},
|
||||
};
|
||||
|
||||
const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049;
|
||||
|
@@ -12,7 +12,7 @@ use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||
use bytes::Bytes;
|
||||
use derive_more::Display;
|
||||
use futures_core::ready;
|
||||
use pin_project_lite::pin_project;
|
||||
use pin_project::pin_project;
|
||||
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
use brotli2::write::BrotliEncoder;
|
||||
@@ -23,100 +23,93 @@ use flate2::write::{GzEncoder, ZlibEncoder};
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
use zstd::stream::write::Encoder as ZstdEncoder;
|
||||
|
||||
use super::Writer;
|
||||
use crate::{
|
||||
body::{BodySize, MessageBody},
|
||||
error::BlockingError,
|
||||
header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING},
|
||||
ResponseHead, StatusCode,
|
||||
body::{AnyBody, BodySize, MessageBody},
|
||||
http::{
|
||||
header::{ContentEncoding, CONTENT_ENCODING},
|
||||
HeaderValue, StatusCode,
|
||||
},
|
||||
ResponseHead,
|
||||
};
|
||||
|
||||
use super::Writer;
|
||||
use crate::error::BlockingError;
|
||||
|
||||
const MAX_CHUNK_SIZE_ENCODE_IN_PLACE: usize = 1024;
|
||||
|
||||
pin_project! {
|
||||
pub struct Encoder<B> {
|
||||
#[pin]
|
||||
body: EncoderBody<B>,
|
||||
encoder: Option<ContentEncoder>,
|
||||
fut: Option<JoinHandle<Result<ContentEncoder, io::Error>>>,
|
||||
eof: bool,
|
||||
}
|
||||
#[pin_project]
|
||||
pub struct Encoder<B> {
|
||||
eof: bool,
|
||||
#[pin]
|
||||
body: EncoderBody<B>,
|
||||
encoder: Option<ContentEncoder>,
|
||||
fut: Option<JoinHandle<Result<ContentEncoder, io::Error>>>,
|
||||
}
|
||||
|
||||
impl<B: MessageBody> Encoder<B> {
|
||||
fn none() -> Self {
|
||||
Encoder {
|
||||
body: EncoderBody::None,
|
||||
encoder: None,
|
||||
fut: None,
|
||||
eof: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn response(
|
||||
encoding: ContentEncoding,
|
||||
head: &mut ResponseHead,
|
||||
body: B,
|
||||
) -> Self {
|
||||
body: AnyBody<B>,
|
||||
) -> AnyBody<Encoder<B>> {
|
||||
let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
|
||||
|| head.status == StatusCode::SWITCHING_PROTOCOLS
|
||||
|| head.status == StatusCode::NO_CONTENT
|
||||
|| encoding == ContentEncoding::Identity
|
||||
|| encoding == ContentEncoding::Auto);
|
||||
|
||||
match body.size() {
|
||||
// no need to compress an empty body
|
||||
BodySize::None => return Self::none(),
|
||||
|
||||
// we cannot assume that Sized is not a stream
|
||||
BodySize::Sized(_) | BodySize::Stream => {}
|
||||
}
|
||||
|
||||
// TODO potentially some optimisation for single-chunk responses here by trying to read the
|
||||
// payload eagerly, stopping after 2 polls if the first is a chunk and the second is None
|
||||
let body = match body {
|
||||
AnyBody::None => return AnyBody::None,
|
||||
AnyBody::Bytes(buf) => {
|
||||
if can_encode {
|
||||
EncoderBody::Bytes(buf)
|
||||
} else {
|
||||
return AnyBody::Bytes(buf);
|
||||
}
|
||||
}
|
||||
AnyBody::Body(body) => EncoderBody::Stream(body),
|
||||
};
|
||||
|
||||
if can_encode {
|
||||
// Modify response body only if encoder is set
|
||||
// Modify response body only if encoder is not None
|
||||
if let Some(enc) = ContentEncoder::encoder(encoding) {
|
||||
update_head(encoding, head);
|
||||
head.no_chunking(false);
|
||||
|
||||
return Encoder {
|
||||
body: EncoderBody::Stream { body },
|
||||
encoder: Some(enc),
|
||||
fut: None,
|
||||
return AnyBody::Body(Encoder {
|
||||
body,
|
||||
eof: false,
|
||||
};
|
||||
fut: None,
|
||||
encoder: Some(enc),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Encoder {
|
||||
body: EncoderBody::Stream { body },
|
||||
encoder: None,
|
||||
fut: None,
|
||||
AnyBody::Body(Encoder {
|
||||
body,
|
||||
eof: false,
|
||||
}
|
||||
fut: None,
|
||||
encoder: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = EncoderBodyProj]
|
||||
enum EncoderBody<B> {
|
||||
None,
|
||||
Stream { #[pin] body: B },
|
||||
}
|
||||
#[pin_project(project = EncoderBodyProj)]
|
||||
enum EncoderBody<B> {
|
||||
Bytes(Bytes),
|
||||
Stream(#[pin] B),
|
||||
}
|
||||
|
||||
impl<B> MessageBody for EncoderBody<B>
|
||||
where
|
||||
B: MessageBody,
|
||||
{
|
||||
type Error = EncoderError;
|
||||
type Error = EncoderError<B::Error>;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
match self {
|
||||
EncoderBody::None => BodySize::None,
|
||||
EncoderBody::Stream { body } => body.size(),
|
||||
EncoderBody::Bytes(ref b) => b.size(),
|
||||
EncoderBody::Stream(ref b) => b.size(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,11 +118,14 @@ where
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
match self.project() {
|
||||
EncoderBodyProj::None => Poll::Ready(None),
|
||||
|
||||
EncoderBodyProj::Stream { body } => body
|
||||
.poll_next(cx)
|
||||
.map_err(|err| EncoderError::Body(err.into())),
|
||||
EncoderBodyProj::Bytes(b) => {
|
||||
if b.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(std::mem::take(b))))
|
||||
}
|
||||
}
|
||||
EncoderBodyProj::Stream(b) => b.poll_next(cx).map_err(EncoderError::Body),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,7 +134,7 @@ impl<B> MessageBody for Encoder<B>
|
||||
where
|
||||
B: MessageBody,
|
||||
{
|
||||
type Error = EncoderError;
|
||||
type Error = EncoderError<B::Error>;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
if self.encoder.is_none() {
|
||||
@@ -201,7 +197,6 @@ where
|
||||
None => {
|
||||
if let Some(encoder) = this.encoder.take() {
|
||||
let chunk = encoder.finish().map_err(EncoderError::Io)?;
|
||||
|
||||
if chunk.is_empty() {
|
||||
return Poll::Ready(None);
|
||||
} else {
|
||||
@@ -219,7 +214,7 @@ where
|
||||
|
||||
fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
|
||||
head.headers_mut().insert(
|
||||
header::CONTENT_ENCODING,
|
||||
CONTENT_ENCODING,
|
||||
HeaderValue::from_static(encoding.as_str()),
|
||||
);
|
||||
}
|
||||
@@ -227,15 +222,12 @@ fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
|
||||
enum ContentEncoder {
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
Deflate(ZlibEncoder<Writer>),
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
Gzip(GzEncoder<Writer>),
|
||||
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
Br(BrotliEncoder<Writer>),
|
||||
|
||||
// Wwe need explicit 'static lifetime here because ZstdEncoder needs a lifetime argument and we
|
||||
// use `spawn_blocking` in `Encoder::poll_next` that requires `FnOnce() -> R + Send + 'static`.
|
||||
// We need explicit 'static lifetime here because ZstdEncoder need lifetime
|
||||
// argument, and we use `spawn_blocking` in `Encoder::poll_next` that require `FnOnce() -> R + Send + 'static`
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
Zstd(ZstdEncoder<'static, Writer>),
|
||||
}
|
||||
@@ -248,24 +240,20 @@ impl ContentEncoder {
|
||||
Writer::new(),
|
||||
flate2::Compression::fast(),
|
||||
))),
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new(
|
||||
Writer::new(),
|
||||
flate2::Compression::fast(),
|
||||
))),
|
||||
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
ContentEncoding::Br => {
|
||||
Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3)))
|
||||
}
|
||||
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
ContentEncoding::Zstd => {
|
||||
let encoder = ZstdEncoder::new(Writer::new(), 3).ok()?;
|
||||
Some(ContentEncoder::Zstd(encoder))
|
||||
}
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -275,13 +263,10 @@ impl ContentEncoder {
|
||||
match *self {
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(),
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(),
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(),
|
||||
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
ContentEncoder::Zstd(ref mut encoder) => encoder.get_mut().take(),
|
||||
}
|
||||
@@ -294,19 +279,16 @@ impl ContentEncoder {
|
||||
Ok(writer) => Ok(writer.buf.freeze()),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoder::Gzip(encoder) => match encoder.finish() {
|
||||
Ok(writer) => Ok(writer.buf.freeze()),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoder::Deflate(encoder) => match encoder.finish() {
|
||||
Ok(writer) => Ok(writer.buf.freeze()),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
ContentEncoder::Zstd(encoder) => match encoder.finish() {
|
||||
Ok(writer) => Ok(writer.buf.freeze()),
|
||||
@@ -325,7 +307,6 @@ impl ContentEncoder {
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
@@ -334,7 +315,6 @@ impl ContentEncoder {
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
@@ -343,7 +323,6 @@ impl ContentEncoder {
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
@@ -358,9 +337,9 @@ impl ContentEncoder {
|
||||
|
||||
#[derive(Debug, Display)]
|
||||
#[non_exhaustive]
|
||||
pub enum EncoderError {
|
||||
pub enum EncoderError<E> {
|
||||
#[display(fmt = "body")]
|
||||
Body(Box<dyn StdError>),
|
||||
Body(E),
|
||||
|
||||
#[display(fmt = "blocking")]
|
||||
Blocking(BlockingError),
|
||||
@@ -369,18 +348,18 @@ pub enum EncoderError {
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
impl StdError for EncoderError {
|
||||
impl<E: StdError + 'static> StdError for EncoderError<E> {
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
match self {
|
||||
EncoderError::Body(err) => Some(&**err),
|
||||
EncoderError::Body(err) => Some(err),
|
||||
EncoderError::Blocking(err) => Some(err),
|
||||
EncoderError::Io(err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EncoderError> for crate::Error {
|
||||
fn from(err: EncoderError) -> Self {
|
||||
impl<E: StdError + 'static> From<EncoderError<E>> for crate::Error {
|
||||
fn from(err: EncoderError<E>) -> Self {
|
||||
crate::Error::new_encoder().with_cause(err)
|
||||
}
|
||||
}
|
||||
|
@@ -10,9 +10,6 @@ mod encoder;
|
||||
pub use self::decoder::Decoder;
|
||||
pub use self::encoder::Encoder;
|
||||
|
||||
/// Special-purpose writer for streaming (de-)compression.
|
||||
///
|
||||
/// Pre-allocates 8KiB of capacity.
|
||||
pub(self) struct Writer {
|
||||
buf: BytesMut,
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Err
|
||||
use derive_more::{Display, Error, From};
|
||||
use http::{uri::InvalidUri, StatusCode};
|
||||
|
||||
use crate::{body::BoxBody, ws, Response};
|
||||
use crate::{body::AnyBody, ws, Response};
|
||||
|
||||
pub use http::Error as HttpError;
|
||||
|
||||
@@ -66,15 +66,14 @@ impl Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for Response<BoxBody> {
|
||||
impl<B> From<Error> for Response<AnyBody<B>> {
|
||||
fn from(err: Error) -> Self {
|
||||
// TODO: more appropriate error status codes, usage assessment needed
|
||||
let status_code = match err.inner.kind {
|
||||
Kind::Parse => StatusCode::BAD_REQUEST,
|
||||
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
};
|
||||
|
||||
Response::new(status_code).set_body(BoxBody::new(err.to_string()))
|
||||
Response::new(status_code).set_body(AnyBody::from(err.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +132,12 @@ impl From<std::convert::Infallible> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ws::ProtocolError> for Error {
|
||||
fn from(err: ws::ProtocolError) -> Self {
|
||||
Self::new_ws().with_cause(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HttpError> for Error {
|
||||
fn from(err: HttpError) -> Self {
|
||||
Self::new_http().with_cause(err)
|
||||
@@ -145,12 +150,6 @@ impl From<ws::HandshakeError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ws::ProtocolError> for Error {
|
||||
fn from(err: ws::ProtocolError) -> Self {
|
||||
Self::new_ws().with_cause(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of errors that can occur during parsing HTTP streams.
|
||||
#[derive(Debug, Display, Error)]
|
||||
#[non_exhaustive]
|
||||
@@ -241,7 +240,7 @@ impl From<ParseError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError> for Response<BoxBody> {
|
||||
impl From<ParseError> for Response<AnyBody> {
|
||||
fn from(err: ParseError) -> Self {
|
||||
Error::from(err).into()
|
||||
}
|
||||
@@ -338,7 +337,7 @@ pub enum DispatchError {
|
||||
/// Service error
|
||||
// FIXME: display and error type
|
||||
#[display(fmt = "Service Error")]
|
||||
Service(#[error(not(source))] Response<BoxBody>),
|
||||
Service(#[error(not(source))] Response<AnyBody>),
|
||||
|
||||
/// Body error
|
||||
// FIXME: display and error type
|
||||
@@ -422,11 +421,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_into_response() {
|
||||
let resp: Response<BoxBody> = ParseError::Incomplete.into();
|
||||
let resp: Response<AnyBody> = ParseError::Incomplete.into();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into();
|
||||
let resp: Response<BoxBody> = Error::new_http().with_cause(err).into();
|
||||
let resp: Response<AnyBody> = Error::new_http().with_cause(err).into();
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
@@ -451,7 +450,7 @@ mod tests {
|
||||
fn test_error_http_response() {
|
||||
let orig = io::Error::new(io::ErrorKind::Other, "other");
|
||||
let err = Error::new_io().with_cause(orig);
|
||||
let resp: Response<BoxBody> = err.into();
|
||||
let resp: Response<AnyBody> = err.into();
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
fmt,
|
||||
fmt, mem,
|
||||
};
|
||||
|
||||
use ahash::AHashMap;
|
||||
@@ -10,7 +10,8 @@ use ahash::AHashMap;
|
||||
/// All entries into this map must be owned types (or static references).
|
||||
#[derive(Default)]
|
||||
pub struct Extensions {
|
||||
/// Use AHasher with a std HashMap with for faster lookups on the small `TypeId` keys.
|
||||
/// Use FxHasher with a std HashMap with for faster
|
||||
/// lookups on the small `TypeId` (u64 equivalent) keys.
|
||||
map: AHashMap<TypeId, Box<dyn Any>>,
|
||||
}
|
||||
|
||||
@@ -123,11 +124,9 @@ impl Extensions {
|
||||
self.map.extend(other.map);
|
||||
}
|
||||
|
||||
/// Sets (or overrides) items from cloneable extensions map into this map.
|
||||
pub(crate) fn clone_from(&mut self, other: &CloneableExtensions) {
|
||||
for (k, val) in &other.map {
|
||||
self.map.insert(*k, (**val).clone_to_any());
|
||||
}
|
||||
/// Sets (or overrides) items from `other` into this map.
|
||||
pub(crate) fn drain_from(&mut self, other: &mut Self) {
|
||||
self.map.extend(mem::take(&mut other.map));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,104 +140,6 @@ fn downcast_owned<T: 'static>(boxed: Box<dyn Any>) -> Option<T> {
|
||||
boxed.downcast().ok().map(|boxed| *boxed)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait CloneToAny {
|
||||
/// Cast `self` into an `Any` reference.
|
||||
#[cfg(test)]
|
||||
fn any_ref(&self) -> &dyn Any;
|
||||
|
||||
/// Clone `self` to a new `Box<Any>` object.
|
||||
fn clone_to_any(&self) -> Box<dyn Any>;
|
||||
|
||||
/// Clone `self` to a new `Box<CloneAny>` object.
|
||||
fn clone_to_clone_any(&self) -> Box<dyn CloneAny>;
|
||||
}
|
||||
|
||||
impl<T: Clone + Any> CloneToAny for T {
|
||||
#[cfg(test)]
|
||||
fn any_ref(&self) -> &dyn Any {
|
||||
&*self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clone_to_any(&self) -> Box<dyn Any> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clone_to_clone_any(&self) -> Box<dyn CloneAny> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`Any`] trait with an additional [`Clone`] requirement.
|
||||
pub trait CloneAny: CloneToAny + Any {}
|
||||
impl<T: Any + Clone> CloneAny for T {}
|
||||
|
||||
impl Clone for Box<dyn CloneAny> {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
(**self).clone_to_clone_any()
|
||||
}
|
||||
}
|
||||
|
||||
trait UncheckedAnyExt {
|
||||
/// # Safety
|
||||
/// Caller must ensure type `T` is true type.
|
||||
#[inline]
|
||||
unsafe fn downcast_unchecked<T: 'static>(self: Box<Self>) -> Box<T> {
|
||||
Box::from_raw(Box::into_raw(self) as *mut T)
|
||||
}
|
||||
}
|
||||
|
||||
impl UncheckedAnyExt for dyn CloneAny {}
|
||||
|
||||
/// A type map for `on_connect` extensions.
|
||||
///
|
||||
/// All entries into this map must be owned types and implement `Clone` trait.
|
||||
///
|
||||
/// Many requests can be processed for each connection but the `on_connect` will only be run once
|
||||
/// when the connection is opened. Therefore, items added to this special map type need to be cloned
|
||||
/// into the regular extensions map for each request. Most useful connection information types are
|
||||
/// cloneable already but you can use reference counted wrappers if not.
|
||||
#[derive(Default)]
|
||||
pub struct CloneableExtensions {
|
||||
/// Use AHasher with a std HashMap with for faster lookups on the small `TypeId` keys.
|
||||
map: AHashMap<TypeId, Box<dyn CloneAny>>,
|
||||
}
|
||||
|
||||
impl CloneableExtensions {
|
||||
/// Insert an item into the map.
|
||||
///
|
||||
/// If an item of this type was already stored, it will be replaced and returned.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::Extensions;
|
||||
/// let mut map = Extensions::new();
|
||||
/// assert_eq!(map.insert(""), None);
|
||||
/// assert_eq!(map.insert(1u32), None);
|
||||
/// assert_eq!(map.insert(2u32), Some(1u32));
|
||||
/// assert_eq!(*map.get::<u32>().unwrap(), 2u32);
|
||||
/// ```
|
||||
pub fn insert<T: CloneAny>(&mut self, val: T) -> Option<T> {
|
||||
self.map
|
||||
.insert(TypeId::of::<T>(), Box::new(val))
|
||||
.map(|boxed| {
|
||||
// Safety:
|
||||
// Box is owned and `T` is known to be true type from map.
|
||||
*unsafe { UncheckedAnyExt::downcast_unchecked::<T>(boxed) }
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn get<T: CloneAny>(&self) -> Option<&T> {
|
||||
self.map
|
||||
.get(&TypeId::of::<T>())
|
||||
.and_then(|boxed| boxed.as_ref().any_ref().downcast_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -278,8 +179,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_integers() {
|
||||
static A: u32 = 8;
|
||||
|
||||
let mut map = Extensions::new();
|
||||
|
||||
map.insert::<i8>(8);
|
||||
@@ -292,7 +191,6 @@ mod tests {
|
||||
map.insert::<u32>(32);
|
||||
map.insert::<u64>(64);
|
||||
map.insert::<u128>(128);
|
||||
map.insert::<&'static u32>(&A);
|
||||
assert!(map.get::<i8>().is_some());
|
||||
assert!(map.get::<i16>().is_some());
|
||||
assert!(map.get::<i32>().is_some());
|
||||
@@ -303,7 +201,6 @@ mod tests {
|
||||
assert!(map.get::<u32>().is_some());
|
||||
assert!(map.get::<u64>().is_some());
|
||||
assert!(map.get::<u128>().is_some());
|
||||
assert!(map.get::<&'static u32>().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -384,41 +281,25 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clone_from() {
|
||||
#[derive(Clone)]
|
||||
struct NonCopy {
|
||||
num: u8,
|
||||
}
|
||||
|
||||
fn test_drain_from() {
|
||||
let mut ext = Extensions::new();
|
||||
ext.insert(2isize);
|
||||
|
||||
let mut more_ext = Extensions::new();
|
||||
|
||||
more_ext.insert(5isize);
|
||||
more_ext.insert(5usize);
|
||||
|
||||
assert_eq!(ext.get::<isize>(), Some(&2isize));
|
||||
assert_eq!(ext.get::<usize>(), None);
|
||||
assert_eq!(more_ext.get::<isize>(), Some(&5isize));
|
||||
assert_eq!(more_ext.get::<usize>(), Some(&5usize));
|
||||
|
||||
let mut more_ext = CloneableExtensions::default();
|
||||
more_ext.insert(3isize);
|
||||
more_ext.insert(3usize);
|
||||
more_ext.insert(NonCopy { num: 8 });
|
||||
ext.drain_from(&mut more_ext);
|
||||
|
||||
ext.clone_from(&more_ext);
|
||||
|
||||
assert_eq!(ext.get::<isize>(), Some(&3isize));
|
||||
assert_eq!(ext.get::<usize>(), Some(&3usize));
|
||||
assert_eq!(more_ext.get::<isize>(), Some(&3isize));
|
||||
assert_eq!(more_ext.get::<usize>(), Some(&3usize));
|
||||
|
||||
assert!(ext.get::<NonCopy>().is_some());
|
||||
assert!(more_ext.get::<NonCopy>().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boxes_not_aliased() {
|
||||
let a: Box<dyn CloneAny> = Box::new(42);
|
||||
let b = a.clone_to_clone_any();
|
||||
assert_ne!(Box::into_raw(a) as *const (), Box::into_raw(b) as *const ());
|
||||
|
||||
let a: Box<dyn CloneAny> = Box::new(42);
|
||||
let b = a.clone_to_any();
|
||||
assert_ne!(Box::into_raw(a) as *const (), Box::into_raw(b) as *const ());
|
||||
assert_eq!(ext.get::<isize>(), Some(&5isize));
|
||||
assert_eq!(ext.get::<usize>(), Some(&5usize));
|
||||
assert_eq!(more_ext.get::<isize>(), None);
|
||||
assert_eq!(more_ext.get::<usize>(), None);
|
||||
}
|
||||
}
|
||||
|
@@ -511,7 +511,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
error::ParseError,
|
||||
header::{HeaderName, SET_COOKIE},
|
||||
http::header::{HeaderName, SET_COOKIE},
|
||||
HttpMessage as _,
|
||||
};
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
error::Error as StdError,
|
||||
fmt,
|
||||
future::Future,
|
||||
io, mem, net,
|
||||
@@ -18,7 +19,7 @@ use log::{error, trace};
|
||||
use pin_project::pin_project;
|
||||
|
||||
use crate::{
|
||||
body::{BodySize, BoxBody, MessageBody},
|
||||
body::{AnyBody, BodySize, MessageBody},
|
||||
config::ServiceConfig,
|
||||
error::{DispatchError, ParseError, PayloadError},
|
||||
service::HttpFlow,
|
||||
@@ -50,12 +51,13 @@ bitflags! {
|
||||
pub struct Dispatcher<T, S, B, X, U>
|
||||
where
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
@@ -71,12 +73,13 @@ where
|
||||
enum DispatcherState<T, S, B, X, U>
|
||||
where
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
@@ -89,12 +92,13 @@ where
|
||||
struct InnerDispatcher<T, S, B, X, U>
|
||||
where
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
@@ -133,12 +137,13 @@ where
|
||||
X: Service<Request, Response = Request>,
|
||||
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
None,
|
||||
ExpectCall(#[pin] X::Future),
|
||||
ServiceCall(#[pin] S::Future),
|
||||
SendPayload(#[pin] B),
|
||||
SendErrorPayload(#[pin] BoxBody),
|
||||
SendErrorPayload(#[pin] AnyBody),
|
||||
}
|
||||
|
||||
impl<S, B, X> State<S, B, X>
|
||||
@@ -148,6 +153,7 @@ where
|
||||
X: Service<Request, Response = Request>,
|
||||
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
fn is_empty(&self) -> bool {
|
||||
matches!(self, State::None)
|
||||
@@ -165,13 +171,14 @@ where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
S::Response: Into<Response<B>>,
|
||||
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
@@ -225,13 +232,14 @@ where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
S::Response: Into<Response<B>>,
|
||||
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
@@ -327,7 +335,7 @@ where
|
||||
fn send_error_response(
|
||||
mut self: Pin<&mut Self>,
|
||||
message: Response<()>,
|
||||
body: BoxBody,
|
||||
body: AnyBody,
|
||||
) -> Result<(), DispatchError> {
|
||||
let size = self.as_mut().send_response_inner(message, &body)?;
|
||||
let state = match size {
|
||||
@@ -372,7 +380,7 @@ where
|
||||
// send_response would update InnerDispatcher state to SendPayload or
|
||||
// None(If response body is empty).
|
||||
// continue loop to poll it.
|
||||
self.as_mut().send_error_response(res, BoxBody::new(()))?;
|
||||
self.as_mut().send_error_response(res, AnyBody::empty())?;
|
||||
}
|
||||
|
||||
// return with upgrade request and poll it exclusively.
|
||||
@@ -392,7 +400,7 @@ where
|
||||
|
||||
// send service call error as response
|
||||
Poll::Ready(Err(err)) => {
|
||||
let res: Response<BoxBody> = err.into();
|
||||
let res: Response<AnyBody> = err.into();
|
||||
let (res, body) = res.replace_body(());
|
||||
self.as_mut().send_error_response(res, body)?;
|
||||
}
|
||||
@@ -489,7 +497,7 @@ where
|
||||
|
||||
// send expect error as response
|
||||
Poll::Ready(Err(err)) => {
|
||||
let res: Response<BoxBody> = err.into();
|
||||
let res: Response<AnyBody> = err.into();
|
||||
let (res, body) = res.replace_body(());
|
||||
self.as_mut().send_error_response(res, body)?;
|
||||
}
|
||||
@@ -538,7 +546,7 @@ where
|
||||
// to notify the dispatcher a new state is set and the outer loop
|
||||
// should be continue.
|
||||
Poll::Ready(Err(err)) => {
|
||||
let res: Response<BoxBody> = err.into();
|
||||
let res: Response<AnyBody> = err.into();
|
||||
let (res, body) = res.replace_body(());
|
||||
return self.send_error_response(res, body);
|
||||
}
|
||||
@@ -558,7 +566,7 @@ where
|
||||
Poll::Pending => Ok(()),
|
||||
// see the comment on ExpectCall state branch's Ready(Err(err)).
|
||||
Poll::Ready(Err(err)) => {
|
||||
let res: Response<BoxBody> = err.into();
|
||||
let res: Response<AnyBody> = err.into();
|
||||
let (res, body) = res.replace_body(());
|
||||
self.send_error_response(res, body)
|
||||
}
|
||||
@@ -764,7 +772,7 @@ where
|
||||
trace!("Slow request timeout");
|
||||
let _ = self.as_mut().send_error_response(
|
||||
Response::with_body(StatusCode::REQUEST_TIMEOUT, ()),
|
||||
BoxBody::new(()),
|
||||
AnyBody::empty(),
|
||||
);
|
||||
this = self.project();
|
||||
this.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
|
||||
@@ -901,13 +909,14 @@ where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
S::Response: Into<Response<B>>,
|
||||
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
@@ -1037,8 +1046,9 @@ mod tests {
|
||||
use crate::{
|
||||
error::Error,
|
||||
h1::{ExpectHandler, UpgradeHandler},
|
||||
http::Method,
|
||||
test::{TestBuffer, TestSeqBuffer},
|
||||
HttpMessage, KeepAlive, Method,
|
||||
HttpMessage, KeepAlive,
|
||||
};
|
||||
|
||||
fn find_slice(haystack: &[u8], needle: &[u8], from: usize) -> Option<usize> {
|
||||
@@ -1057,19 +1067,17 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn ok_service(
|
||||
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error>
|
||||
fn ok_service() -> impl Service<Request, Response = Response<AnyBody>, Error = Error>
|
||||
{
|
||||
fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok())))
|
||||
}
|
||||
|
||||
fn echo_path_service(
|
||||
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error>
|
||||
{
|
||||
) -> impl Service<Request, Response = Response<AnyBody>, Error = Error> {
|
||||
fn_service(|req: Request| {
|
||||
let path = req.path().as_bytes();
|
||||
ready(Ok::<_, Error>(
|
||||
Response::ok().set_body(Bytes::copy_from_slice(path)),
|
||||
Response::ok().set_body(AnyBody::copy_from_slice(path)),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
@@ -531,10 +531,8 @@ mod tests {
|
||||
use http::header::AUTHORIZATION;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
header::{HeaderValue, CONTENT_TYPE},
|
||||
RequestHead,
|
||||
};
|
||||
use crate::http::header::{HeaderValue, CONTENT_TYPE};
|
||||
use crate::RequestHead;
|
||||
|
||||
#[test]
|
||||
fn test_chunked_te() {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
error::Error as StdError,
|
||||
fmt,
|
||||
marker::PhantomData,
|
||||
net,
|
||||
@@ -15,7 +16,7 @@ use actix_utils::future::ready;
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
|
||||
use crate::{
|
||||
body::{BoxBody, MessageBody},
|
||||
body::{AnyBody, MessageBody},
|
||||
config::ServiceConfig,
|
||||
error::DispatchError,
|
||||
service::HttpServiceHandler,
|
||||
@@ -37,7 +38,7 @@ pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler> {
|
||||
impl<T, S, B> H1Service<T, S, B>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody,
|
||||
@@ -62,20 +63,21 @@ impl<S, B, X, U> H1Service<TcpStream, S, B, X, U>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X::Future: 'static,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
X::InitError: fmt::Debug,
|
||||
|
||||
U: ServiceFactory<(Request, Framed<TcpStream, Codec>), Config = (), Response = ()>,
|
||||
U::Future: 'static,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create simple tcp stream service
|
||||
@@ -112,15 +114,16 @@ mod openssl {
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X::Future: 'static,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
X::InitError: fmt::Debug,
|
||||
|
||||
U: ServiceFactory<
|
||||
@@ -129,7 +132,7 @@ mod openssl {
|
||||
Response = (),
|
||||
>,
|
||||
U::Future: 'static,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create OpenSSL based service.
|
||||
@@ -174,15 +177,16 @@ mod rustls {
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X::Future: 'static,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
X::InitError: fmt::Debug,
|
||||
|
||||
U: ServiceFactory<
|
||||
@@ -191,7 +195,7 @@ mod rustls {
|
||||
Response = (),
|
||||
>,
|
||||
U::Future: 'static,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create Rustls based service.
|
||||
@@ -222,7 +226,7 @@ mod rustls {
|
||||
impl<T, S, B, X, U> H1Service<T, S, B, X, U>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
S::Response: Into<Response<B>>,
|
||||
S::InitError: fmt::Debug,
|
||||
B: MessageBody,
|
||||
@@ -230,7 +234,7 @@ where
|
||||
pub fn expect<X1>(self, expect: X1) -> H1Service<T, S, B, X1, U>
|
||||
where
|
||||
X1: ServiceFactory<Request, Response = Request>,
|
||||
X1::Error: Into<Response<BoxBody>>,
|
||||
X1::Error: Into<Response<AnyBody>>,
|
||||
X1::InitError: fmt::Debug,
|
||||
{
|
||||
H1Service {
|
||||
@@ -273,20 +277,21 @@ where
|
||||
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
S::Response: Into<Response<B>>,
|
||||
S::InitError: fmt::Debug,
|
||||
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X::Future: 'static,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
X::InitError: fmt::Debug,
|
||||
|
||||
U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
|
||||
U::Future: 'static,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
type Response = ();
|
||||
@@ -342,16 +347,17 @@ where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
S::Response: Into<Response<B>>,
|
||||
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
||||
{
|
||||
type Response = ();
|
||||
type Error = DispatchError;
|
||||
|
@@ -1,30 +1,22 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::{
|
||||
body::{BodySize, MessageBody},
|
||||
error::Error,
|
||||
h1::{Codec, Message},
|
||||
response::Response,
|
||||
};
|
||||
use crate::body::{BodySize, MessageBody};
|
||||
use crate::error::Error;
|
||||
use crate::h1::{Codec, Message};
|
||||
use crate::response::Response;
|
||||
|
||||
pin_project! {
|
||||
/// Send HTTP/1 response
|
||||
pub struct SendResponse<T, B> {
|
||||
res: Option<Message<(Response<()>, BodySize)>>,
|
||||
|
||||
#[pin]
|
||||
body: Option<B>,
|
||||
|
||||
#[pin]
|
||||
framed: Option<Framed<T, Codec>>,
|
||||
}
|
||||
/// Send HTTP/1 response
|
||||
#[pin_project::pin_project]
|
||||
pub struct SendResponse<T, B> {
|
||||
res: Option<Message<(Response<()>, BodySize)>>,
|
||||
#[pin]
|
||||
body: Option<B>,
|
||||
#[pin]
|
||||
framed: Option<Framed<T, Codec>>,
|
||||
}
|
||||
|
||||
impl<T, B> SendResponse<T, B>
|
||||
|
@@ -10,7 +10,7 @@ use std::{
|
||||
};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_rt::time::{sleep, Sleep};
|
||||
use actix_rt::time::Sleep;
|
||||
use actix_service::Service;
|
||||
use actix_utils::future::poll_fn;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
@@ -24,7 +24,7 @@ use log::{error, trace};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::{
|
||||
body::{BodySize, BoxBody, MessageBody},
|
||||
body::{AnyBody, BodySize, MessageBody},
|
||||
config::ServiceConfig,
|
||||
service::HttpFlow,
|
||||
OnConnectData, Payload, Request, Response, ResponseHead,
|
||||
@@ -51,29 +51,22 @@ where
|
||||
{
|
||||
pub(crate) fn new(
|
||||
flow: Rc<HttpFlow<S, X, U>>,
|
||||
mut conn: Connection<T, Bytes>,
|
||||
mut connection: Connection<T, Bytes>,
|
||||
on_connect_data: OnConnectData,
|
||||
config: ServiceConfig,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
timer: Option<Pin<Box<Sleep>>>,
|
||||
) -> Self {
|
||||
let ping_pong = config.keep_alive().map(|dur| H2PingPong {
|
||||
timer: timer
|
||||
.map(|mut timer| {
|
||||
// reset timer if it's received from new function.
|
||||
timer.as_mut().reset(config.now() + dur);
|
||||
timer
|
||||
})
|
||||
.unwrap_or_else(|| Box::pin(sleep(dur))),
|
||||
let ping_pong = config.keep_alive_timer().map(|timer| H2PingPong {
|
||||
timer: Box::pin(timer),
|
||||
on_flight: false,
|
||||
ping_pong: conn.ping_pong().unwrap(),
|
||||
ping_pong: connection.ping_pong().unwrap(),
|
||||
});
|
||||
|
||||
Self {
|
||||
flow,
|
||||
config,
|
||||
peer_addr,
|
||||
connection: conn,
|
||||
connection,
|
||||
on_connect_data,
|
||||
ping_pong,
|
||||
_phantom: PhantomData,
|
||||
@@ -92,11 +85,12 @@ where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>>,
|
||||
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
type Output = Result<(), crate::error::DispatchError>;
|
||||
|
||||
@@ -131,7 +125,7 @@ where
|
||||
let res = match fut.await {
|
||||
Ok(res) => handle_response(res.into(), tx, config).await,
|
||||
Err(err) => {
|
||||
let res: Response<BoxBody> = err.into();
|
||||
let res: Response<AnyBody> = err.into();
|
||||
handle_response(res, tx, config).await
|
||||
}
|
||||
};
|
||||
@@ -206,6 +200,7 @@ async fn handle_response<B>(
|
||||
) -> Result<(), DispatchError>
|
||||
where
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
let (res, body) = res.replace_body(());
|
||||
|
||||
|
@@ -1,30 +1,20 @@
|
||||
//! HTTP/2 protocol.
|
||||
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_rt::time::Sleep;
|
||||
use bytes::Bytes;
|
||||
use futures_core::{ready, Stream};
|
||||
use h2::{
|
||||
server::{handshake, Connection, Handshake},
|
||||
RecvStream,
|
||||
};
|
||||
use h2::RecvStream;
|
||||
|
||||
mod dispatcher;
|
||||
mod service;
|
||||
|
||||
pub use self::dispatcher::Dispatcher;
|
||||
pub use self::service::H2Service;
|
||||
|
||||
use crate::{
|
||||
config::ServiceConfig,
|
||||
error::{DispatchError, PayloadError},
|
||||
};
|
||||
use crate::error::PayloadError;
|
||||
|
||||
/// HTTP/2 peer stream.
|
||||
pub struct Payload {
|
||||
@@ -60,44 +50,3 @@ impl Stream for Payload {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handshake_with_timeout<T>(
|
||||
io: T,
|
||||
config: &ServiceConfig,
|
||||
) -> HandshakeWithTimeout<T>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
HandshakeWithTimeout {
|
||||
handshake: handshake(io),
|
||||
timer: config.client_timer().map(Box::pin),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct HandshakeWithTimeout<T: AsyncRead + AsyncWrite + Unpin> {
|
||||
handshake: Handshake<T>,
|
||||
timer: Option<Pin<Box<Sleep>>>,
|
||||
}
|
||||
|
||||
impl<T> Future for HandshakeWithTimeout<T>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
type Output = Result<(Connection<T, Bytes>, Option<Pin<Box<Sleep>>>), DispatchError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.get_mut();
|
||||
|
||||
match Pin::new(&mut this.handshake).poll(cx)? {
|
||||
// return the timer on success handshake. It can be re-used for h2 ping-pong.
|
||||
Poll::Ready(conn) => Poll::Ready(Ok((conn, this.timer.take()))),
|
||||
Poll::Pending => match this.timer.as_mut() {
|
||||
Some(timer) => {
|
||||
ready!(timer.as_mut().poll(cx));
|
||||
Poll::Ready(Err(DispatchError::SlowRequestTimeout))
|
||||
}
|
||||
None => Poll::Pending,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
error::Error as StdError,
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
net,
|
||||
@@ -14,18 +15,20 @@ use actix_service::{
|
||||
ServiceFactoryExt as _,
|
||||
};
|
||||
use actix_utils::future::ready;
|
||||
use bytes::Bytes;
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use h2::server::{handshake as h2_handshake, Handshake as H2Handshake};
|
||||
use log::error;
|
||||
|
||||
use crate::{
|
||||
body::{BoxBody, MessageBody},
|
||||
body::{AnyBody, MessageBody},
|
||||
config::ServiceConfig,
|
||||
error::DispatchError,
|
||||
service::HttpFlow,
|
||||
ConnectCallback, OnConnectData, Request, Response,
|
||||
};
|
||||
|
||||
use super::{dispatcher::Dispatcher, handshake_with_timeout, HandshakeWithTimeout};
|
||||
use super::dispatcher::Dispatcher;
|
||||
|
||||
/// `ServiceFactory` implementation for HTTP/2 transport
|
||||
pub struct H2Service<T, S, B> {
|
||||
@@ -38,11 +41,12 @@ pub struct H2Service<T, S, B> {
|
||||
impl<T, S, B> H2Service<T, S, B>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
/// Create new `H2Service` instance with config.
|
||||
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
|
||||
@@ -68,11 +72,12 @@ impl<S, B> H2Service<TcpStream, S, B>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
/// Create plain TCP based service
|
||||
pub fn tcp(
|
||||
@@ -111,11 +116,12 @@ mod openssl {
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
/// Create OpenSSL based service.
|
||||
pub fn openssl(
|
||||
@@ -158,11 +164,12 @@ mod rustls {
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
/// Create Rustls based service.
|
||||
pub fn rustls(
|
||||
@@ -199,11 +206,12 @@ where
|
||||
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
type Response = ();
|
||||
type Error = DispatchError;
|
||||
@@ -238,7 +246,7 @@ where
|
||||
impl<T, S, B> H2ServiceHandler<T, S, B>
|
||||
where
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
B: MessageBody + 'static,
|
||||
@@ -261,10 +269,11 @@ impl<T, S, B> Service<(T, Option<net::SocketAddr>)> for H2ServiceHandler<T, S, B
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
type Response = ();
|
||||
type Error = DispatchError;
|
||||
@@ -288,7 +297,7 @@ where
|
||||
Some(self.cfg.clone()),
|
||||
addr,
|
||||
on_connect_data,
|
||||
handshake_with_timeout(io, &self.cfg),
|
||||
h2_handshake(io),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -305,7 +314,7 @@ where
|
||||
Option<ServiceConfig>,
|
||||
Option<net::SocketAddr>,
|
||||
OnConnectData,
|
||||
HandshakeWithTimeout<T>,
|
||||
H2Handshake<T, Bytes>,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -313,7 +322,7 @@ pub struct H2ServiceHandlerResponse<T, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
B: MessageBody + 'static,
|
||||
@@ -325,10 +334,11 @@ impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
type Output = Result<(), DispatchError>;
|
||||
|
||||
@@ -342,7 +352,7 @@ where
|
||||
ref mut on_connect_data,
|
||||
ref mut handshake,
|
||||
) => match ready!(Pin::new(handshake).poll(cx)) {
|
||||
Ok((conn, timer)) => {
|
||||
Ok(conn) => {
|
||||
let on_connect_data = std::mem::take(on_connect_data);
|
||||
self.state = State::Incoming(Dispatcher::new(
|
||||
srv.take().unwrap(),
|
||||
@@ -350,13 +360,12 @@ where
|
||||
on_connect_data,
|
||||
config.take().unwrap(),
|
||||
*peer_addr,
|
||||
timer,
|
||||
));
|
||||
self.poll(cx)
|
||||
}
|
||||
Err(err) => {
|
||||
trace!("H2 handshake error: {}", err);
|
||||
Poll::Ready(Err(err))
|
||||
Poll::Ready(Err(err.into()))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@ use crate::header::AsHeaderName;
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
///
|
||||
/// let mut map = HeaderMap::new();
|
||||
///
|
||||
@@ -75,7 +75,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::HeaderMap;
|
||||
/// # use actix_http::http::HeaderMap;
|
||||
/// let map = HeaderMap::new();
|
||||
///
|
||||
/// assert!(map.is_empty());
|
||||
@@ -92,7 +92,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::HeaderMap;
|
||||
/// # use actix_http::http::HeaderMap;
|
||||
/// let map = HeaderMap::with_capacity(16);
|
||||
///
|
||||
/// assert!(map.is_empty());
|
||||
@@ -139,7 +139,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
/// assert_eq!(map.len(), 0);
|
||||
///
|
||||
@@ -162,7 +162,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
/// assert_eq!(map.len_keys(), 0);
|
||||
///
|
||||
@@ -181,7 +181,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
/// assert!(map.is_empty());
|
||||
///
|
||||
@@ -198,7 +198,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
///
|
||||
/// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain"));
|
||||
@@ -231,7 +231,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
///
|
||||
/// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1"));
|
||||
@@ -264,7 +264,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
///
|
||||
/// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1"));
|
||||
@@ -293,7 +293,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
///
|
||||
/// let mut none_iter = map.get_all(header::ORIGIN);
|
||||
@@ -319,7 +319,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
/// assert!(!map.contains_key(header::ACCEPT));
|
||||
///
|
||||
@@ -342,7 +342,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
///
|
||||
/// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain"));
|
||||
@@ -359,7 +359,7 @@ impl HeaderMap {
|
||||
/// A convenience method is provided on the returned iterator to check if the insertion replaced
|
||||
/// any values.
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
///
|
||||
/// let removed = map.insert(header::ACCEPT, HeaderValue::from_static("text/plain"));
|
||||
@@ -381,7 +381,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
///
|
||||
/// map.append(header::HOST, HeaderValue::from_static("example.com"));
|
||||
@@ -411,7 +411,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
///
|
||||
/// map.append(header::SET_COOKIE, HeaderValue::from_static("one=1"));
|
||||
@@ -430,7 +430,7 @@ impl HeaderMap {
|
||||
/// A convenience method is provided on the returned iterator to check if the `remove` call
|
||||
/// actually removed any values.
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
///
|
||||
/// let removed = map.remove("accept");
|
||||
@@ -459,7 +459,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::HeaderMap;
|
||||
/// # use actix_http::http::HeaderMap;
|
||||
/// let map = HeaderMap::with_capacity(16);
|
||||
///
|
||||
/// assert!(map.is_empty());
|
||||
@@ -479,7 +479,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::HeaderMap;
|
||||
/// # use actix_http::http::HeaderMap;
|
||||
/// let mut map = HeaderMap::with_capacity(2);
|
||||
/// assert!(map.capacity() >= 2);
|
||||
///
|
||||
@@ -499,7 +499,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
///
|
||||
/// let mut iter = map.iter();
|
||||
@@ -531,7 +531,7 @@ impl HeaderMap {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
///
|
||||
/// let mut iter = map.keys();
|
||||
@@ -559,7 +559,7 @@ impl HeaderMap {
|
||||
/// Keeps the allocated memory for reuse.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
|
||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||
/// let mut map = HeaderMap::new();
|
||||
///
|
||||
/// let mut iter = map.drain();
|
||||
|
@@ -38,14 +38,13 @@ pub mod map;
|
||||
mod shared;
|
||||
mod utils;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use self::shared::*;
|
||||
|
||||
pub use self::as_name::AsHeaderName;
|
||||
pub use self::into_pair::IntoHeaderPair;
|
||||
pub use self::into_value::IntoHeaderValue;
|
||||
pub use self::map::HeaderMap;
|
||||
pub use self::shared::{
|
||||
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate,
|
||||
LanguageTag, Quality, QualityItem,
|
||||
};
|
||||
pub use self::utils::{
|
||||
fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode,
|
||||
};
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//! Originally taken from `hyper::header::parsing`.
|
||||
// Originally from hyper v0.11.27 src/header/parsing.rs
|
||||
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
|
@@ -4,13 +4,11 @@ mod charset;
|
||||
mod content_encoding;
|
||||
mod extended;
|
||||
mod http_date;
|
||||
mod quality;
|
||||
mod quality_item;
|
||||
|
||||
pub use self::charset::Charset;
|
||||
pub use self::content_encoding::ContentEncoding;
|
||||
pub use self::extended::{parse_extended_value, ExtendedValue};
|
||||
pub use self::http_date::HttpDate;
|
||||
pub use self::quality::{q, Quality};
|
||||
pub use self::quality_item::QualityItem;
|
||||
pub use self::quality_item::{q, qitem, Quality, QualityItem};
|
||||
pub use language_tags::LanguageTag;
|
||||
|
@@ -1,208 +0,0 @@
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
fmt,
|
||||
};
|
||||
|
||||
use derive_more::{Display, Error};
|
||||
|
||||
const MAX_QUALITY_INT: u16 = 1000;
|
||||
const MAX_QUALITY_FLOAT: f32 = 1.0;
|
||||
|
||||
/// Represents a quality used in q-factor values.
|
||||
///
|
||||
/// The default value is equivalent to `q=1.0` (the [max](Self::MAX) value).
|
||||
///
|
||||
/// # Implementation notes
|
||||
/// The quality value is defined as a number between 0.0 and 1.0 with three decimal places.
|
||||
/// This means there are 1001 possible values. Since floating point numbers are not exact and the
|
||||
/// smallest floating point data type (`f32`) consumes four bytes, we use an `u16` value to store
|
||||
/// the quality internally.
|
||||
///
|
||||
/// [RFC 7231 §5.3.1] gives more information on quality values in HTTP header fields.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_http::header::{Quality, q};
|
||||
/// assert_eq!(q(1.0), Quality::MAX);
|
||||
///
|
||||
/// assert_eq!(q(0.42).to_string(), "0.42");
|
||||
/// assert_eq!(q(1.0).to_string(), "1");
|
||||
/// assert_eq!(Quality::MIN.to_string(), "0");
|
||||
/// ```
|
||||
///
|
||||
/// [RFC 7231 §5.3.1]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Quality(pub(super) u16);
|
||||
|
||||
impl Quality {
|
||||
/// The maximum quality value, equivalent to `q=1.0`.
|
||||
pub const MAX: Quality = Quality(MAX_QUALITY_INT);
|
||||
|
||||
/// The minimum quality value, equivalent to `q=0.0`.
|
||||
pub const MIN: Quality = Quality(0);
|
||||
|
||||
/// Converts a float in the range 0.0–1.0 to a `Quality`.
|
||||
///
|
||||
/// Intentionally private. External uses should rely on the `TryFrom` impl.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics in debug mode when value is not in the range 0.0 <= n <= 1.0.
|
||||
fn from_f32(value: f32) -> Self {
|
||||
// Check that `value` is within range should be done before calling this method.
|
||||
// Just in case, this debug_assert should catch if we were forgetful.
|
||||
debug_assert!(
|
||||
(0.0f32..=1.0f32).contains(&value),
|
||||
"q value must be between 0.0 and 1.0"
|
||||
);
|
||||
|
||||
Quality((value * MAX_QUALITY_INT as f32) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// The default value is [`Quality::MAX`].
|
||||
impl Default for Quality {
|
||||
fn default() -> Quality {
|
||||
Quality::MAX
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Quality {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.0 {
|
||||
0 => f.write_str("0"),
|
||||
MAX_QUALITY_INT => f.write_str("1"),
|
||||
|
||||
// some number in the range 1–999
|
||||
x => {
|
||||
f.write_str("0.")?;
|
||||
|
||||
// This implementation avoids string allocation for removing trailing zeroes.
|
||||
// In benchmarks it is twice as fast as approach using something like
|
||||
// `format!("{}").trim_end_matches('0')` for non-fast-path quality values.
|
||||
|
||||
if x < 10 {
|
||||
// x in is range 1–9
|
||||
|
||||
f.write_str("00")?;
|
||||
|
||||
// 0 is already handled so it's not possible to have a trailing 0 in this range
|
||||
// we can just write the integer
|
||||
itoa::fmt(f, x)
|
||||
} else if x < 100 {
|
||||
// x in is range 10–99
|
||||
|
||||
f.write_str("0")?;
|
||||
|
||||
if x % 10 == 0 {
|
||||
// trailing 0, divide by 10 and write
|
||||
itoa::fmt(f, x / 10)
|
||||
} else {
|
||||
itoa::fmt(f, x)
|
||||
}
|
||||
} else {
|
||||
// x is in range 100–999
|
||||
|
||||
if x % 100 == 0 {
|
||||
// two trailing 0s, divide by 100 and write
|
||||
itoa::fmt(f, x / 100)
|
||||
} else if x % 10 == 0 {
|
||||
// one trailing 0, divide by 10 and write
|
||||
itoa::fmt(f, x / 10)
|
||||
} else {
|
||||
itoa::fmt(f, x)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Display, Error)]
|
||||
#[display(fmt = "quality out of bounds")]
|
||||
#[non_exhaustive]
|
||||
pub struct QualityOutOfBounds;
|
||||
|
||||
impl TryFrom<f32> for Quality {
|
||||
type Error = QualityOutOfBounds;
|
||||
|
||||
#[inline]
|
||||
fn try_from(value: f32) -> Result<Self, Self::Error> {
|
||||
if (0.0..=MAX_QUALITY_FLOAT).contains(&value) {
|
||||
Ok(Quality::from_f32(value))
|
||||
} else {
|
||||
Err(QualityOutOfBounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience function to create a [`Quality`] from an `f32` (0.0–1.0).
|
||||
///
|
||||
/// Not recommended for use with user input. Rely on the `TryFrom` impls where possible.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if value is out of range.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{q, Quality};
|
||||
/// let q1 = q(1.0);
|
||||
/// assert_eq!(q1, Quality::MAX);
|
||||
///
|
||||
/// let q2 = q(0.0);
|
||||
/// assert_eq!(q2, Quality::MIN);
|
||||
///
|
||||
/// let q3 = q(0.42);
|
||||
/// ```
|
||||
///
|
||||
/// An out-of-range `f32` quality will panic.
|
||||
/// ```should_panic
|
||||
/// # use actix_http::header::q;
|
||||
/// let _q2 = q(1.42);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn q<T>(quality: T) -> Quality
|
||||
where
|
||||
T: TryInto<Quality>,
|
||||
T::Error: fmt::Debug,
|
||||
{
|
||||
quality.try_into().expect("quality value was out of bounds")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn q_helper() {
|
||||
assert_eq!(q(0.5), Quality(500));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_output() {
|
||||
assert_eq!(q(0.0).to_string(), "0");
|
||||
assert_eq!(q(1.0).to_string(), "1");
|
||||
assert_eq!(q(0.001).to_string(), "0.001");
|
||||
assert_eq!(q(0.5).to_string(), "0.5");
|
||||
assert_eq!(q(0.22).to_string(), "0.22");
|
||||
assert_eq!(q(0.123).to_string(), "0.123");
|
||||
assert_eq!(q(0.999).to_string(), "0.999");
|
||||
|
||||
for x in 0..=1000 {
|
||||
// if trailing zeroes are handled correctly, we would not expect the serialized length
|
||||
// to ever exceed "0." + 3 decimal places = 5 in length
|
||||
assert!(q(x as f32 / 1000.0).to_string().len() <= 5);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn negative_quality() {
|
||||
q(-1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn quality_out_of_bounds() {
|
||||
q(2.0);
|
||||
}
|
||||
}
|
@@ -1,36 +1,85 @@
|
||||
use std::{cmp, convert::TryFrom as _, fmt, str};
|
||||
use std::{
|
||||
cmp,
|
||||
convert::{TryFrom, TryInto},
|
||||
fmt, str,
|
||||
};
|
||||
|
||||
use derive_more::{Display, Error};
|
||||
|
||||
use crate::error::ParseError;
|
||||
|
||||
use super::Quality;
|
||||
const MAX_QUALITY: u16 = 1000;
|
||||
const MAX_FLOAT_QUALITY: f32 = 1.0;
|
||||
|
||||
/// Represents a quality used in quality values.
|
||||
///
|
||||
/// Can be created with the [`q`] function.
|
||||
///
|
||||
/// # Implementation notes
|
||||
///
|
||||
/// The quality value is defined as a number between 0 and 1 with three decimal
|
||||
/// places. This means there are 1001 possible values. Since floating point
|
||||
/// numbers are not exact and the smallest floating point data type (`f32`)
|
||||
/// consumes four bytes, hyper uses an `u16` value to store the
|
||||
/// quality internally. For performance reasons you may set quality directly to
|
||||
/// a value between 0 and 1000 e.g. `Quality(532)` matches the quality
|
||||
/// `q=0.532`.
|
||||
///
|
||||
/// [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1) gives more
|
||||
/// information on quality values in HTTP header fields.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Quality(u16);
|
||||
|
||||
impl Quality {
|
||||
/// # Panics
|
||||
/// Panics in debug mode when value is not in the range 0.0 <= n <= 1.0.
|
||||
fn from_f32(value: f32) -> Self {
|
||||
// Check that `value` is within range should be done before calling this method.
|
||||
// Just in case, this debug_assert should catch if we were forgetful.
|
||||
debug_assert!(
|
||||
(0.0f32..=1.0f32).contains(&value),
|
||||
"q value must be between 0.0 and 1.0"
|
||||
);
|
||||
|
||||
Quality((value * MAX_QUALITY as f32) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Quality {
|
||||
fn default() -> Quality {
|
||||
Quality(MAX_QUALITY)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Display, Error)]
|
||||
pub struct QualityOutOfBounds;
|
||||
|
||||
impl TryFrom<u16> for Quality {
|
||||
type Error = QualityOutOfBounds;
|
||||
|
||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||
if (0..=MAX_QUALITY).contains(&value) {
|
||||
Ok(Quality(value))
|
||||
} else {
|
||||
Err(QualityOutOfBounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<f32> for Quality {
|
||||
type Error = QualityOutOfBounds;
|
||||
|
||||
fn try_from(value: f32) -> Result<Self, Self::Error> {
|
||||
if (0.0..=MAX_FLOAT_QUALITY).contains(&value) {
|
||||
Ok(Quality::from_f32(value))
|
||||
} else {
|
||||
Err(QualityOutOfBounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an item with a quality value as defined
|
||||
/// in [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1).
|
||||
///
|
||||
/// # Parsing and Formatting
|
||||
/// This wrapper be used to parse header value items that have a q-factor annotation as well as
|
||||
/// serialize items with a their q-factor.
|
||||
///
|
||||
/// # Ordering
|
||||
/// Since this context of use for this type is header value items, ordering is defined for
|
||||
/// `QualityItem`s but _only_ considers the item's quality. Order of appearance should be used as
|
||||
/// the secondary sorting parameter; i.e., a stable sort over the quality values will produce a
|
||||
/// correctly sorted sequence.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::header::{QualityItem, q};
|
||||
/// let q_item: QualityItem<String> = "hello;q=0.3".parse().unwrap();
|
||||
/// assert_eq!(&q_item.item, "hello");
|
||||
/// assert_eq!(q_item.quality, q(0.3));
|
||||
///
|
||||
/// // note that format is normalized compared to parsed item
|
||||
/// assert_eq!(q_item.to_string(), "hello; q=0.3");
|
||||
///
|
||||
/// // item with q=0.3 is greater than item with q=0.1
|
||||
/// let q_item_fallback: QualityItem<String> = "abc;q=0.1".parse().unwrap();
|
||||
/// assert!(q_item > q_item_fallback);
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct QualityItem<T> {
|
||||
/// The wrapped contents of the field.
|
||||
@@ -44,22 +93,12 @@ impl<T> QualityItem<T> {
|
||||
/// Constructs a new `QualityItem` from an item and a quality value.
|
||||
///
|
||||
/// The item can be of any type. The quality should be a value in the range [0, 1].
|
||||
pub fn new(item: T, quality: Quality) -> Self {
|
||||
pub fn new(item: T, quality: Quality) -> QualityItem<T> {
|
||||
QualityItem { item, quality }
|
||||
}
|
||||
|
||||
/// Constructs a new `QualityItem` from an item, using the maximum q-value.
|
||||
pub fn max(item: T) -> Self {
|
||||
Self::new(item, Quality::MAX)
|
||||
}
|
||||
|
||||
/// Constructs a new `QualityItem` from an item, using the minimum q-value.
|
||||
pub fn min(item: T) -> Self {
|
||||
Self::new(item, Quality::MIN)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> PartialOrd for QualityItem<T> {
|
||||
impl<T: PartialEq> cmp::PartialOrd for QualityItem<T> {
|
||||
fn partial_cmp(&self, other: &QualityItem<T>) -> Option<cmp::Ordering> {
|
||||
self.quality.partial_cmp(&other.quality)
|
||||
}
|
||||
@@ -69,12 +108,10 @@ impl<T: fmt::Display> fmt::Display for QualityItem<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.item, f)?;
|
||||
|
||||
match self.quality {
|
||||
// q-factor value is implied for max value
|
||||
Quality::MAX => Ok(()),
|
||||
|
||||
Quality::MIN => f.write_str("; q=0"),
|
||||
q => write!(f, "; q={}", q),
|
||||
match self.quality.0 {
|
||||
MAX_QUALITY => Ok(()),
|
||||
0 => f.write_str("; q=0"),
|
||||
x => write!(f, "; q=0.{}", format!("{:03}", x).trim_end_matches('0')),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,58 +119,78 @@ impl<T: fmt::Display> fmt::Display for QualityItem<T> {
|
||||
impl<T: str::FromStr> str::FromStr for QualityItem<T> {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(q_item_str: &str) -> Result<Self, Self::Err> {
|
||||
if !q_item_str.is_ascii() {
|
||||
fn from_str(qitem_str: &str) -> Result<Self, Self::Err> {
|
||||
if !qitem_str.is_ascii() {
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
|
||||
// set defaults used if quality-item parsing fails, i.e., item has no q attribute
|
||||
let mut raw_item = q_item_str;
|
||||
let mut quality = Quality::MAX;
|
||||
// Set defaults used if parsing fails.
|
||||
let mut raw_item = qitem_str;
|
||||
let mut quality = 1f32;
|
||||
|
||||
let parts = q_item_str
|
||||
.rsplit_once(';')
|
||||
.map(|(item, q_attr)| (item.trim(), q_attr.trim()));
|
||||
// TODO: MSRV(1.52): use rsplit_once
|
||||
let parts: Vec<_> = qitem_str.rsplitn(2, ';').map(str::trim).collect();
|
||||
|
||||
if let Some((val, q_attr)) = parts {
|
||||
if parts.len() == 2 {
|
||||
// example for item with q-factor:
|
||||
//
|
||||
// gzip;q=0.65
|
||||
// ^^^^ val
|
||||
// ^^^^^^ q_attr
|
||||
// ^^ q
|
||||
// ^^^^ q_val
|
||||
// gzip; q=0.65
|
||||
// ^^^^^^ parts[0]
|
||||
// ^^ start
|
||||
// ^^^^ q_val
|
||||
// ^^^^ parts[1]
|
||||
|
||||
if q_attr.len() < 2 {
|
||||
if parts[0].len() < 2 {
|
||||
// Can't possibly be an attribute since an attribute needs at least a name followed
|
||||
// by an equals sign. And bare identifiers are forbidden.
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
|
||||
let q = &q_attr[0..2];
|
||||
let start = &parts[0][0..2];
|
||||
|
||||
if q == "q=" || q == "Q=" {
|
||||
let q_val = &q_attr[2..];
|
||||
if start == "q=" || start == "Q=" {
|
||||
let q_val = &parts[0][2..];
|
||||
if q_val.len() > 5 {
|
||||
// longer than 5 indicates an over-precise q-factor
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
|
||||
let q_value = q_val.parse::<f32>().map_err(|_| ParseError::Header)?;
|
||||
let q_value =
|
||||
Quality::try_from(q_value).map_err(|_| ParseError::Header)?;
|
||||
|
||||
quality = q_value;
|
||||
raw_item = val;
|
||||
if (0f32..=1f32).contains(&q_value) {
|
||||
quality = q_value;
|
||||
raw_item = parts[1];
|
||||
} else {
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let item = raw_item.parse::<T>().map_err(|_| ParseError::Header)?;
|
||||
|
||||
Ok(QualityItem::new(item, quality))
|
||||
// we already checked above that the quality is within range
|
||||
Ok(QualityItem::new(item, Quality::from_f32(quality)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience function to wrap a value in a `QualityItem`
|
||||
/// Sets `q` to the default 1.0
|
||||
pub fn qitem<T>(item: T) -> QualityItem<T> {
|
||||
QualityItem::new(item, Quality::default())
|
||||
}
|
||||
|
||||
/// Convenience function to create a `Quality` from a float or integer.
|
||||
///
|
||||
/// Implemented for `u16` and `f32`. Panics if value is out of range.
|
||||
pub fn q<T>(val: T) -> Quality
|
||||
where
|
||||
T: TryInto<Quality>,
|
||||
T::Error: fmt::Debug,
|
||||
{
|
||||
// TODO: on next breaking change, handle unwrap differently
|
||||
val.try_into().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -188,7 +245,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_quality_item_fmt_q_1() {
|
||||
use Encoding::*;
|
||||
let x = QualityItem::max(Chunked);
|
||||
let x = qitem(Chunked);
|
||||
assert_eq!(format!("{}", x), "chunked");
|
||||
}
|
||||
#[test]
|
||||
@@ -287,8 +344,25 @@ mod tests {
|
||||
fn test_quality_item_ordering() {
|
||||
let x: QualityItem<Encoding> = "gzip; q=0.5".parse().ok().unwrap();
|
||||
let y: QualityItem<Encoding> = "gzip; q=0.273".parse().ok().unwrap();
|
||||
let comparison_result: bool = x.gt(&y);
|
||||
assert!(comparison_result)
|
||||
let comparision_result: bool = x.gt(&y);
|
||||
assert!(comparision_result)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quality() {
|
||||
assert_eq!(q(0.5), Quality(500));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_quality_invalid() {
|
||||
q(-1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_quality_invalid2() {
|
||||
q(2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@@ -65,9 +65,8 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Percent encode a sequence of bytes with a character set defined in [RFC 5987 §3.2].
|
||||
///
|
||||
/// [RFC 5987 §3.2]: https://datatracker.ietf.org/doc/html/rfc5987#section-3.2
|
||||
/// Percent encode a sequence of bytes with a character set defined in
|
||||
/// <https://datatracker.ietf.org/doc/html/rfc5987#section-3.2>
|
||||
#[inline]
|
||||
pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
|
||||
let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
|
||||
@@ -84,13 +83,6 @@ mod tests {
|
||||
let res: Vec<usize> = from_comma_delimited(headers.iter()).unwrap();
|
||||
assert_eq!(res, vec![0; 0]);
|
||||
|
||||
let headers = vec![
|
||||
HeaderValue::from_static("1, 2"),
|
||||
HeaderValue::from_static("3,4"),
|
||||
];
|
||||
let res: Vec<usize> = from_comma_delimited(headers.iter()).unwrap();
|
||||
assert_eq!(res, vec![1, 2, 3, 4]);
|
||||
|
||||
let headers = vec![
|
||||
HeaderValue::from_static(""),
|
||||
HeaderValue::from_static(","),
|
||||
|
@@ -53,7 +53,7 @@ pub mod ws;
|
||||
pub use self::builder::HttpServiceBuilder;
|
||||
pub use self::config::{KeepAlive, ServiceConfig};
|
||||
pub use self::error::Error;
|
||||
pub use self::extensions::{CloneableExtensions, Extensions};
|
||||
pub use self::extensions::Extensions;
|
||||
pub use self::header::ContentEncoding;
|
||||
pub use self::http_message::HttpMessage;
|
||||
pub use self::message::ConnectionType;
|
||||
@@ -67,6 +67,26 @@ pub use self::service::HttpService;
|
||||
pub use ::http::{uri, uri::Uri};
|
||||
pub use ::http::{Method, StatusCode, Version};
|
||||
|
||||
// TODO: deprecate this mish-mash of random items
|
||||
pub mod http {
|
||||
//! Various HTTP related types.
|
||||
|
||||
// re-exports
|
||||
pub use http::header::{HeaderName, HeaderValue};
|
||||
pub use http::uri::PathAndQuery;
|
||||
pub use http::{uri, Error, Uri};
|
||||
pub use http::{Method, StatusCode, Version};
|
||||
|
||||
pub use crate::header::HeaderMap;
|
||||
|
||||
/// A collection of HTTP headers and helpers.
|
||||
pub mod header {
|
||||
pub use crate::header::*;
|
||||
}
|
||||
pub use crate::header::ContentEncoding;
|
||||
pub use crate::message::ConnectionType;
|
||||
}
|
||||
|
||||
/// A major HTTP protocol version.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[non_exhaustive]
|
||||
@@ -76,14 +96,14 @@ pub enum Protocol {
|
||||
Http3,
|
||||
}
|
||||
|
||||
type ConnectCallback<IO> = dyn Fn(&IO, &mut CloneableExtensions);
|
||||
type ConnectCallback<IO> = dyn Fn(&IO, &mut Extensions);
|
||||
|
||||
/// Container for data that extract with ConnectCallback.
|
||||
///
|
||||
/// # Implementation Details
|
||||
/// Uses Option to reduce necessary allocations when merging with request extensions.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct OnConnectData(Option<CloneableExtensions>);
|
||||
pub(crate) struct OnConnectData(Option<Extensions>);
|
||||
|
||||
impl OnConnectData {
|
||||
/// Construct by calling the on-connect callback with the underlying transport I/O.
|
||||
@@ -92,7 +112,7 @@ impl OnConnectData {
|
||||
on_connect_ext: Option<&ConnectCallback<T>>,
|
||||
) -> Self {
|
||||
let ext = on_connect_ext.map(|handler| {
|
||||
let mut extensions = CloneableExtensions::default();
|
||||
let mut extensions = Extensions::new();
|
||||
handler(io, &mut extensions);
|
||||
extensions
|
||||
});
|
||||
@@ -103,8 +123,8 @@ impl OnConnectData {
|
||||
/// Merge self into given request's extensions.
|
||||
#[inline]
|
||||
pub(crate) fn merge_into(&mut self, req: &mut Request) {
|
||||
if let Some(ref ext) = self.0 {
|
||||
req.head.extensions.get_mut().clone_from(ext);
|
||||
if let Some(ref mut ext) = self.0 {
|
||||
req.head.extensions.get_mut().drain_from(ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -46,8 +46,8 @@ pub trait Head: Default + 'static {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RequestHead {
|
||||
pub method: Method,
|
||||
pub uri: Uri,
|
||||
pub method: Method,
|
||||
pub version: Version,
|
||||
pub headers: HeaderMap,
|
||||
pub extensions: RefCell<Extensions>,
|
||||
@@ -58,13 +58,13 @@ pub struct RequestHead {
|
||||
impl Default for RequestHead {
|
||||
fn default() -> RequestHead {
|
||||
RequestHead {
|
||||
method: Method::default(),
|
||||
uri: Uri::default(),
|
||||
method: Method::default(),
|
||||
version: Version::HTTP_11,
|
||||
headers: HeaderMap::with_capacity(16),
|
||||
extensions: RefCell::new(Extensions::new()),
|
||||
peer_addr: None,
|
||||
flags: Flags::empty(),
|
||||
peer_addr: None,
|
||||
extensions: RefCell::new(Extensions::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,7 +192,6 @@ impl RequestHead {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum RequestHeadType {
|
||||
Owned(RequestHead),
|
||||
Rc(Rc<RequestHead>, Option<HeaderMap>),
|
||||
|
@@ -6,14 +6,14 @@ use std::{
|
||||
};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use bytestring::ByteString;
|
||||
|
||||
use crate::{
|
||||
body::{BoxBody, MessageBody},
|
||||
body::{AnyBody, MessageBody},
|
||||
error::Error,
|
||||
extensions::Extensions,
|
||||
header::{self, HeaderMap, IntoHeaderValue},
|
||||
http::{HeaderMap, StatusCode},
|
||||
message::{BoxedResponseHead, ResponseHead},
|
||||
Error, ResponseBuilder, StatusCode,
|
||||
ResponseBuilder,
|
||||
};
|
||||
|
||||
/// An HTTP response.
|
||||
@@ -22,13 +22,13 @@ pub struct Response<B> {
|
||||
pub(crate) body: B,
|
||||
}
|
||||
|
||||
impl Response<BoxBody> {
|
||||
impl Response<AnyBody> {
|
||||
/// Constructs a new response with default body.
|
||||
#[inline]
|
||||
pub fn new(status: StatusCode) -> Self {
|
||||
Response {
|
||||
head: BoxedResponseHead::new(status),
|
||||
body: BoxBody::new(()),
|
||||
body: AnyBody::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,14 +189,6 @@ impl<B> Response<B> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn map_into_boxed_body(self) -> Response<BoxBody>
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
self.map_body(|_, body| BoxBody::new(body))
|
||||
}
|
||||
|
||||
/// Returns body, consuming this response.
|
||||
pub fn into_body(self) -> B {
|
||||
self.body
|
||||
@@ -231,99 +223,81 @@ impl<B: Default> Default for Response<B> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Into<Response<BoxBody>>, E: Into<Error>> From<Result<I, E>>
|
||||
for Response<BoxBody>
|
||||
impl<I: Into<Response<AnyBody>>, E: Into<Error>> From<Result<I, E>>
|
||||
for Response<AnyBody>
|
||||
{
|
||||
fn from(res: Result<I, E>) -> Self {
|
||||
match res {
|
||||
Ok(val) => val.into(),
|
||||
Err(err) => Response::from(err.into()),
|
||||
Err(err) => err.into().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResponseBuilder> for Response<BoxBody> {
|
||||
impl From<ResponseBuilder> for Response<AnyBody> {
|
||||
fn from(mut builder: ResponseBuilder) -> Self {
|
||||
builder.finish().map_into_boxed_body()
|
||||
builder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::convert::Infallible> for Response<BoxBody> {
|
||||
impl From<std::convert::Infallible> for Response<AnyBody> {
|
||||
fn from(val: std::convert::Infallible) -> Self {
|
||||
match val {}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for Response<&'static str> {
|
||||
impl From<&'static str> for Response<AnyBody> {
|
||||
fn from(val: &'static str) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val);
|
||||
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res
|
||||
Response::build(StatusCode::OK)
|
||||
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||
.body(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static [u8]> for Response<&'static [u8]> {
|
||||
impl From<&'static [u8]> for Response<AnyBody> {
|
||||
fn from(val: &'static [u8]) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val);
|
||||
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res
|
||||
Response::build(StatusCode::OK)
|
||||
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||
.body(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Response<String> {
|
||||
impl From<String> for Response<AnyBody> {
|
||||
fn from(val: String) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val);
|
||||
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res
|
||||
Response::build(StatusCode::OK)
|
||||
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||
.body(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&String> for Response<String> {
|
||||
fn from(val: &String) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val.clone());
|
||||
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res
|
||||
impl<'a> From<&'a String> for Response<AnyBody> {
|
||||
fn from(val: &'a String) -> Self {
|
||||
Response::build(StatusCode::OK)
|
||||
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||
.body(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bytes> for Response<Bytes> {
|
||||
impl From<Bytes> for Response<AnyBody> {
|
||||
fn from(val: Bytes) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val);
|
||||
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res
|
||||
Response::build(StatusCode::OK)
|
||||
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||
.body(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BytesMut> for Response<BytesMut> {
|
||||
impl From<BytesMut> for Response<AnyBody> {
|
||||
fn from(val: BytesMut) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val);
|
||||
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ByteString> for Response<ByteString> {
|
||||
fn from(val: ByteString) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val);
|
||||
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res
|
||||
Response::build(StatusCode::OK)
|
||||
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||
.body(val)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
body::to_bytes,
|
||||
header::{HeaderValue, CONTENT_TYPE, COOKIE},
|
||||
};
|
||||
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE};
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
@@ -335,73 +309,73 @@ mod tests {
|
||||
assert!(dbg.contains("Response"));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_into_response() {
|
||||
let res = Response::from("test");
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
#[test]
|
||||
fn test_into_response() {
|
||||
let resp: Response<AnyBody> = "test".into();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().get_ref(), b"test");
|
||||
|
||||
let res = Response::from(b"test".as_ref());
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let resp: Response<AnyBody> = b"test".as_ref().into();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().get_ref(), b"test");
|
||||
|
||||
let res = Response::from("test".to_owned());
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let resp: Response<AnyBody> = "test".to_owned().into();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().get_ref(), b"test");
|
||||
|
||||
let res = Response::from("test".to_owned());
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let resp: Response<AnyBody> = (&"test".to_owned()).into();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().get_ref(), b"test");
|
||||
|
||||
let b = Bytes::from_static(b"test");
|
||||
let res = Response::from(b);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let resp: Response<AnyBody> = b.into();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().get_ref(), b"test");
|
||||
|
||||
let b = Bytes::from_static(b"test");
|
||||
let res = Response::from(b);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let resp: Response<AnyBody> = b.into();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().get_ref(), b"test");
|
||||
|
||||
let b = BytesMut::from("test");
|
||||
let res = Response::from(b);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let resp: Response<AnyBody> = b.into();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().get_ref(), b"test");
|
||||
}
|
||||
}
|
||||
|
@@ -2,11 +2,19 @@
|
||||
|
||||
use std::{
|
||||
cell::{Ref, RefMut},
|
||||
fmt, str,
|
||||
error::Error as StdError,
|
||||
fmt,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
str,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures_core::Stream;
|
||||
|
||||
use crate::{
|
||||
body::{EitherBody, MessageBody},
|
||||
body::{AnyBody, BodyStream},
|
||||
error::{Error, HttpError},
|
||||
header::{self, IntoHeaderPair, IntoHeaderValue},
|
||||
message::{BoxedResponseHead, ConnectionType, ResponseHead},
|
||||
@@ -20,7 +28,7 @@ use crate::{
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_http::{Response, ResponseBuilder, StatusCode, body, header};
|
||||
/// use actix_http::{Response, ResponseBuilder, body, http::StatusCode, http::header};
|
||||
///
|
||||
/// # actix_rt::System::new().block_on(async {
|
||||
/// let mut res: Response<_> = Response::build(StatusCode::OK)
|
||||
@@ -47,7 +55,9 @@ impl ResponseBuilder {
|
||||
/// Create response builder
|
||||
///
|
||||
/// # Examples
|
||||
// /// use actix_http::{Response, ResponseBuilder, StatusCode};, / ``
|
||||
/// ```
|
||||
/// use actix_http::{Response, ResponseBuilder, http::StatusCode};
|
||||
///
|
||||
/// let res: Response<_> = ResponseBuilder::default().finish();
|
||||
/// assert_eq!(res.status(), StatusCode::OK);
|
||||
/// ```
|
||||
@@ -62,7 +72,9 @@ impl ResponseBuilder {
|
||||
/// Set HTTP status code of this response.
|
||||
///
|
||||
/// # Examples
|
||||
// /// use actix_http::{ResponseBuilder, StatusCode};, / ``
|
||||
/// ```
|
||||
/// use actix_http::{ResponseBuilder, http::StatusCode};
|
||||
///
|
||||
/// let res = ResponseBuilder::default().status(StatusCode::NOT_FOUND).finish();
|
||||
/// assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||
/// ```
|
||||
@@ -78,7 +90,7 @@ impl ResponseBuilder {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_http::{ResponseBuilder, header};
|
||||
/// use actix_http::{ResponseBuilder, http::header};
|
||||
///
|
||||
/// let res = ResponseBuilder::default()
|
||||
/// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON))
|
||||
@@ -108,7 +120,7 @@ impl ResponseBuilder {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_http::{ResponseBuilder, header};
|
||||
/// use actix_http::{ResponseBuilder, http::header};
|
||||
///
|
||||
/// let res = ResponseBuilder::default()
|
||||
/// .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON))
|
||||
@@ -223,14 +235,10 @@ impl ResponseBuilder {
|
||||
/// Generate response with a wrapped body.
|
||||
///
|
||||
/// This `ResponseBuilder` will be left in a useless state.
|
||||
pub fn body<B>(&mut self, body: B) -> Response<EitherBody<B>>
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
match self.message_body(body) {
|
||||
Ok(res) => res.map_body(|_, body| EitherBody::left(body)),
|
||||
Err(err) => Response::from(err).map_body(|_, body| EitherBody::right(body)),
|
||||
}
|
||||
#[inline]
|
||||
pub fn body<B: Into<AnyBody>>(&mut self, body: B) -> Response<AnyBody> {
|
||||
self.message_body(body.into())
|
||||
.unwrap_or_else(Response::from)
|
||||
}
|
||||
|
||||
/// Generate response with a body.
|
||||
@@ -245,12 +253,24 @@ impl ResponseBuilder {
|
||||
Ok(Response { head, body })
|
||||
}
|
||||
|
||||
/// Generate response with a streaming body.
|
||||
///
|
||||
/// This `ResponseBuilder` will be left in a useless state.
|
||||
#[inline]
|
||||
pub fn streaming<S, E>(&mut self, stream: S) -> Response<AnyBody>
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||
E: Into<Box<dyn StdError>> + 'static,
|
||||
{
|
||||
self.body(AnyBody::new_boxed(BodyStream::new(stream)))
|
||||
}
|
||||
|
||||
/// Generate response with an empty body.
|
||||
///
|
||||
/// This `ResponseBuilder` will be left in a useless state.
|
||||
#[inline]
|
||||
pub fn finish(&mut self) -> Response<EitherBody<()>> {
|
||||
self.body(())
|
||||
pub fn finish(&mut self) -> Response<AnyBody> {
|
||||
self.body(AnyBody::empty())
|
||||
}
|
||||
|
||||
/// Create an owned `ResponseBuilder`, leaving the original in a useless state.
|
||||
@@ -307,6 +327,14 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for ResponseBuilder {
|
||||
type Output = Result<Response<AnyBody>, Error>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Poll::Ready(Ok(self.finish()))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ResponseBuilder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let head = self.head.as_ref().unwrap();
|
||||
@@ -328,10 +356,9 @@ impl fmt::Debug for ResponseBuilder {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bytes::Bytes;
|
||||
|
||||
use super::*;
|
||||
use crate::header::{HeaderName, HeaderValue, CONTENT_TYPE};
|
||||
use crate::body::AnyBody;
|
||||
use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
|
||||
|
||||
#[test]
|
||||
fn test_basic_builder() {
|
||||
@@ -356,28 +383,20 @@ mod tests {
|
||||
#[test]
|
||||
fn test_force_close() {
|
||||
let resp = Response::build(StatusCode::OK).force_close().finish();
|
||||
assert!(!resp.keep_alive());
|
||||
assert!(!resp.keep_alive())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_type() {
|
||||
let resp = Response::build(StatusCode::OK)
|
||||
.content_type("text/plain")
|
||||
.body(Bytes::new());
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain");
|
||||
|
||||
let resp = Response::build(StatusCode::OK)
|
||||
.content_type(mime::APPLICATION_JAVASCRIPT_UTF_8)
|
||||
.body(Bytes::new());
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
"application/javascript; charset=utf-8"
|
||||
);
|
||||
.body(AnyBody::empty());
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_builder() {
|
||||
let mut resp: Response<_> = "test".into();
|
||||
let mut resp: Response<AnyBody> = "test".into();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
resp.headers_mut().insert(
|
||||
|
@@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
error::Error as StdError,
|
||||
fmt,
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
@@ -8,16 +9,18 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use ::h2::server::{handshake as h2_handshake, Handshake as H2Handshake};
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_service::{
|
||||
fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use pin_project_lite::pin_project;
|
||||
use pin_project::pin_project;
|
||||
|
||||
use crate::{
|
||||
body::{BoxBody, MessageBody},
|
||||
body::{AnyBody, MessageBody},
|
||||
builder::HttpServiceBuilder,
|
||||
config::{KeepAlive, ServiceConfig},
|
||||
error::DispatchError,
|
||||
@@ -37,7 +40,7 @@ pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler> {
|
||||
impl<T, S, B> HttpService<T, S, B>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
@@ -52,11 +55,12 @@ where
|
||||
impl<T, S, B> HttpService<T, S, B>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
/// Create new `HttpService` instance.
|
||||
pub fn new<F: IntoServiceFactory<S, Request>>(service: F) -> Self {
|
||||
@@ -91,7 +95,7 @@ where
|
||||
impl<T, S, B, X, U> HttpService<T, S, B, X, U>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
@@ -105,7 +109,7 @@ where
|
||||
pub fn expect<X1>(self, expect: X1) -> HttpService<T, S, B, X1, U>
|
||||
where
|
||||
X1: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X1::Error: Into<Response<BoxBody>>,
|
||||
X1::Error: Into<Response<AnyBody>>,
|
||||
X1::InitError: fmt::Debug,
|
||||
{
|
||||
HttpService {
|
||||
@@ -149,16 +153,17 @@ impl<S, B, X, U> HttpService<TcpStream, S, B, X, U>
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X::Future: 'static,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
X::InitError: fmt::Debug,
|
||||
|
||||
U: ServiceFactory<
|
||||
@@ -167,7 +172,7 @@ where
|
||||
Response = (),
|
||||
>,
|
||||
U::Future: 'static,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create simple tcp stream service
|
||||
@@ -205,16 +210,17 @@ mod openssl {
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X::Future: 'static,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
X::InitError: fmt::Debug,
|
||||
|
||||
U: ServiceFactory<
|
||||
@@ -223,7 +229,7 @@ mod openssl {
|
||||
Response = (),
|
||||
>,
|
||||
U::Future: 'static,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create OpenSSL based service.
|
||||
@@ -277,16 +283,17 @@ mod rustls {
|
||||
where
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X::Future: 'static,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
X::InitError: fmt::Debug,
|
||||
|
||||
U: ServiceFactory<
|
||||
@@ -295,7 +302,7 @@ mod rustls {
|
||||
Response = (),
|
||||
>,
|
||||
U::Future: 'static,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create Rustls based service.
|
||||
@@ -343,21 +350,22 @@ where
|
||||
|
||||
S: ServiceFactory<Request, Config = ()>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||
X::Future: 'static,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
X::InitError: fmt::Debug,
|
||||
|
||||
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
|
||||
U::Future: 'static,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
type Response = ();
|
||||
@@ -420,11 +428,11 @@ where
|
||||
impl<T, S, B, X, U> HttpServiceHandler<T, S, B, X, U>
|
||||
where
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
X: Service<Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
U: Service<(Request, Framed<T, h1::Codec>)>,
|
||||
U::Error: Into<Response<BoxBody>>,
|
||||
U::Error: Into<Response<AnyBody>>,
|
||||
{
|
||||
pub(super) fn new(
|
||||
cfg: ServiceConfig,
|
||||
@@ -444,7 +452,7 @@ where
|
||||
pub(super) fn _poll_ready(
|
||||
&self,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), Response<BoxBody>>> {
|
||||
) -> Poll<Result<(), Response<AnyBody>>> {
|
||||
ready!(self.flow.expect.poll_ready(cx).map_err(Into::into))?;
|
||||
|
||||
ready!(self.flow.service.poll_ready(cx).map_err(Into::into))?;
|
||||
@@ -480,17 +488,18 @@ where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
||||
{
|
||||
type Response = ();
|
||||
type Error = DispatchError;
|
||||
@@ -512,27 +521,23 @@ where
|
||||
|
||||
match proto {
|
||||
Protocol::Http2 => HttpServiceHandlerResponse {
|
||||
state: State::H2Handshake {
|
||||
handshake: Some((
|
||||
h2::handshake_with_timeout(io, &self.cfg),
|
||||
self.cfg.clone(),
|
||||
self.flow.clone(),
|
||||
on_connect_data,
|
||||
peer_addr,
|
||||
)),
|
||||
},
|
||||
state: State::H2Handshake(Some((
|
||||
h2_handshake(io),
|
||||
self.cfg.clone(),
|
||||
self.flow.clone(),
|
||||
on_connect_data,
|
||||
peer_addr,
|
||||
))),
|
||||
},
|
||||
|
||||
Protocol::Http1 => HttpServiceHandlerResponse {
|
||||
state: State::H1 {
|
||||
dispatcher: h1::Dispatcher::new(
|
||||
io,
|
||||
self.cfg.clone(),
|
||||
self.flow.clone(),
|
||||
on_connect_data,
|
||||
peer_addr,
|
||||
),
|
||||
},
|
||||
state: State::H1(h1::Dispatcher::new(
|
||||
io,
|
||||
self.cfg.clone(),
|
||||
self.flow.clone(),
|
||||
on_connect_data,
|
||||
peer_addr,
|
||||
)),
|
||||
},
|
||||
|
||||
proto => unimplemented!("Unsupported HTTP version: {:?}.", proto),
|
||||
@@ -540,65 +545,58 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = StateProj]
|
||||
enum State<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead,
|
||||
T: AsyncWrite,
|
||||
T: Unpin,
|
||||
#[pin_project(project = StateProj)]
|
||||
enum State<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
|
||||
S: Service<Request>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S: Service<Request>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<AnyBody>>,
|
||||
|
||||
B: MessageBody,
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
H1 { #[pin] dispatcher: h1::Dispatcher<T, S, B, X, U> },
|
||||
H2 { #[pin] dispatcher: h2::Dispatcher<T, S, B, X, U> },
|
||||
H2Handshake {
|
||||
handshake: Option<(
|
||||
h2::HandshakeWithTimeout<T>,
|
||||
ServiceConfig,
|
||||
Rc<HttpFlow<S, X, U>>,
|
||||
OnConnectData,
|
||||
Option<net::SocketAddr>,
|
||||
)>,
|
||||
},
|
||||
}
|
||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
H1(#[pin] h1::Dispatcher<T, S, B, X, U>),
|
||||
H2(#[pin] h2::Dispatcher<T, S, B, X, U>),
|
||||
H2Handshake(
|
||||
Option<(
|
||||
H2Handshake<T, Bytes>,
|
||||
ServiceConfig,
|
||||
Rc<HttpFlow<S, X, U>>,
|
||||
OnConnectData,
|
||||
Option<net::SocketAddr>,
|
||||
)>,
|
||||
),
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub struct HttpServiceHandlerResponse<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead,
|
||||
T: AsyncWrite,
|
||||
T: Unpin,
|
||||
#[pin_project]
|
||||
pub struct HttpServiceHandlerResponse<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: 'static,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>>,
|
||||
S::Response: 'static,
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
|
||||
B: MessageBody,
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
#[pin]
|
||||
state: State<T, S, B, X, U>,
|
||||
}
|
||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
#[pin]
|
||||
state: State<T, S, B, X, U>,
|
||||
}
|
||||
|
||||
impl<T, S, B, X, U> Future for HttpServiceHandlerResponse<T, S, B, X, U>
|
||||
@@ -606,14 +604,15 @@ where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Error: Into<Response<AnyBody>> + 'static,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::Error: Into<Response<AnyBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
@@ -622,29 +621,27 @@ where
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.as_mut().project().state.project() {
|
||||
StateProj::H1 { dispatcher } => dispatcher.poll(cx),
|
||||
StateProj::H2 { dispatcher } => dispatcher.poll(cx),
|
||||
StateProj::H2Handshake { handshake: data } => {
|
||||
StateProj::H1(disp) => disp.poll(cx),
|
||||
StateProj::H2(disp) => disp.poll(cx),
|
||||
StateProj::H2Handshake(data) => {
|
||||
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
|
||||
Ok((conn, timer)) => {
|
||||
let (_, config, flow, on_connect_data, peer_addr) =
|
||||
Ok(conn) => {
|
||||
let (_, cfg, srv, on_connect_data, peer_addr) =
|
||||
data.take().unwrap();
|
||||
|
||||
self.as_mut().project().state.set(State::H2 {
|
||||
dispatcher: h2::Dispatcher::new(
|
||||
flow,
|
||||
self.as_mut().project().state.set(State::H2(
|
||||
h2::Dispatcher::new(
|
||||
srv,
|
||||
conn,
|
||||
on_connect_data,
|
||||
config,
|
||||
cfg,
|
||||
peer_addr,
|
||||
timer,
|
||||
),
|
||||
});
|
||||
));
|
||||
self.poll(cx)
|
||||
}
|
||||
Err(err) => {
|
||||
trace!("H2 handshake error: {}", err);
|
||||
Poll::Ready(Err(err))
|
||||
Poll::Ready(Err(err.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -63,8 +63,8 @@ pub enum Item {
|
||||
Last(Bytes),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
/// WebSocket protocol codec.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Codec {
|
||||
flags: Flags,
|
||||
max_size: usize,
|
||||
@@ -89,8 +89,7 @@ impl Codec {
|
||||
|
||||
/// Set max frame size.
|
||||
///
|
||||
/// By default max size is set to 64KiB.
|
||||
#[must_use = "This returns the a new Codec, without modifying the original."]
|
||||
/// By default max size is set to 64kB.
|
||||
pub fn max_size(mut self, size: usize) -> Self {
|
||||
self.max_size = size;
|
||||
self
|
||||
@@ -99,19 +98,12 @@ impl Codec {
|
||||
/// Set decoder to client mode.
|
||||
///
|
||||
/// By default decoder works in server mode.
|
||||
#[must_use = "This returns the a new Codec, without modifying the original."]
|
||||
pub fn client_mode(mut self) -> Self {
|
||||
self.flags.remove(Flags::SERVER);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Codec {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder<Message> for Codec {
|
||||
type Error = ProtocolError;
|
||||
|
||||
|
@@ -4,21 +4,17 @@ use std::task::{Context, Poll};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_service::{IntoService, Service};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::{Codec, Frame, Message};
|
||||
|
||||
pin_project! {
|
||||
pub struct Dispatcher<S, T>
|
||||
where
|
||||
S: Service<Frame, Response = Message>,
|
||||
S: 'static,
|
||||
T: AsyncRead,
|
||||
T: AsyncWrite,
|
||||
{
|
||||
#[pin]
|
||||
inner: inner::Dispatcher<S, T, Codec, Message>,
|
||||
}
|
||||
#[pin_project::pin_project]
|
||||
pub struct Dispatcher<S, T>
|
||||
where
|
||||
S: Service<Frame, Response = Message> + 'static,
|
||||
T: AsyncRead + AsyncWrite,
|
||||
{
|
||||
#[pin]
|
||||
inner: inner::Dispatcher<S, T, Codec, Message>,
|
||||
}
|
||||
|
||||
impl<S, T> Dispatcher<S, T>
|
||||
@@ -76,7 +72,7 @@ mod inner {
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
|
||||
|
||||
use crate::{body::BoxBody, Response};
|
||||
use crate::{body::AnyBody, Response};
|
||||
|
||||
/// Framed transport errors
|
||||
pub enum DispatcherError<E, U, I>
|
||||
@@ -140,7 +136,7 @@ mod inner {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, U, I> From<DispatcherError<E, U, I>> for Response<BoxBody>
|
||||
impl<E, U, I> From<DispatcherError<E, U, I>> for Response<AnyBody>
|
||||
where
|
||||
E: fmt::Debug + fmt::Display,
|
||||
U: Encoder<I> + Decoder,
|
||||
@@ -148,7 +144,7 @@ mod inner {
|
||||
<U as Decoder>::Error: fmt::Debug,
|
||||
{
|
||||
fn from(err: DispatcherError<E, U, I>) -> Self {
|
||||
Response::internal_server_error().set_body(BoxBody::new(err.to_string()))
|
||||
Response::internal_server_error().set_body(AnyBody::from(err.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -8,9 +8,9 @@ use std::io;
|
||||
use derive_more::{Display, Error, From};
|
||||
use http::{header, Method, StatusCode};
|
||||
|
||||
use crate::body::BoxBody;
|
||||
use crate::{
|
||||
header::HeaderValue, message::RequestHead, response::Response, ResponseBuilder,
|
||||
body::AnyBody, header::HeaderValue, message::RequestHead, response::Response,
|
||||
ResponseBuilder,
|
||||
};
|
||||
|
||||
mod codec;
|
||||
@@ -69,7 +69,7 @@ pub enum ProtocolError {
|
||||
}
|
||||
|
||||
/// WebSocket handshake errors
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Display, Error)]
|
||||
#[derive(Debug, PartialEq, Display, Error)]
|
||||
pub enum HandshakeError {
|
||||
/// Only get method is allowed.
|
||||
#[display(fmt = "Method not allowed.")]
|
||||
@@ -96,8 +96,8 @@ pub enum HandshakeError {
|
||||
BadWebsocketKey,
|
||||
}
|
||||
|
||||
impl From<HandshakeError> for Response<BoxBody> {
|
||||
fn from(err: HandshakeError) -> Self {
|
||||
impl From<&HandshakeError> for Response<AnyBody> {
|
||||
fn from(err: &HandshakeError) -> Self {
|
||||
match err {
|
||||
HandshakeError::GetMethodRequired => {
|
||||
let mut res = Response::new(StatusCode::METHOD_NOT_ALLOWED);
|
||||
@@ -139,9 +139,9 @@ impl From<HandshakeError> for Response<BoxBody> {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&HandshakeError> for Response<BoxBody> {
|
||||
fn from(err: &HandshakeError) -> Self {
|
||||
(*err).into()
|
||||
impl From<HandshakeError> for Response<AnyBody> {
|
||||
fn from(err: HandshakeError) -> Self {
|
||||
(&err).into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,10 +220,9 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{header, Method};
|
||||
|
||||
use super::*;
|
||||
use crate::test::TestRequest;
|
||||
use crate::{body::AnyBody, test::TestRequest};
|
||||
use http::{header, Method};
|
||||
|
||||
#[test]
|
||||
fn test_handshake() {
|
||||
@@ -337,17 +336,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_ws_error_http_response() {
|
||||
let resp: Response<BoxBody> = HandshakeError::GetMethodRequired.into();
|
||||
let resp: Response<AnyBody> = HandshakeError::GetMethodRequired.into();
|
||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
let resp: Response<BoxBody> = HandshakeError::NoWebsocketUpgrade.into();
|
||||
let resp: Response<AnyBody> = HandshakeError::NoWebsocketUpgrade.into();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
let resp: Response<BoxBody> = HandshakeError::NoConnectionUpgrade.into();
|
||||
let resp: Response<AnyBody> = HandshakeError::NoConnectionUpgrade.into();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
let resp: Response<BoxBody> = HandshakeError::NoVersionHeader.into();
|
||||
let resp: Response<AnyBody> = HandshakeError::NoVersionHeader.into();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
let resp: Response<BoxBody> = HandshakeError::UnsupportedVersion.into();
|
||||
let resp: Response<AnyBody> = HandshakeError::UnsupportedVersion.into();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
let resp: Response<BoxBody> = HandshakeError::BadWebsocketKey.into();
|
||||
let resp: Response<AnyBody> = HandshakeError::BadWebsocketKey.into();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
use std::convert::Infallible;
|
||||
|
||||
use actix_http::{
|
||||
body::BoxBody, HttpMessage, HttpService, Request, Response, StatusCode,
|
||||
body::AnyBody, http, http::StatusCode, HttpMessage, HttpService, Request, Response,
|
||||
};
|
||||
use actix_http_test::test_server;
|
||||
use actix_service::ServiceFactoryExt;
|
||||
@@ -99,7 +99,7 @@ async fn test_with_query_parameter() {
|
||||
#[display(fmt = "expect failed")]
|
||||
struct ExpectFailed;
|
||||
|
||||
impl From<ExpectFailed> for Response<BoxBody> {
|
||||
impl From<ExpectFailed> for Response<AnyBody> {
|
||||
fn from(_: ExpectFailed) -> Self {
|
||||
Response::new(StatusCode::EXPECTATION_FAILED)
|
||||
}
|
||||
|
77
actix-http/tests/test_h2_ping_pong.rs
Normal file
77
actix-http/tests/test_h2_ping_pong.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use std::io;
|
||||
|
||||
use actix_http::{error::Error, HttpService, Response};
|
||||
use actix_server::Server;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn h2_ping_pong() -> io::Result<()> {
|
||||
let (tx, rx) = std::sync::mpsc::sync_channel(1);
|
||||
|
||||
let lst = std::net::TcpListener::bind("127.0.0.1:0")?;
|
||||
|
||||
let addr = lst.local_addr().unwrap();
|
||||
|
||||
let join = std::thread::spawn(move || {
|
||||
actix_rt::System::new().block_on(async move {
|
||||
let srv = Server::build()
|
||||
.disable_signals()
|
||||
.workers(1)
|
||||
.listen("h2_ping_pong", lst, || {
|
||||
HttpService::build()
|
||||
.keep_alive(3)
|
||||
.h2(|_| async { Ok::<_, Error>(Response::ok()) })
|
||||
.tcp()
|
||||
})?
|
||||
.run();
|
||||
|
||||
tx.send(srv.handle()).unwrap();
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let handle = rx.recv().unwrap();
|
||||
|
||||
let (sync_tx, rx) = std::sync::mpsc::sync_channel(1);
|
||||
|
||||
// use a separate thread for h2 client so it can be blocked.
|
||||
std::thread::spawn(move || {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async move {
|
||||
let stream = tokio::net::TcpStream::connect(addr).await.unwrap();
|
||||
|
||||
let (mut tx, conn) = h2::client::handshake(stream).await.unwrap();
|
||||
|
||||
tokio::spawn(async move { conn.await.unwrap() });
|
||||
|
||||
let (res, _) = tx.send_request(::http::Request::new(()), true).unwrap();
|
||||
let res = res.await.unwrap();
|
||||
|
||||
assert_eq!(res.status().as_u16(), 200);
|
||||
|
||||
sync_tx.send(()).unwrap();
|
||||
|
||||
// intentionally block the client thread so it can not answer ping pong.
|
||||
std::thread::sleep(std::time::Duration::from_secs(1000));
|
||||
})
|
||||
});
|
||||
|
||||
rx.recv().unwrap();
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
// stop server gracefully. this step would take up to 30 seconds.
|
||||
handle.stop(true).await;
|
||||
|
||||
// join server thread. only when connection are all gone this step would finish.
|
||||
join.join().unwrap()?;
|
||||
|
||||
// check the time used for join server thread so it's known that the server shutdown
|
||||
// is from keep alive and not server graceful shutdown timeout.
|
||||
assert!(now.elapsed() < std::time::Duration::from_secs(30));
|
||||
|
||||
Ok(())
|
||||
}
|
@@ -1,153 +0,0 @@
|
||||
use std::io;
|
||||
|
||||
use actix_http::{error::Error, HttpService, Response};
|
||||
use actix_server::Server;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn h2_ping_pong() -> io::Result<()> {
|
||||
let (tx, rx) = std::sync::mpsc::sync_channel(1);
|
||||
|
||||
let lst = std::net::TcpListener::bind("127.0.0.1:0")?;
|
||||
|
||||
let addr = lst.local_addr().unwrap();
|
||||
|
||||
let join = std::thread::spawn(move || {
|
||||
actix_rt::System::new().block_on(async move {
|
||||
let srv = Server::build()
|
||||
.disable_signals()
|
||||
.workers(1)
|
||||
.listen("h2_ping_pong", lst, || {
|
||||
HttpService::build()
|
||||
.keep_alive(3)
|
||||
.h2(|_| async { Ok::<_, Error>(Response::ok()) })
|
||||
.tcp()
|
||||
})?
|
||||
.run();
|
||||
|
||||
tx.send(srv.handle()).unwrap();
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let handle = rx.recv().unwrap();
|
||||
|
||||
let (sync_tx, rx) = std::sync::mpsc::sync_channel(1);
|
||||
|
||||
// use a separate thread for h2 client so it can be blocked.
|
||||
std::thread::spawn(move || {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async move {
|
||||
let stream = tokio::net::TcpStream::connect(addr).await.unwrap();
|
||||
|
||||
let (mut tx, conn) = h2::client::handshake(stream).await.unwrap();
|
||||
|
||||
tokio::spawn(async move { conn.await.unwrap() });
|
||||
|
||||
let (res, _) = tx.send_request(::http::Request::new(()), true).unwrap();
|
||||
let res = res.await.unwrap();
|
||||
|
||||
assert_eq!(res.status().as_u16(), 200);
|
||||
|
||||
sync_tx.send(()).unwrap();
|
||||
|
||||
// intentionally block the client thread so it can not answer ping pong.
|
||||
std::thread::sleep(std::time::Duration::from_secs(1000));
|
||||
})
|
||||
});
|
||||
|
||||
rx.recv().unwrap();
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
// stop server gracefully. this step would take up to 30 seconds.
|
||||
handle.stop(true).await;
|
||||
|
||||
// join server thread. only when connection are all gone this step would finish.
|
||||
join.join().unwrap()?;
|
||||
|
||||
// check the time used for join server thread so it's known that the server shutdown
|
||||
// is from keep alive and not server graceful shutdown timeout.
|
||||
assert!(now.elapsed() < std::time::Duration::from_secs(30));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn h2_handshake_timeout() -> io::Result<()> {
|
||||
let (tx, rx) = std::sync::mpsc::sync_channel(1);
|
||||
|
||||
let lst = std::net::TcpListener::bind("127.0.0.1:0")?;
|
||||
|
||||
let addr = lst.local_addr().unwrap();
|
||||
|
||||
let join = std::thread::spawn(move || {
|
||||
actix_rt::System::new().block_on(async move {
|
||||
let srv = Server::build()
|
||||
.disable_signals()
|
||||
.workers(1)
|
||||
.listen("h2_ping_pong", lst, || {
|
||||
HttpService::build()
|
||||
.keep_alive(30)
|
||||
// set first request timeout to 5 seconds.
|
||||
// this is the timeout used for http2 handshake.
|
||||
.client_timeout(5000)
|
||||
.h2(|_| async { Ok::<_, Error>(Response::ok()) })
|
||||
.tcp()
|
||||
})?
|
||||
.run();
|
||||
|
||||
tx.send(srv.handle()).unwrap();
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let handle = rx.recv().unwrap();
|
||||
|
||||
let (sync_tx, rx) = std::sync::mpsc::sync_channel(1);
|
||||
|
||||
// use a separate thread for tcp client so it can be blocked.
|
||||
std::thread::spawn(move || {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async move {
|
||||
let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
|
||||
|
||||
// do not send the last new line intentionally.
|
||||
// This should hang the server handshake
|
||||
let malicious_buf = b"PRI * HTTP/2.0\r\n\r\nSM\r\n";
|
||||
stream.write_all(malicious_buf).await.unwrap();
|
||||
stream.flush().await.unwrap();
|
||||
|
||||
sync_tx.send(()).unwrap();
|
||||
|
||||
// intentionally block the client thread so it sit idle and not do handshake.
|
||||
std::thread::sleep(std::time::Duration::from_secs(1000));
|
||||
|
||||
drop(stream)
|
||||
})
|
||||
});
|
||||
|
||||
rx.recv().unwrap();
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
// stop server gracefully. this step would take up to 30 seconds.
|
||||
handle.stop(true).await;
|
||||
|
||||
// join server thread. only when connection are all gone this step would finish.
|
||||
join.join().unwrap()?;
|
||||
|
||||
// check the time used for join server thread so it's known that the server shutdown
|
||||
// is from handshake timeout and not server graceful shutdown timeout.
|
||||
assert!(now.elapsed() < std::time::Duration::from_secs(30));
|
||||
|
||||
Ok(())
|
||||
}
|
@@ -5,10 +5,13 @@ extern crate tls_openssl as openssl;
|
||||
use std::{convert::Infallible, io};
|
||||
|
||||
use actix_http::{
|
||||
body::{BodyStream, BoxBody, SizedStream},
|
||||
body::{AnyBody, SizedStream},
|
||||
error::PayloadError,
|
||||
header::{self, HeaderValue},
|
||||
Error, HttpMessage, HttpService, Method, Request, Response, StatusCode, Version,
|
||||
http::{
|
||||
header::{self, HeaderValue},
|
||||
Method, StatusCode, Version,
|
||||
},
|
||||
Error, HttpMessage, HttpService, Request, Response,
|
||||
};
|
||||
use actix_http_test::test_server;
|
||||
use actix_service::{fn_service, ServiceFactoryExt};
|
||||
@@ -345,7 +348,7 @@ async fn test_h2_body_chunked_explicit() {
|
||||
ok::<_, Infallible>(
|
||||
Response::build(StatusCode::OK)
|
||||
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||
.body(BodyStream::new(body)),
|
||||
.streaming(body),
|
||||
)
|
||||
})
|
||||
.openssl(tls_config())
|
||||
@@ -396,11 +399,9 @@ async fn test_h2_response_http_error_handling() {
|
||||
#[display(fmt = "error")]
|
||||
struct BadRequest;
|
||||
|
||||
impl From<BadRequest> for Response<BoxBody> {
|
||||
impl From<BadRequest> for Response<AnyBody> {
|
||||
fn from(err: BadRequest) -> Self {
|
||||
Response::build(StatusCode::BAD_REQUEST)
|
||||
.body(err.to_string())
|
||||
.map_into_boxed_body()
|
||||
Response::build(StatusCode::BAD_REQUEST).body(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,7 +409,7 @@ impl From<BadRequest> for Response<BoxBody> {
|
||||
async fn test_h2_service_error() {
|
||||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|_| err::<Response<BoxBody>, _>(BadRequest))
|
||||
.h2(|_| err::<Response<AnyBody>, _>(BadRequest))
|
||||
.openssl(tls_config())
|
||||
.map_err(|_| ())
|
||||
})
|
||||
|
@@ -10,10 +10,13 @@ use std::{
|
||||
};
|
||||
|
||||
use actix_http::{
|
||||
body::{BodyStream, BoxBody, SizedStream},
|
||||
body::{AnyBody, SizedStream},
|
||||
error::PayloadError,
|
||||
header::{self, HeaderName, HeaderValue},
|
||||
Error, HttpService, Method, Request, Response, StatusCode, Version,
|
||||
http::{
|
||||
header::{self, HeaderName, HeaderValue},
|
||||
Method, StatusCode, Version,
|
||||
},
|
||||
Error, HttpService, Request, Response,
|
||||
};
|
||||
use actix_http_test::test_server;
|
||||
use actix_service::{fn_factory_with_config, fn_service};
|
||||
@@ -413,7 +416,7 @@ async fn test_h2_body_chunked_explicit() {
|
||||
ok::<_, Infallible>(
|
||||
Response::build(StatusCode::OK)
|
||||
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||
.body(BodyStream::new(body)),
|
||||
.streaming(body),
|
||||
)
|
||||
})
|
||||
.rustls(tls_config())
|
||||
@@ -464,9 +467,9 @@ async fn test_h2_response_http_error_handling() {
|
||||
#[display(fmt = "error")]
|
||||
struct BadRequest;
|
||||
|
||||
impl From<BadRequest> for Response<BoxBody> {
|
||||
impl From<BadRequest> for Response<AnyBody> {
|
||||
fn from(_: BadRequest) -> Self {
|
||||
Response::bad_request().set_body(BoxBody::new("error"))
|
||||
Response::bad_request().set_body(AnyBody::from("error"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,7 +477,7 @@ impl From<BadRequest> for Response<BoxBody> {
|
||||
async fn test_h2_service_error() {
|
||||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|_| err::<Response<BoxBody>, _>(BadRequest))
|
||||
.h2(|_| err::<Response<AnyBody>, _>(BadRequest))
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
@@ -491,7 +494,7 @@ async fn test_h2_service_error() {
|
||||
async fn test_h1_service_error() {
|
||||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h1(|_| err::<Response<BoxBody>, _>(BadRequest))
|
||||
.h1(|_| err::<Response<AnyBody>, _>(BadRequest))
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
@@ -6,8 +6,9 @@ use std::{
|
||||
};
|
||||
|
||||
use actix_http::{
|
||||
body::{self, BodyStream, BoxBody, SizedStream},
|
||||
header, Error, HttpMessage, HttpService, KeepAlive, Request, Response, StatusCode,
|
||||
body::{AnyBody, SizedStream},
|
||||
header, http, Error, HttpMessage, HttpService, KeepAlive, Request, Response,
|
||||
StatusCode,
|
||||
};
|
||||
use actix_http_test::test_server;
|
||||
use actix_rt::time::sleep;
|
||||
@@ -68,7 +69,7 @@ async fn test_h1_2() {
|
||||
#[display(fmt = "expect failed")]
|
||||
struct ExpectFailed;
|
||||
|
||||
impl From<ExpectFailed> for Response<BoxBody> {
|
||||
impl From<ExpectFailed> for Response<AnyBody> {
|
||||
fn from(_: ExpectFailed) -> Self {
|
||||
Response::new(StatusCode::EXPECTATION_FAILED)
|
||||
}
|
||||
@@ -382,7 +383,7 @@ async fn test_http1_keepalive_disabled() {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_content_length() {
|
||||
use actix_http::{
|
||||
use actix_http::http::{
|
||||
header::{HeaderName, HeaderValue},
|
||||
StatusCode,
|
||||
};
|
||||
@@ -621,7 +622,7 @@ async fn test_h1_body_chunked_explicit() {
|
||||
ok::<_, Infallible>(
|
||||
Response::build(StatusCode::OK)
|
||||
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||
.body(BodyStream::new(body)),
|
||||
.streaming(body),
|
||||
)
|
||||
})
|
||||
.tcp()
|
||||
@@ -655,9 +656,7 @@ async fn test_h1_body_chunked_implicit() {
|
||||
HttpService::build()
|
||||
.h1(|_| {
|
||||
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
||||
ok::<_, Infallible>(
|
||||
Response::build(StatusCode::OK).body(BodyStream::new(body)),
|
||||
)
|
||||
ok::<_, Infallible>(Response::build(StatusCode::OK).streaming(body))
|
||||
})
|
||||
.tcp()
|
||||
})
|
||||
@@ -715,9 +714,9 @@ async fn test_h1_response_http_error_handling() {
|
||||
#[display(fmt = "error")]
|
||||
struct BadRequest;
|
||||
|
||||
impl From<BadRequest> for Response<BoxBody> {
|
||||
impl From<BadRequest> for Response<AnyBody> {
|
||||
fn from(_: BadRequest) -> Self {
|
||||
Response::bad_request().set_body(BoxBody::new("error"))
|
||||
Response::bad_request().set_body(AnyBody::from("error"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -725,7 +724,7 @@ impl From<BadRequest> for Response<BoxBody> {
|
||||
async fn test_h1_service_error() {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.h1(|_| err::<Response<()>, _>(BadRequest))
|
||||
.h1(|_| err::<Response<AnyBody>, _>(BadRequest))
|
||||
.tcp()
|
||||
})
|
||||
.await;
|
||||
@@ -774,35 +773,36 @@ async fn test_not_modified_spec_h1() {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.h1(|req: Request| {
|
||||
let res: Response<BoxBody> = match req.path() {
|
||||
let res: Response<AnyBody> = match req.path() {
|
||||
// with no content-length
|
||||
"/none" => {
|
||||
Response::with_body(StatusCode::NOT_MODIFIED, body::None::new())
|
||||
.map_into_boxed_body()
|
||||
Response::with_body(StatusCode::NOT_MODIFIED, AnyBody::None)
|
||||
}
|
||||
|
||||
// with no content-length
|
||||
"/body" => Response::with_body(StatusCode::NOT_MODIFIED, "1234")
|
||||
.map_into_boxed_body(),
|
||||
"/body" => Response::with_body(
|
||||
StatusCode::NOT_MODIFIED,
|
||||
AnyBody::from("1234"),
|
||||
),
|
||||
|
||||
// with manual content-length header and specific None body
|
||||
"/cl-none" => {
|
||||
let mut res = Response::with_body(
|
||||
StatusCode::NOT_MODIFIED,
|
||||
body::None::new(),
|
||||
);
|
||||
let mut res =
|
||||
Response::with_body(StatusCode::NOT_MODIFIED, AnyBody::None);
|
||||
res.headers_mut()
|
||||
.insert(CL.clone(), header::HeaderValue::from_static("24"));
|
||||
res.map_into_boxed_body()
|
||||
res
|
||||
}
|
||||
|
||||
// with manual content-length header and ignore-able body
|
||||
"/cl-body" => {
|
||||
let mut res =
|
||||
Response::with_body(StatusCode::NOT_MODIFIED, "1234");
|
||||
let mut res = Response::with_body(
|
||||
StatusCode::NOT_MODIFIED,
|
||||
AnyBody::from("1234"),
|
||||
);
|
||||
res.headers_mut()
|
||||
.insert(CL.clone(), header::HeaderValue::from_static("4"));
|
||||
res.map_into_boxed_body()
|
||||
res
|
||||
}
|
||||
|
||||
_ => panic!("unknown route"),
|
||||
|
@@ -6,7 +6,7 @@ use std::{
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_http::{
|
||||
body::{BodySize, BoxBody},
|
||||
body::{AnyBody, BodySize},
|
||||
h1,
|
||||
ws::{self, CloseCode, Frame, Item, Message},
|
||||
Error, HttpService, Request, Response,
|
||||
@@ -50,14 +50,14 @@ enum WsServiceError {
|
||||
Dispatcher,
|
||||
}
|
||||
|
||||
impl From<WsServiceError> for Response<BoxBody> {
|
||||
impl From<WsServiceError> for Response<AnyBody> {
|
||||
fn from(err: WsServiceError) -> Self {
|
||||
match err {
|
||||
WsServiceError::Http(err) => err.into(),
|
||||
WsServiceError::Ws(err) => err.into(),
|
||||
WsServiceError::Io(_err) => unreachable!(),
|
||||
WsServiceError::Dispatcher => Response::internal_server_error()
|
||||
.set_body(BoxBody::new(format!("{}", err))),
|
||||
.set_body(AnyBody::from(format!("{}", err))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -435,10 +435,10 @@ impl Field {
|
||||
|
||||
/// Returns the field's Content-Disposition.
|
||||
///
|
||||
/// Per [RFC 7578 §4.2]: "Each part MUST contain a Content-Disposition header field where the
|
||||
/// disposition type is `form-data`. The Content-Disposition header field MUST also contain an
|
||||
/// additional parameter of `name`; the value of the `name` parameter is the original field name
|
||||
/// from the form."
|
||||
/// Per [RFC 7578 §4.2]: 'Each part MUST contain a Content-Disposition header field where the
|
||||
/// disposition type is "form-data". The Content-Disposition header field MUST also contain an
|
||||
/// additional parameter of "name"; the value of the "name" parameter is the original field name
|
||||
/// from the form.'
|
||||
///
|
||||
/// This crate validates that it exists before returning a `Field`. As such, it is safe to
|
||||
/// unwrap `.content_disposition().get_name()`. The [name](Self::name) method is provided as
|
||||
@@ -451,8 +451,7 @@ impl Field {
|
||||
|
||||
/// Returns the field's name.
|
||||
///
|
||||
/// See [content_disposition](Self::content_disposition) regarding guarantees about existence of
|
||||
/// the name field.
|
||||
/// See [content_disposition] regarding guarantees about
|
||||
pub fn name(&self) -> &str {
|
||||
self.content_disposition()
|
||||
.get_name()
|
||||
|
@@ -31,15 +31,17 @@ extern crate tls_openssl as openssl;
|
||||
#[cfg(feature = "rustls")]
|
||||
extern crate tls_rustls as rustls;
|
||||
|
||||
use std::{fmt, net, thread, time::Duration};
|
||||
use std::{error::Error as StdError, fmt, net, thread, time::Duration};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
pub use actix_http::test::TestBuffer;
|
||||
use actix_http::{header::HeaderMap, ws, HttpService, Method, Request, Response};
|
||||
use actix_http::{
|
||||
http::{HeaderMap, Method},
|
||||
ws, HttpService, Request, Response,
|
||||
};
|
||||
use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
|
||||
use actix_web::{
|
||||
body::MessageBody,
|
||||
dev::{AppConfig, Server, ServerHandle, Service},
|
||||
dev::{AppConfig, MessageBody, Server, ServerHandle, Service},
|
||||
rt::{self, System},
|
||||
web, Error,
|
||||
};
|
||||
@@ -86,6 +88,7 @@ where
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
start_with(TestServerConfig::default(), factory)
|
||||
}
|
||||
@@ -125,6 +128,7 @@ where
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
// for sending handles and server info back from the spawned thread
|
||||
let (started_tx, started_rx) = std::sync::mpsc::channel();
|
||||
|
@@ -1,12 +1,8 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
* Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920]
|
||||
* Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920]
|
||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
[#1920]: https://github.com/actix/actix-web/pull/1920
|
||||
|
||||
|
||||
## 4.0.0-beta.7 - 2021-09-09
|
||||
* Minimum supported Rust version (MSRV) is now 1.51.
|
||||
|
@@ -22,7 +22,7 @@ actix-web = { version = "4.0.0-beta.11", default-features = false }
|
||||
bytes = "1"
|
||||
bytestring = "1"
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
pin-project-lite = "0.2"
|
||||
pin-project = "1.0.0"
|
||||
tokio = { version = "1", features = ["sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
@@ -1,222 +1,38 @@
|
||||
//! Websocket integration.
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
convert::TryFrom,
|
||||
future::Future,
|
||||
io, mem,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{collections::VecDeque, convert::TryFrom};
|
||||
|
||||
use actix::dev::{
|
||||
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, ToEnvelope,
|
||||
};
|
||||
use actix::fut::ActorFuture;
|
||||
use actix::{
|
||||
dev::{
|
||||
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler,
|
||||
ToEnvelope,
|
||||
},
|
||||
fut::ActorFuture,
|
||||
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message as ActixMessage,
|
||||
SpawnHandle,
|
||||
};
|
||||
use actix_codec::{Decoder as _, Encoder as _};
|
||||
use actix_http::ws::{hash_key, Codec};
|
||||
use actix_codec::{Decoder, Encoder};
|
||||
pub use actix_http::ws::{
|
||||
CloseCode, CloseReason, Frame, HandshakeError, Message, ProtocolError,
|
||||
};
|
||||
use actix_http::{
|
||||
http::HeaderValue,
|
||||
ws::{hash_key, Codec},
|
||||
};
|
||||
use actix_web::{
|
||||
error::{Error, PayloadError},
|
||||
http::{
|
||||
header::{self, HeaderValue},
|
||||
Method, StatusCode,
|
||||
},
|
||||
http::{header, Method, StatusCode},
|
||||
HttpRequest, HttpResponse, HttpResponseBuilder,
|
||||
};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use bytestring::ByteString;
|
||||
use futures_core::Stream;
|
||||
use pin_project_lite::pin_project;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
/// Builder for Websocket session response.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Create a Websocket session response with default configuration.
|
||||
/// ```ignore
|
||||
/// WsResponseBuilder::new(WsActor, &req, stream).start()
|
||||
/// ```
|
||||
///
|
||||
/// Create a Websocket session with a specific max frame size, [`Codec`], and protocols.
|
||||
/// ```ignore
|
||||
/// const MAX_FRAME_SIZE: usize = 16_384; // 16KiB
|
||||
///
|
||||
/// ws::WsResponseBuilder::new(WsActor, &req, stream)
|
||||
/// .codec(Codec::new())
|
||||
/// .protocols(&["A", "B"])
|
||||
/// .frame_size(MAX_FRAME_SIZE)
|
||||
/// .start()
|
||||
/// ```
|
||||
pub struct WsResponseBuilder<'a, A, T>
|
||||
where
|
||||
A: Actor<Context = WebsocketContext<A>> + StreamHandler<Result<Message, ProtocolError>>,
|
||||
T: Stream<Item = Result<Bytes, PayloadError>> + 'static,
|
||||
{
|
||||
actor: A,
|
||||
req: &'a HttpRequest,
|
||||
stream: T,
|
||||
codec: Option<Codec>,
|
||||
protocols: Option<&'a [&'a str]>,
|
||||
frame_size: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'a, A, T> WsResponseBuilder<'a, A, T>
|
||||
where
|
||||
A: Actor<Context = WebsocketContext<A>> + StreamHandler<Result<Message, ProtocolError>>,
|
||||
T: Stream<Item = Result<Bytes, PayloadError>> + 'static,
|
||||
{
|
||||
/// Construct a new `WsResponseBuilder` with actor, request, and payload stream.
|
||||
///
|
||||
/// For usage example, see docs on [`WsResponseBuilder`] struct.
|
||||
pub fn new(actor: A, req: &'a HttpRequest, stream: T) -> Self {
|
||||
WsResponseBuilder {
|
||||
actor,
|
||||
req,
|
||||
stream,
|
||||
codec: None,
|
||||
protocols: None,
|
||||
frame_size: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the protocols for the session.
|
||||
pub fn protocols(mut self, protocols: &'a [&'a str]) -> Self {
|
||||
self.protocols = Some(protocols);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the max frame size for each message (in bytes).
|
||||
///
|
||||
/// **Note**: This will override any given [`Codec`]'s max frame size.
|
||||
pub fn frame_size(mut self, frame_size: usize) -> Self {
|
||||
self.frame_size = Some(frame_size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the [`Codec`] for the session. If [`Self::frame_size`] is also set, the given
|
||||
/// [`Codec`]'s max frame size will be overridden.
|
||||
pub fn codec(mut self, codec: Codec) -> Self {
|
||||
self.codec = Some(codec);
|
||||
self
|
||||
}
|
||||
|
||||
fn handshake_resp(&self) -> Result<HttpResponseBuilder, HandshakeError> {
|
||||
match self.protocols {
|
||||
Some(protocols) => handshake_with_protocols(self.req, protocols),
|
||||
None => handshake(self.req),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_frame_size(&mut self) {
|
||||
if let Some(frame_size) = self.frame_size {
|
||||
match &mut self.codec {
|
||||
Some(codec) => {
|
||||
// modify existing codec's max frame size
|
||||
let orig_codec = mem::take(codec);
|
||||
*codec = orig_codec.max_size(frame_size);
|
||||
}
|
||||
|
||||
None => {
|
||||
// create a new codec with the given size
|
||||
self.codec = Some(Codec::new().max_size(frame_size));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new Websocket context from an actor, request stream, and codec.
|
||||
///
|
||||
/// Returns a pair, where the first item is an addr for the created actor, and the second item
|
||||
/// is a stream intended to be set as part of the response
|
||||
/// via [`HttpResponseBuilder::streaming()`].
|
||||
fn create_with_codec_addr<S>(
|
||||
actor: A,
|
||||
stream: S,
|
||||
codec: Codec,
|
||||
) -> (Addr<A>, impl Stream<Item = Result<Bytes, Error>>)
|
||||
where
|
||||
A: StreamHandler<Result<Message, ProtocolError>>,
|
||||
S: Stream<Item = Result<Bytes, PayloadError>> + 'static,
|
||||
{
|
||||
let mb = Mailbox::default();
|
||||
let mut ctx = WebsocketContext {
|
||||
inner: ContextParts::new(mb.sender_producer()),
|
||||
messages: VecDeque::new(),
|
||||
};
|
||||
ctx.add_stream(WsStream::new(stream, codec.clone()));
|
||||
|
||||
let addr = ctx.address();
|
||||
|
||||
(addr, WebsocketContextFut::new(ctx, actor, mb, codec))
|
||||
}
|
||||
|
||||
/// Perform WebSocket handshake and start actor.
|
||||
///
|
||||
/// `req` is an [`HttpRequest`] that should be requesting a websocket protocol change.
|
||||
/// `stream` should be a [`Bytes`] stream (such as `actix_web::web::Payload`) that contains a
|
||||
/// stream of the body request.
|
||||
///
|
||||
/// If there is a problem with the handshake, an error is returned.
|
||||
///
|
||||
/// If successful, consume the [`WsResponseBuilder`] and return a [`HttpResponse`] wrapped in
|
||||
/// a [`Result`].
|
||||
pub fn start(mut self) -> Result<HttpResponse, Error> {
|
||||
let mut res = self.handshake_resp()?;
|
||||
self.set_frame_size();
|
||||
|
||||
match self.codec {
|
||||
Some(codec) => {
|
||||
let out_stream = WebsocketContext::with_codec(self.actor, self.stream, codec);
|
||||
Ok(res.streaming(out_stream))
|
||||
}
|
||||
None => {
|
||||
let out_stream = WebsocketContext::create(self.actor, self.stream);
|
||||
Ok(res.streaming(out_stream))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform WebSocket handshake and start actor.
|
||||
///
|
||||
/// `req` is an [`HttpRequest`] that should be requesting a websocket protocol change.
|
||||
/// `stream` should be a [`Bytes`] stream (such as `actix_web::web::Payload`) that contains a
|
||||
/// stream of the body request.
|
||||
///
|
||||
/// If there is a problem with the handshake, an error is returned.
|
||||
///
|
||||
/// If successful, returns a pair where the first item is an address for the created actor and
|
||||
/// the second item is the [`HttpResponse`] that should be returned from the websocket request.
|
||||
pub fn start_with_addr(mut self) -> Result<(Addr<A>, HttpResponse), Error> {
|
||||
let mut res = self.handshake_resp()?;
|
||||
self.set_frame_size();
|
||||
|
||||
match self.codec {
|
||||
Some(codec) => {
|
||||
let (addr, out_stream) =
|
||||
Self::create_with_codec_addr(self.actor, self.stream, codec);
|
||||
Ok((addr, res.streaming(out_stream)))
|
||||
}
|
||||
None => {
|
||||
let (addr, out_stream) =
|
||||
WebsocketContext::create_with_addr(self.actor, self.stream);
|
||||
Ok((addr, res.streaming(out_stream)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
use tokio::sync::oneshot::Sender;
|
||||
|
||||
/// Perform WebSocket handshake and start actor.
|
||||
///
|
||||
/// To customize options, see [`WsResponseBuilder`].
|
||||
pub fn start<A, T>(actor: A, req: &HttpRequest, stream: T) -> Result<HttpResponse, Error>
|
||||
where
|
||||
A: Actor<Context = WebsocketContext<A>> + StreamHandler<Result<Message, ProtocolError>>,
|
||||
@@ -228,15 +44,15 @@ where
|
||||
|
||||
/// Perform WebSocket handshake and start actor.
|
||||
///
|
||||
/// `req` is an HTTP Request that should be requesting a websocket protocol change. `stream` should
|
||||
/// be a `Bytes` stream (such as `actix_web::web::Payload`) that contains a stream of the
|
||||
/// body request.
|
||||
/// `req` is an HTTP Request that should be requesting a websocket protocol
|
||||
/// change. `stream` should be a `Bytes` stream (such as
|
||||
/// `actix_web::web::Payload`) that contains a stream of the body request.
|
||||
///
|
||||
/// If there is a problem with the handshake, an error is returned.
|
||||
///
|
||||
/// If successful, returns a pair where the first item is an address for the created actor and the
|
||||
/// second item is the response that should be returned from the WebSocket request.
|
||||
#[deprecated(since = "4.0.0", note = "Prefer `WsResponseBuilder::start_with_addr`.")]
|
||||
/// If successful, returns a pair where the first item is an address for the
|
||||
/// created actor and the second item is the response that should be returned
|
||||
/// from the WebSocket request.
|
||||
pub fn start_with_addr<A, T>(
|
||||
actor: A,
|
||||
req: &HttpRequest,
|
||||
@@ -254,10 +70,6 @@ where
|
||||
/// Do WebSocket handshake and start ws actor.
|
||||
///
|
||||
/// `protocols` is a sequence of known protocols.
|
||||
#[deprecated(
|
||||
since = "4.0.0",
|
||||
note = "Prefer `WsResponseBuilder` for setting protocols."
|
||||
)]
|
||||
pub fn start_with_protocols<A, T>(
|
||||
actor: A,
|
||||
protocols: &[&str],
|
||||
@@ -274,19 +86,20 @@ where
|
||||
|
||||
/// Prepare WebSocket handshake response.
|
||||
///
|
||||
/// This function returns handshake `HttpResponse`, ready to send to peer. It does not perform
|
||||
/// any IO.
|
||||
/// This function returns handshake `HttpResponse`, ready to send to peer.
|
||||
/// It does not perform any IO.
|
||||
pub fn handshake(req: &HttpRequest) -> Result<HttpResponseBuilder, HandshakeError> {
|
||||
handshake_with_protocols(req, &[])
|
||||
}
|
||||
|
||||
/// Prepare WebSocket handshake response.
|
||||
///
|
||||
/// This function returns handshake `HttpResponse`, ready to send to peer. It does not perform
|
||||
/// any IO.
|
||||
/// This function returns handshake `HttpResponse`, ready to send to peer.
|
||||
/// It does not perform any IO.
|
||||
///
|
||||
/// `protocols` is a sequence of known protocols. On successful handshake, the returned response
|
||||
/// headers contain the first protocol in this list which the server also knows.
|
||||
/// `protocols` is a sequence of known protocols. On successful handshake,
|
||||
/// the returned response headers contain the first protocol in this list
|
||||
/// which the server also knows.
|
||||
pub fn handshake_with_protocols(
|
||||
req: &HttpRequest,
|
||||
protocols: &[&str],
|
||||
@@ -433,8 +246,8 @@ impl<A> WebsocketContext<A>
|
||||
where
|
||||
A: Actor<Context = Self>,
|
||||
{
|
||||
/// Create a new Websocket context from a request and an actor.
|
||||
#[inline]
|
||||
/// Create a new Websocket context from a request and an actor
|
||||
pub fn create<S>(actor: A, stream: S) -> impl Stream<Item = Result<Bytes, Error>>
|
||||
where
|
||||
A: StreamHandler<Result<Message, ProtocolError>>,
|
||||
@@ -444,11 +257,12 @@ where
|
||||
stream
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Create a new Websocket context from a request and an actor.
|
||||
///
|
||||
/// Returns a pair, where the first item is an addr for the created actor, and the second item
|
||||
/// is a stream intended to be set as part of the response
|
||||
/// via [`HttpResponseBuilder::streaming()`].
|
||||
/// Returns a pair, where the first item is an addr for the created actor,
|
||||
/// and the second item is a stream intended to be set as part of the
|
||||
/// response via `HttpResponseBuilder::streaming()`.
|
||||
pub fn create_with_addr<S>(
|
||||
actor: A,
|
||||
stream: S,
|
||||
@@ -469,6 +283,7 @@ where
|
||||
(addr, WebsocketContextFut::new(ctx, actor, mb, Codec::new()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Create a new Websocket context from a request, an actor, and a codec
|
||||
pub fn with_codec<S>(
|
||||
actor: A,
|
||||
@@ -484,7 +299,7 @@ where
|
||||
inner: ContextParts::new(mb.sender_producer()),
|
||||
messages: VecDeque::new(),
|
||||
};
|
||||
ctx.add_stream(WsStream::new(stream, codec.clone()));
|
||||
ctx.add_stream(WsStream::new(stream, codec));
|
||||
|
||||
WebsocketContextFut::new(ctx, actor, mb, codec)
|
||||
}
|
||||
@@ -642,20 +457,18 @@ where
|
||||
M: ActixMessage + Send + 'static,
|
||||
M::Result: Send,
|
||||
{
|
||||
fn pack(msg: M, tx: Option<oneshot::Sender<M::Result>>) -> Envelope<A> {
|
||||
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> Envelope<A> {
|
||||
Envelope::new(msg, tx)
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[derive(Debug)]
|
||||
struct WsStream<S> {
|
||||
#[pin]
|
||||
stream: S,
|
||||
decoder: Codec,
|
||||
buf: BytesMut,
|
||||
closed: bool,
|
||||
}
|
||||
#[pin_project::pin_project]
|
||||
struct WsStream<S> {
|
||||
#[pin]
|
||||
stream: S,
|
||||
decoder: Codec,
|
||||
buf: BytesMut,
|
||||
closed: bool,
|
||||
}
|
||||
|
||||
impl<S> WsStream<S>
|
||||
@@ -734,12 +547,9 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::{
|
||||
http::{header, Method},
|
||||
test::TestRequest,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use actix_web::http::{header, Method};
|
||||
use actix_web::test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_handshake() {
|
||||
|
@@ -1,9 +1,11 @@
|
||||
use actix::prelude::*;
|
||||
use actix_http::ws::Codec;
|
||||
use actix_web::{web, App, HttpRequest};
|
||||
use actix_web_actors::ws;
|
||||
use actix_web::{
|
||||
http::{header, StatusCode},
|
||||
web, App, HttpRequest, HttpResponse,
|
||||
};
|
||||
use actix_web_actors::*;
|
||||
use bytes::Bytes;
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use futures_util::{SinkExt as _, StreamExt as _};
|
||||
|
||||
struct Ws;
|
||||
|
||||
@@ -13,34 +15,37 @@ impl Actor for Ws {
|
||||
|
||||
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Ws {
|
||||
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
|
||||
match msg {
|
||||
Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
|
||||
Ok(ws::Message::Text(text)) => ctx.text(text),
|
||||
Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
|
||||
Ok(ws::Message::Close(reason)) => ctx.close(reason),
|
||||
_ => ctx.close(Some(ws::CloseCode::Error.into())),
|
||||
match msg.unwrap() {
|
||||
ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||
ws::Message::Text(text) => ctx.text(text),
|
||||
ws::Message::Binary(bin) => ctx.binary(bin),
|
||||
ws::Message::Close(reason) => ctx.close(reason),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_FRAME_SIZE: usize = 10_000;
|
||||
const DEFAULT_FRAME_SIZE: usize = 10;
|
||||
#[actix_rt::test]
|
||||
async fn test_simple() {
|
||||
let mut srv = actix_test::start(|| {
|
||||
App::new().service(web::resource("/").to(
|
||||
|req: HttpRequest, stream: web::Payload| async move { ws::start(Ws, &req, stream) },
|
||||
))
|
||||
});
|
||||
|
||||
async fn common_test_code(mut srv: actix_test::TestServer, frame_size: usize) {
|
||||
// client service
|
||||
let mut framed = srv.ws().await.unwrap();
|
||||
|
||||
framed.send(ws::Message::Text("text".into())).await.unwrap();
|
||||
|
||||
let item = framed.next().await.unwrap().unwrap();
|
||||
assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text")));
|
||||
|
||||
let bytes = Bytes::from(vec![0; frame_size]);
|
||||
framed
|
||||
.send(ws::Message::Binary(bytes.clone()))
|
||||
.send(ws::Message::Binary("text".into()))
|
||||
.await
|
||||
.unwrap();
|
||||
let item = framed.next().await.unwrap().unwrap();
|
||||
assert_eq!(item, ws::Frame::Binary(bytes));
|
||||
assert_eq!(item, ws::Frame::Binary(Bytes::from_static(b"text")));
|
||||
|
||||
framed.send(ws::Message::Ping("text".into())).await.unwrap();
|
||||
let item = framed.next().await.unwrap().unwrap();
|
||||
@@ -50,137 +55,55 @@ async fn common_test_code(mut srv: actix_test::TestServer, frame_size: usize) {
|
||||
.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let item = framed.next().await.unwrap().unwrap();
|
||||
assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into())));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn simple_builder() {
|
||||
let srv = actix_test::start(|| {
|
||||
App::new().service(web::resource("/").to(
|
||||
|req: HttpRequest, stream: web::Payload| async move {
|
||||
ws::WsResponseBuilder::new(Ws, &req, stream).start()
|
||||
},
|
||||
))
|
||||
});
|
||||
|
||||
common_test_code(srv, DEFAULT_FRAME_SIZE).await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn builder_with_frame_size() {
|
||||
let srv = actix_test::start(|| {
|
||||
App::new().service(web::resource("/").to(
|
||||
|req: HttpRequest, stream: web::Payload| async move {
|
||||
ws::WsResponseBuilder::new(Ws, &req, stream)
|
||||
.frame_size(MAX_FRAME_SIZE)
|
||||
.start()
|
||||
},
|
||||
))
|
||||
});
|
||||
|
||||
common_test_code(srv, MAX_FRAME_SIZE).await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn builder_with_frame_size_exceeded() {
|
||||
const MAX_FRAME_SIZE: usize = 64;
|
||||
|
||||
async fn test_with_credentials() {
|
||||
let mut srv = actix_test::start(|| {
|
||||
App::new().service(web::resource("/").to(
|
||||
|req: HttpRequest, stream: web::Payload| async move {
|
||||
ws::WsResponseBuilder::new(Ws, &req, stream)
|
||||
.frame_size(MAX_FRAME_SIZE)
|
||||
.start()
|
||||
if req.headers().contains_key("Authorization") {
|
||||
ws::start(Ws, &req, stream)
|
||||
} else {
|
||||
Ok(HttpResponse::new(StatusCode::UNAUTHORIZED))
|
||||
}
|
||||
},
|
||||
))
|
||||
});
|
||||
|
||||
// client service
|
||||
let mut framed = srv.ws().await.unwrap();
|
||||
// client service without credentials
|
||||
match srv.ws().await {
|
||||
Ok(_) => panic!("WebSocket client without credentials should panic"),
|
||||
Err(awc::error::WsClientError::InvalidResponseStatus(status)) => {
|
||||
assert_eq!(status, StatusCode::UNAUTHORIZED)
|
||||
}
|
||||
Err(e) => panic!("Invalid error from WebSocket client: {}", e),
|
||||
}
|
||||
|
||||
// create a request with a frame size larger than expected
|
||||
let bytes = Bytes::from(vec![0; MAX_FRAME_SIZE + 1]);
|
||||
framed.send(ws::Message::Binary(bytes)).await.unwrap();
|
||||
let headers = srv.client_headers().unwrap();
|
||||
headers.insert(
|
||||
header::AUTHORIZATION,
|
||||
header::HeaderValue::from_static("Bearer Something"),
|
||||
);
|
||||
|
||||
let frame = framed.next().await.unwrap().unwrap();
|
||||
let close_reason = match frame {
|
||||
ws::Frame::Close(Some(reason)) => reason,
|
||||
_ => panic!("close frame expected"),
|
||||
};
|
||||
assert_eq!(close_reason.code, ws::CloseCode::Error);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn builder_with_codec() {
|
||||
let srv = actix_test::start(|| {
|
||||
App::new().service(web::resource("/").to(
|
||||
|req: HttpRequest, stream: web::Payload| async move {
|
||||
ws::WsResponseBuilder::new(Ws, &req, stream)
|
||||
.codec(Codec::new())
|
||||
.start()
|
||||
},
|
||||
))
|
||||
});
|
||||
|
||||
common_test_code(srv, DEFAULT_FRAME_SIZE).await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn builder_with_protocols() {
|
||||
let srv = actix_test::start(|| {
|
||||
App::new().service(web::resource("/").to(
|
||||
|req: HttpRequest, stream: web::Payload| async move {
|
||||
ws::WsResponseBuilder::new(Ws, &req, stream)
|
||||
.protocols(&["A", "B"])
|
||||
.start()
|
||||
},
|
||||
))
|
||||
});
|
||||
|
||||
common_test_code(srv, DEFAULT_FRAME_SIZE).await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn builder_with_codec_and_frame_size() {
|
||||
let srv = actix_test::start(|| {
|
||||
App::new().service(web::resource("/").to(
|
||||
|req: HttpRequest, stream: web::Payload| async move {
|
||||
ws::WsResponseBuilder::new(Ws, &req, stream)
|
||||
.codec(Codec::new())
|
||||
.frame_size(MAX_FRAME_SIZE)
|
||||
.start()
|
||||
},
|
||||
))
|
||||
});
|
||||
|
||||
common_test_code(srv, DEFAULT_FRAME_SIZE).await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn builder_full() {
|
||||
let srv = actix_test::start(|| {
|
||||
App::new().service(web::resource("/").to(
|
||||
|req: HttpRequest, stream: web::Payload| async move {
|
||||
ws::WsResponseBuilder::new(Ws, &req, stream)
|
||||
.frame_size(MAX_FRAME_SIZE)
|
||||
.codec(Codec::new())
|
||||
.protocols(&["A", "B"])
|
||||
.start()
|
||||
},
|
||||
))
|
||||
});
|
||||
|
||||
common_test_code(srv, MAX_FRAME_SIZE).await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn simple_start() {
|
||||
let srv = actix_test::start(|| {
|
||||
App::new().service(web::resource("/").to(
|
||||
|req: HttpRequest, stream: web::Payload| async move { ws::start(Ws, &req, stream) },
|
||||
))
|
||||
});
|
||||
|
||||
common_test_code(srv, DEFAULT_FRAME_SIZE).await;
|
||||
// client service with credentials
|
||||
let client = srv.ws();
|
||||
|
||||
let mut framed = client.await.unwrap();
|
||||
|
||||
framed.send(ws::Message::Text("text".into())).await.unwrap();
|
||||
|
||||
let item = framed.next().await.unwrap().unwrap();
|
||||
assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text")));
|
||||
|
||||
framed
|
||||
.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let item = framed.next().await.unwrap().unwrap();
|
||||
assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into())));
|
||||
}
|
||||
|
@@ -97,7 +97,7 @@ actix-web = { version = "4.0.0-beta.11", features = ["openssl"] }
|
||||
actix-http = { version = "3.0.0-beta.14", features = ["openssl"] }
|
||||
actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] }
|
||||
actix-utils = "3.0.0"
|
||||
actix-server = "2.0.0-rc.1"
|
||||
actix-server = "2.0.0-beta.9"
|
||||
actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] }
|
||||
actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] }
|
||||
|
||||
@@ -105,7 +105,6 @@ brotli2 = "0.3.2"
|
||||
env_logger = "0.9"
|
||||
flate2 = "1.0.13"
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
static_assertions = "1.1"
|
||||
rcgen = "0.8"
|
||||
rustls-pemfile = "0.2"
|
||||
|
||||
|
@@ -1,266 +0,0 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt, mem,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures_core::Stream;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use actix_http::body::{BodySize, BodyStream, BoxBody, MessageBody, SizedStream};
|
||||
|
||||
use crate::BoxError;
|
||||
|
||||
pin_project! {
|
||||
/// Represents various types of HTTP message body.
|
||||
#[derive(Clone)]
|
||||
#[project = AnyBodyProj]
|
||||
pub enum AnyBody<B = BoxBody> {
|
||||
/// Empty response. `Content-Length` header is not set.
|
||||
None,
|
||||
|
||||
/// Complete, in-memory response body.
|
||||
Bytes { body: Bytes },
|
||||
|
||||
/// Generic / Other message body.
|
||||
Body { #[pin] body: B },
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyBody {
|
||||
/// Constructs a "body" representing an empty response.
|
||||
pub fn none() -> Self {
|
||||
Self::None
|
||||
}
|
||||
|
||||
/// Constructs a new, 0-length body.
|
||||
pub fn empty() -> Self {
|
||||
Self::Bytes { body: Bytes::new() }
|
||||
}
|
||||
|
||||
/// Create boxed body from generic message body.
|
||||
pub fn new_boxed<B>(body: B) -> Self
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
Self::Body {
|
||||
body: BoxBody::new(body),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs new `AnyBody` instance from a slice of bytes by copying it.
|
||||
///
|
||||
/// If your bytes container is owned, it may be cheaper to use a `From` impl.
|
||||
pub fn copy_from_slice(s: &[u8]) -> Self {
|
||||
Self::Bytes {
|
||||
body: Bytes::copy_from_slice(s),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "4.0.0", note = "Renamed to `copy_from_slice`.")]
|
||||
pub fn from_slice(s: &[u8]) -> Self {
|
||||
Self::Bytes {
|
||||
body: Bytes::copy_from_slice(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> AnyBody<B> {
|
||||
/// Create body from generic message body.
|
||||
pub fn new(body: B) -> Self {
|
||||
Self::Body { body }
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> AnyBody<B>
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
pub fn into_boxed(self) -> AnyBody {
|
||||
match self {
|
||||
Self::None => AnyBody::None,
|
||||
Self::Bytes { body: bytes } => AnyBody::Bytes { body: bytes },
|
||||
Self::Body { body } => AnyBody::new_boxed(body),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> MessageBody for AnyBody<B>
|
||||
where
|
||||
B: MessageBody,
|
||||
{
|
||||
type Error = crate::BoxError;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
match self {
|
||||
AnyBody::None => BodySize::None,
|
||||
AnyBody::Bytes { ref body } => BodySize::Sized(body.len() as u64),
|
||||
AnyBody::Body { ref body } => body.size(),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
match self.project() {
|
||||
AnyBodyProj::None => Poll::Ready(None),
|
||||
AnyBodyProj::Bytes { body } => {
|
||||
let len = body.len();
|
||||
if len == 0 {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(mem::take(body))))
|
||||
}
|
||||
}
|
||||
|
||||
AnyBodyProj::Body { body } => body.poll_next(cx).map_err(|err| err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AnyBody {
|
||||
fn eq(&self, other: &AnyBody) -> bool {
|
||||
match self {
|
||||
AnyBody::None => matches!(*other, AnyBody::None),
|
||||
AnyBody::Bytes { body } => match other {
|
||||
AnyBody::Bytes { body: b2 } => body == b2,
|
||||
_ => false,
|
||||
},
|
||||
AnyBody::Body { .. } => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: fmt::Debug> fmt::Debug for AnyBody<S> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
AnyBody::None => write!(f, "AnyBody::None"),
|
||||
AnyBody::Bytes { ref body } => write!(f, "AnyBody::Bytes({:?})", body),
|
||||
AnyBody::Body { ref body } => write!(f, "AnyBody::Message({:?})", body),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<&'static str> for AnyBody<B> {
|
||||
fn from(string: &'static str) -> Self {
|
||||
Self::Bytes {
|
||||
body: Bytes::from_static(string.as_ref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<&'static [u8]> for AnyBody<B> {
|
||||
fn from(bytes: &'static [u8]) -> Self {
|
||||
Self::Bytes {
|
||||
body: Bytes::from_static(bytes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<Vec<u8>> for AnyBody<B> {
|
||||
fn from(vec: Vec<u8>) -> Self {
|
||||
Self::Bytes {
|
||||
body: Bytes::from(vec),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<String> for AnyBody<B> {
|
||||
fn from(string: String) -> Self {
|
||||
Self::Bytes {
|
||||
body: Bytes::from(string),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<&'_ String> for AnyBody<B> {
|
||||
fn from(string: &String) -> Self {
|
||||
Self::Bytes {
|
||||
body: Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&string)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<Cow<'_, str>> for AnyBody<B> {
|
||||
fn from(string: Cow<'_, str>) -> Self {
|
||||
match string {
|
||||
Cow::Owned(s) => Self::from(s),
|
||||
Cow::Borrowed(s) => Self::Bytes {
|
||||
body: Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<Bytes> for AnyBody<B> {
|
||||
fn from(bytes: Bytes) -> Self {
|
||||
Self::Bytes { body: bytes }
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> From<BytesMut> for AnyBody<B> {
|
||||
fn from(bytes: BytesMut) -> Self {
|
||||
Self::Bytes {
|
||||
body: bytes.freeze(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, E> From<SizedStream<S>> for AnyBody
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||
E: Into<BoxError> + 'static,
|
||||
{
|
||||
fn from(stream: SizedStream<S>) -> Self {
|
||||
AnyBody::new_boxed(stream)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, E> From<BodyStream<S>> for AnyBody
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||
E: Into<BoxError> + 'static,
|
||||
{
|
||||
fn from(stream: BodyStream<S>) -> Self {
|
||||
AnyBody::new_boxed(stream)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::marker::PhantomPinned;
|
||||
|
||||
use static_assertions::{assert_impl_all, assert_not_impl_all};
|
||||
|
||||
use super::*;
|
||||
|
||||
struct PinType(PhantomPinned);
|
||||
|
||||
impl MessageBody for PinType {
|
||||
type Error = crate::BoxError;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
assert_impl_all!(AnyBody<()>: MessageBody, fmt::Debug, Send, Sync, Unpin);
|
||||
assert_impl_all!(AnyBody<AnyBody<()>>: MessageBody, fmt::Debug, Send, Sync, Unpin);
|
||||
assert_impl_all!(AnyBody<Bytes>: MessageBody, fmt::Debug, Send, Sync, Unpin);
|
||||
assert_impl_all!(AnyBody: MessageBody, fmt::Debug, Unpin);
|
||||
assert_impl_all!(AnyBody<PinType>: MessageBody);
|
||||
|
||||
assert_not_impl_all!(AnyBody: Send, Sync, Unpin);
|
||||
assert_not_impl_all!(AnyBody<PinType>: Send, Sync, Unpin);
|
||||
}
|
@@ -1,10 +1,6 @@
|
||||
use std::{convert::TryFrom, fmt, net::IpAddr, rc::Rc, time::Duration};
|
||||
|
||||
use actix_http::{
|
||||
error::HttpError,
|
||||
header::{self, HeaderMap, HeaderName},
|
||||
Uri,
|
||||
};
|
||||
use actix_http::http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri};
|
||||
use actix_rt::net::{ActixStream, TcpStream};
|
||||
use actix_service::{boxed, Service};
|
||||
|
||||
|
@@ -12,9 +12,9 @@ use bytes::Bytes;
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use h2::client::SendRequest;
|
||||
|
||||
use actix_http::{body::MessageBody, h1::ClientCodec, Payload, RequestHeadType, ResponseHead};
|
||||
|
||||
use crate::BoxError;
|
||||
use actix_http::{
|
||||
body::MessageBody, h1::ClientCodec, Error, Payload, RequestHeadType, ResponseHead,
|
||||
};
|
||||
|
||||
use super::error::SendRequestError;
|
||||
use super::pool::Acquired;
|
||||
@@ -254,7 +254,7 @@ where
|
||||
where
|
||||
H: Into<RequestHeadType> + 'static,
|
||||
RB: MessageBody + 'static,
|
||||
RB::Error: Into<BoxError>,
|
||||
RB::Error: Into<Error>,
|
||||
{
|
||||
Box::pin(async move {
|
||||
match self {
|
||||
|
@@ -1,13 +1,13 @@
|
||||
use std::{fmt, io};
|
||||
use std::{error::Error as StdError, fmt, io};
|
||||
|
||||
use derive_more::{Display, From};
|
||||
|
||||
use actix_http::error::{HttpError, ParseError};
|
||||
|
||||
use actix_http::{
|
||||
error::{Error, ParseError},
|
||||
http::Error as HttpError,
|
||||
};
|
||||
#[cfg(feature = "openssl")]
|
||||
use actix_tls::accept::openssl::reexports::Error as OpensslError;
|
||||
|
||||
use crate::BoxError;
|
||||
use actix_tls::accept::openssl::reexports::Error as OpenSslError;
|
||||
|
||||
/// A set of errors that can occur while connecting to an HTTP host
|
||||
#[derive(Debug, Display, From)]
|
||||
@@ -20,7 +20,7 @@ pub enum ConnectError {
|
||||
/// SSL error
|
||||
#[cfg(feature = "openssl")]
|
||||
#[display(fmt = "{}", _0)]
|
||||
SslError(OpensslError),
|
||||
SslError(OpenSslError),
|
||||
|
||||
/// Failed to resolve the hostname
|
||||
#[display(fmt = "Failed resolving hostname: {}", _0)]
|
||||
@@ -118,11 +118,11 @@ pub enum SendRequestError {
|
||||
TunnelNotSupported,
|
||||
|
||||
/// Error sending request body
|
||||
Body(BoxError),
|
||||
Body(Error),
|
||||
|
||||
/// Other errors that can occur after submitting a request.
|
||||
#[display(fmt = "{:?}: {}", _1, _0)]
|
||||
Custom(BoxError, Box<dyn fmt::Debug>),
|
||||
Custom(Box<dyn StdError>, Box<dyn fmt::Debug>),
|
||||
}
|
||||
|
||||
impl std::error::Error for SendRequestError {}
|
||||
@@ -141,7 +141,7 @@ pub enum FreezeRequestError {
|
||||
|
||||
/// Other errors that can occur after submitting a request.
|
||||
#[display(fmt = "{:?}: {}", _1, _0)]
|
||||
Custom(BoxError, Box<dyn fmt::Debug>),
|
||||
Custom(Box<dyn StdError>, Box<dyn fmt::Debug>),
|
||||
}
|
||||
|
||||
impl std::error::Error for FreezeRequestError {}
|
||||
|
@@ -9,8 +9,11 @@ use actix_http::{
|
||||
body::{BodySize, MessageBody},
|
||||
error::PayloadError,
|
||||
h1,
|
||||
header::{HeaderMap, IntoHeaderValue, EXPECT, HOST},
|
||||
Payload, RequestHeadType, ResponseHead, StatusCode,
|
||||
http::{
|
||||
header::{HeaderMap, IntoHeaderValue, EXPECT, HOST},
|
||||
StatusCode,
|
||||
},
|
||||
Error, Payload, RequestHeadType, ResponseHead,
|
||||
};
|
||||
use actix_utils::future::poll_fn;
|
||||
use bytes::buf::BufMut;
|
||||
@@ -19,8 +22,6 @@ use futures_core::{ready, Stream};
|
||||
use futures_util::SinkExt as _;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::BoxError;
|
||||
|
||||
use super::connection::{ConnectionIo, H1Connection};
|
||||
use super::error::{ConnectError, SendRequestError};
|
||||
|
||||
@@ -32,7 +33,7 @@ pub(crate) async fn send_request<Io, B>(
|
||||
where
|
||||
Io: ConnectionIo,
|
||||
B: MessageBody,
|
||||
B::Error: Into<BoxError>,
|
||||
B::Error: Into<Error>,
|
||||
{
|
||||
// set request host header
|
||||
if !head.as_ref().headers.contains_key(HOST)
|
||||
@@ -154,7 +155,7 @@ pub(crate) async fn send_body<Io, B>(
|
||||
where
|
||||
Io: ConnectionIo,
|
||||
B: MessageBody,
|
||||
B::Error: Into<BoxError>,
|
||||
B::Error: Into<Error>,
|
||||
{
|
||||
actix_rt::pin!(body);
|
||||
|
||||
@@ -165,7 +166,7 @@ where
|
||||
Some(Ok(chunk)) => {
|
||||
framed.as_mut().write(h1::Message::Chunk(Some(chunk)))?;
|
||||
}
|
||||
Some(Err(err)) => return Err(SendRequestError::Body(err.into())),
|
||||
Some(Err(err)) => return Err(err.into().into()),
|
||||
None => {
|
||||
eof = true;
|
||||
framed.as_mut().write(h1::Message::Chunk(None))?;
|
||||
|
@@ -13,11 +13,9 @@ use log::trace;
|
||||
use actix_http::{
|
||||
body::{BodySize, MessageBody},
|
||||
header::HeaderMap,
|
||||
Payload, RequestHeadType, ResponseHead,
|
||||
Error, Payload, RequestHeadType, ResponseHead,
|
||||
};
|
||||
|
||||
use crate::BoxError;
|
||||
|
||||
use super::{
|
||||
config::ConnectorConfig,
|
||||
connection::{ConnectionIo, H2Connection},
|
||||
@@ -32,7 +30,7 @@ pub(crate) async fn send_request<Io, B>(
|
||||
where
|
||||
Io: ConnectionIo,
|
||||
B: MessageBody,
|
||||
B::Error: Into<BoxError>,
|
||||
B::Error: Into<Error>,
|
||||
{
|
||||
trace!("Sending client request: {:?} {:?}", head, body.size());
|
||||
|
||||
@@ -135,12 +133,10 @@ where
|
||||
async fn send_body<B>(body: B, mut send: SendStream<Bytes>) -> Result<(), SendRequestError>
|
||||
where
|
||||
B: MessageBody,
|
||||
B::Error: Into<BoxError>,
|
||||
B::Error: Into<Error>,
|
||||
{
|
||||
let mut buf = None;
|
||||
|
||||
actix_rt::pin!(body);
|
||||
|
||||
loop {
|
||||
if buf.is_none() {
|
||||
match poll_fn(|cx| body.as_mut().poll_next(cx)).await {
|
||||
@@ -148,10 +144,10 @@ where
|
||||
send.reserve_capacity(b.len());
|
||||
buf = Some(b);
|
||||
}
|
||||
Some(Err(err)) => return Err(SendRequestError::Body(err.into())),
|
||||
Some(Err(e)) => return Err(e.into().into()),
|
||||
None => {
|
||||
if let Err(err) = send.send_data(Bytes::new(), true) {
|
||||
return Err(err.into());
|
||||
if let Err(e) = send.send_data(Bytes::new(), true) {
|
||||
return Err(e.into());
|
||||
}
|
||||
send.reserve_capacity(0);
|
||||
return Ok(());
|
||||
|
@@ -7,17 +7,16 @@ use std::{
|
||||
};
|
||||
|
||||
use actix_codec::Framed;
|
||||
use actix_http::{h1::ClientCodec, Payload, RequestHead, RequestHeadType, ResponseHead};
|
||||
use actix_http::{
|
||||
body::AnyBody, h1::ClientCodec, Payload, RequestHead, RequestHeadType, ResponseHead,
|
||||
};
|
||||
use actix_service::Service;
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
|
||||
use crate::{
|
||||
any_body::AnyBody,
|
||||
client::{
|
||||
Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError,
|
||||
},
|
||||
response::ClientResponse,
|
||||
use crate::client::{
|
||||
Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError,
|
||||
};
|
||||
use crate::response::ClientResponse;
|
||||
|
||||
pub type BoxConnectorService = Rc<
|
||||
dyn Service<
|
||||
|
@@ -1,10 +1,9 @@
|
||||
//! HTTP client errors
|
||||
|
||||
pub use actix_http::{
|
||||
error::{HttpError, PayloadError},
|
||||
header::HeaderValue,
|
||||
error::PayloadError,
|
||||
http::{header::HeaderValue, Error as HttpError, StatusCode},
|
||||
ws::{HandshakeError as WsHandshakeError, ProtocolError as WsProtocolError},
|
||||
StatusCode,
|
||||
};
|
||||
|
||||
use derive_more::{Display, From};
|
||||
@@ -12,8 +11,6 @@ use serde_json::error::Error as JsonError;
|
||||
|
||||
pub use crate::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError};
|
||||
|
||||
// TODO: address display, error, and from impls
|
||||
|
||||
/// Websocket client error
|
||||
#[derive(Debug, Display, From)]
|
||||
pub enum WsClientError {
|
||||
|
@@ -1,19 +1,18 @@
|
||||
use std::{convert::TryFrom, net, rc::Rc, time::Duration};
|
||||
use std::{convert::TryFrom, error::Error as StdError, net, rc::Rc, time::Duration};
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures_core::Stream;
|
||||
use serde::Serialize;
|
||||
|
||||
use actix_http::{
|
||||
error::HttpError,
|
||||
header::{HeaderMap, HeaderName, IntoHeaderValue},
|
||||
Method, RequestHead, Uri,
|
||||
body::AnyBody,
|
||||
http::{header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, Method, Uri},
|
||||
RequestHead,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
any_body::AnyBody,
|
||||
sender::{RequestSender, SendClientRequest},
|
||||
BoxError, ClientConfig,
|
||||
ClientConfig,
|
||||
};
|
||||
|
||||
/// `FrozenClientRequest` struct represents cloneable client request.
|
||||
@@ -83,7 +82,7 @@ impl FrozenClientRequest {
|
||||
pub fn send_stream<S, E>(&self, stream: S) -> SendClientRequest
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
||||
E: Into<BoxError> + 'static,
|
||||
E: Into<Box<dyn StdError>> + 'static,
|
||||
{
|
||||
RequestSender::Rc(self.head.clone(), None).send_stream(
|
||||
self.addr,
|
||||
@@ -208,7 +207,7 @@ impl FrozenSendBuilder {
|
||||
pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
||||
E: Into<BoxError> + 'static,
|
||||
E: Into<Box<dyn StdError>> + 'static,
|
||||
{
|
||||
if let Some(e) = self.err {
|
||||
return e.into();
|
||||
|
@@ -104,7 +104,6 @@
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
mod any_body;
|
||||
mod builder;
|
||||
mod client;
|
||||
mod connect;
|
||||
@@ -117,8 +116,7 @@ mod sender;
|
||||
pub mod test;
|
||||
pub mod ws;
|
||||
|
||||
// TODO: hmmmmmm
|
||||
pub use actix_http as http;
|
||||
pub use actix_http::http;
|
||||
#[cfg(feature = "cookies")]
|
||||
pub use cookie;
|
||||
|
||||
@@ -132,14 +130,15 @@ pub use self::sender::SendClientRequest;
|
||||
|
||||
use std::{convert::TryFrom, rc::Rc, time::Duration};
|
||||
|
||||
use actix_http::{error::HttpError, header::HeaderMap, Method, RequestHead, Uri};
|
||||
use actix_http::{
|
||||
http::{Error as HttpError, HeaderMap, Method, Uri},
|
||||
RequestHead,
|
||||
};
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_service::Service;
|
||||
|
||||
use self::client::{ConnectInfo, TcpConnectError, TcpConnection};
|
||||
|
||||
pub(crate) type BoxError = Box<dyn std::error::Error>;
|
||||
|
||||
/// An asynchronous HTTP and WebSocket client.
|
||||
///
|
||||
/// You should take care to create, at most, one `Client` per thread. Otherwise, expect higher CPU
|
||||
|
@@ -7,18 +7,20 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_http::{header, Method, RequestHead, RequestHeadType, StatusCode, Uri};
|
||||
use actix_http::{
|
||||
body::AnyBody,
|
||||
http::{header, Method, StatusCode, Uri},
|
||||
RequestHead, RequestHeadType,
|
||||
};
|
||||
use actix_service::Service;
|
||||
use bytes::Bytes;
|
||||
use futures_core::ready;
|
||||
|
||||
use super::Transform;
|
||||
use crate::{
|
||||
any_body::AnyBody,
|
||||
client::{InvalidUrl, SendRequestError},
|
||||
connect::{ConnectRequest, ConnectResponse},
|
||||
ClientResponse,
|
||||
};
|
||||
|
||||
use crate::client::{InvalidUrl, SendRequestError};
|
||||
use crate::connect::{ConnectRequest, ConnectResponse};
|
||||
use crate::ClientResponse;
|
||||
|
||||
pub struct Redirect {
|
||||
max_redirect_times: u8,
|
||||
@@ -93,7 +95,7 @@ where
|
||||
};
|
||||
|
||||
let body_opt = match body {
|
||||
AnyBody::Bytes { ref body } => Some(body.clone()),
|
||||
AnyBody::Bytes(ref b) => Some(b.clone()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@@ -190,9 +192,7 @@ where
|
||||
let body_new = if is_redirect {
|
||||
// try to reuse body
|
||||
match body {
|
||||
Some(ref bytes) => AnyBody::Bytes {
|
||||
body: bytes.clone(),
|
||||
},
|
||||
Some(ref bytes) => AnyBody::Bytes(bytes.clone()),
|
||||
// TODO: should this be AnyBody::Empty or AnyBody::None.
|
||||
_ => AnyBody::empty(),
|
||||
}
|
||||
@@ -281,12 +281,12 @@ fn remove_sensitive_headers(headers: &mut header::HeaderMap, prev_uri: &Uri, nex
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use actix_web::{web, App, Error, HttpRequest, HttpResponse};
|
||||
|
||||
use super::*;
|
||||
use crate::{http::header::HeaderValue, ClientBuilder};
|
||||
use crate::http::HeaderValue;
|
||||
use crate::ClientBuilder;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_basic_redirect() {
|
||||
|
@@ -1,25 +1,26 @@
|
||||
use std::{convert::TryFrom, fmt, net, rc::Rc, time::Duration};
|
||||
use std::{convert::TryFrom, error::Error as StdError, fmt, net, rc::Rc, time::Duration};
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures_core::Stream;
|
||||
use serde::Serialize;
|
||||
|
||||
use actix_http::{
|
||||
error::HttpError,
|
||||
header::{self, HeaderMap, HeaderValue, IntoHeaderPair},
|
||||
ConnectionType, Method, RequestHead, Uri, Version,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
any_body::AnyBody,
|
||||
error::{FreezeRequestError, InvalidUrl},
|
||||
frozen::FrozenClientRequest,
|
||||
sender::{PrepForSendingError, RequestSender, SendClientRequest},
|
||||
BoxError, ClientConfig,
|
||||
body::AnyBody,
|
||||
http::{
|
||||
header::{self, IntoHeaderPair},
|
||||
ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version,
|
||||
},
|
||||
RequestHead,
|
||||
};
|
||||
|
||||
#[cfg(feature = "cookies")]
|
||||
use crate::cookie::{Cookie, CookieJar};
|
||||
use crate::{
|
||||
error::{FreezeRequestError, InvalidUrl},
|
||||
frozen::FrozenClientRequest,
|
||||
sender::{PrepForSendingError, RequestSender, SendClientRequest},
|
||||
ClientConfig,
|
||||
};
|
||||
|
||||
/// An HTTP Client request builder
|
||||
///
|
||||
@@ -403,7 +404,7 @@ impl ClientRequest {
|
||||
pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
||||
E: Into<BoxError> + 'static,
|
||||
E: Into<Box<dyn StdError>> + 'static,
|
||||
{
|
||||
let slf = match self.prep_for_sending() {
|
||||
Ok(slf) => slf,
|
||||
@@ -537,7 +538,7 @@ impl fmt::Debug for ClientRequest {
|
||||
mod tests {
|
||||
use std::time::SystemTime;
|
||||
|
||||
use actix_http::header::HttpDate;
|
||||
use actix_http::http::header::HttpDate;
|
||||
|
||||
use super::*;
|
||||
use crate::Client;
|
||||
|
@@ -10,8 +10,9 @@ use std::{
|
||||
};
|
||||
|
||||
use actix_http::{
|
||||
error::PayloadError, header, header::HeaderMap, Extensions, HttpMessage, Payload,
|
||||
PayloadStream, ResponseHead, StatusCode, Version,
|
||||
error::PayloadError,
|
||||
http::{header, HeaderMap, StatusCode, Version},
|
||||
Extensions, HttpMessage, Payload, PayloadStream, ResponseHead,
|
||||
};
|
||||
use actix_rt::time::{sleep, Sleep};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
@@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
error::Error as StdError,
|
||||
future::Future,
|
||||
net,
|
||||
pin::Pin,
|
||||
@@ -8,10 +9,12 @@ use std::{
|
||||
};
|
||||
|
||||
use actix_http::{
|
||||
body::BodyStream,
|
||||
error::HttpError,
|
||||
header::{self, HeaderMap, HeaderName, IntoHeaderValue},
|
||||
RequestHead, RequestHeadType,
|
||||
body::{AnyBody, BodyStream},
|
||||
http::{
|
||||
header::{self, HeaderMap, HeaderName, IntoHeaderValue},
|
||||
Error as HttpError,
|
||||
},
|
||||
Error, RequestHead, RequestHeadType,
|
||||
};
|
||||
use actix_rt::time::{sleep, Sleep};
|
||||
use bytes::Bytes;
|
||||
@@ -20,12 +23,11 @@ use futures_core::Stream;
|
||||
use serde::Serialize;
|
||||
|
||||
#[cfg(feature = "__compress")]
|
||||
use actix_http::{encoding::Decoder, header::ContentEncoding, Payload, PayloadStream};
|
||||
use actix_http::{encoding::Decoder, http::header::ContentEncoding, Payload, PayloadStream};
|
||||
|
||||
use crate::{
|
||||
any_body::AnyBody,
|
||||
error::{FreezeRequestError, InvalidUrl, SendRequestError},
|
||||
BoxError, ClientConfig, ClientResponse, ConnectRequest, ConnectResponse,
|
||||
ClientConfig, ClientResponse, ConnectRequest, ConnectResponse,
|
||||
};
|
||||
|
||||
#[derive(Debug, From)]
|
||||
@@ -160,6 +162,12 @@ impl From<SendRequestError> for SendClientRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for SendClientRequest {
|
||||
fn from(e: Error) -> Self {
|
||||
SendClientRequest::Err(Some(e.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HttpError> for SendClientRequest {
|
||||
fn from(e: HttpError) -> Self {
|
||||
SendClientRequest::Err(Some(e.into()))
|
||||
@@ -228,9 +236,7 @@ impl RequestSender {
|
||||
response_decompress,
|
||||
timeout,
|
||||
config,
|
||||
AnyBody::Bytes {
|
||||
body: Bytes::from(body),
|
||||
},
|
||||
AnyBody::Bytes(Bytes::from(body)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -259,9 +265,7 @@ impl RequestSender {
|
||||
response_decompress,
|
||||
timeout,
|
||||
config,
|
||||
AnyBody::Bytes {
|
||||
body: Bytes::from(body),
|
||||
},
|
||||
AnyBody::Bytes(Bytes::from(body)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -275,7 +279,7 @@ impl RequestSender {
|
||||
) -> SendClientRequest
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
||||
E: Into<BoxError> + 'static,
|
||||
E: Into<Box<dyn StdError>> + 'static,
|
||||
{
|
||||
self.send_body(
|
||||
addr,
|
||||
|
@@ -1,6 +1,7 @@
|
||||
//! Test helpers for actix http client to use during testing.
|
||||
|
||||
use actix_http::{h1, header::IntoHeaderPair, Payload, ResponseHead, StatusCode, Version};
|
||||
use actix_http::http::header::IntoHeaderPair;
|
||||
use actix_http::http::{StatusCode, Version};
|
||||
use actix_http::{h1, Payload, ResponseHead};
|
||||
use bytes::Bytes;
|
||||
|
||||
#[cfg(feature = "cookies")]
|
||||
@@ -88,7 +89,7 @@ impl TestResponse {
|
||||
|
||||
#[cfg(feature = "cookies")]
|
||||
for cookie in self.cookies.delta() {
|
||||
use actix_http::header::{self, HeaderValue};
|
||||
use actix_http::http::header::{self, HeaderValue};
|
||||
|
||||
head.headers.insert(
|
||||
header::SET_COOKIE,
|
||||
@@ -108,7 +109,7 @@ impl TestResponse {
|
||||
mod tests {
|
||||
use std::time::SystemTime;
|
||||
|
||||
use actix_http::header::HttpDate;
|
||||
use actix_http::http::header::HttpDate;
|
||||
|
||||
use super::*;
|
||||
use crate::{cookie, http::header};
|
||||
|
@@ -26,7 +26,9 @@
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use std::{convert::TryFrom, fmt, net::SocketAddr, str};
|
||||
use std::convert::TryFrom;
|
||||
use std::net::SocketAddr;
|
||||
use std::{fmt, str};
|
||||
|
||||
use actix_codec::Framed;
|
||||
use actix_http::{ws, Payload, RequestHead};
|
||||
@@ -35,19 +37,14 @@ use actix_service::Service;
|
||||
|
||||
pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message};
|
||||
|
||||
use crate::{
|
||||
connect::{BoxedSocket, ConnectRequest},
|
||||
error::{HttpError, InvalidUrl, SendRequestError, WsClientError},
|
||||
http::{
|
||||
header::{self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION},
|
||||
ConnectionType, Method, StatusCode, Uri, Version,
|
||||
},
|
||||
response::ClientResponse,
|
||||
ClientConfig,
|
||||
};
|
||||
|
||||
use crate::connect::{BoxedSocket, ConnectRequest};
|
||||
#[cfg(feature = "cookies")]
|
||||
use crate::cookie::{Cookie, CookieJar};
|
||||
use crate::error::{InvalidUrl, SendRequestError, WsClientError};
|
||||
use crate::http::header::{self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION};
|
||||
use crate::http::{ConnectionType, Error as HttpError, Method, StatusCode, Uri, Version};
|
||||
use crate::response::ClientResponse;
|
||||
use crate::ClientConfig;
|
||||
|
||||
/// WebSocket connection.
|
||||
pub struct WebsocketsRequest {
|
||||
|
@@ -21,7 +21,10 @@ use brotli2::write::BrotliEncoder;
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
use flate2::{read::GzDecoder, write::GzEncoder, Compression};
|
||||
|
||||
use actix_http::{ContentEncoding, HttpService, StatusCode};
|
||||
use actix_http::{
|
||||
http::{self, StatusCode},
|
||||
HttpService,
|
||||
};
|
||||
use actix_http_test::test_server;
|
||||
use actix_service::{fn_service, map_config, ServiceFactoryExt as _};
|
||||
use actix_web::{
|
||||
@@ -644,7 +647,9 @@ async fn test_client_brotli_encoding_large_random() {
|
||||
async fn test_client_deflate_encoding() {
|
||||
let srv = actix_test::start(|| {
|
||||
App::new().default_service(web::to(|body: Bytes| {
|
||||
HttpResponse::Ok().encoding(ContentEncoding::Br).body(body)
|
||||
HttpResponse::Ok()
|
||||
.encoding(http::ContentEncoding::Br)
|
||||
.body(body)
|
||||
}))
|
||||
});
|
||||
|
||||
@@ -667,7 +672,9 @@ async fn test_client_deflate_encoding_large_random() {
|
||||
|
||||
let srv = actix_test::start(|| {
|
||||
App::new().default_service(web::to(|body: Bytes| {
|
||||
HttpResponse::Ok().encoding(ContentEncoding::Br).body(body)
|
||||
HttpResponse::Ok()
|
||||
.encoding(http::ContentEncoding::Br)
|
||||
.body(body)
|
||||
}))
|
||||
});
|
||||
|
||||
@@ -685,7 +692,7 @@ async fn test_client_streaming_explicit() {
|
||||
let srv = actix_test::start(|| {
|
||||
App::new().default_service(web::to(|body: web::Payload| {
|
||||
HttpResponse::Ok()
|
||||
.encoding(ContentEncoding::Identity)
|
||||
.encoding(http::ContentEncoding::Identity)
|
||||
.streaming(body)
|
||||
}))
|
||||
});
|
||||
@@ -710,7 +717,7 @@ async fn test_body_streaming_implicit() {
|
||||
});
|
||||
|
||||
HttpResponse::Ok()
|
||||
.encoding(ContentEncoding::Gzip)
|
||||
.encoding(http::ContentEncoding::Gzip)
|
||||
.streaming(Box::pin(body))
|
||||
}))
|
||||
});
|
||||
|
@@ -1,10 +1,9 @@
|
||||
use std::{future::Future, time::Instant};
|
||||
|
||||
use actix_http::body::BoxBody;
|
||||
use actix_utils::future::{ready, Ready};
|
||||
use actix_web::{
|
||||
error, http::StatusCode, test::TestRequest, Error, HttpRequest, HttpResponse, Responder,
|
||||
};
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::test::TestRequest;
|
||||
use actix_web::{error, Error, HttpRequest, HttpResponse, Responder};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use futures_util::future::{join_all, Either};
|
||||
|
||||
@@ -51,9 +50,7 @@ where
|
||||
}
|
||||
|
||||
impl Responder for StringResponder {
|
||||
type Body = BoxBody;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||
HttpResponse::build(StatusCode::OK)
|
||||
.content_type("text/plain; charset=utf-8")
|
||||
.body(self.0)
|
||||
@@ -61,11 +58,9 @@ impl Responder for StringResponder {
|
||||
}
|
||||
|
||||
impl<T: Responder> Responder for OptionResponder<T> {
|
||||
type Body = BoxBody;
|
||||
|
||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||
match self.0 {
|
||||
Some(t) => t.respond_to(req).map_into_boxed_body(),
|
||||
Some(t) => t.respond_to(req),
|
||||
None => HttpResponse::from_error(error::ErrorInternalServerError("err")),
|
||||
}
|
||||
}
|
||||
|
@@ -6,8 +6,7 @@
|
||||
|
||||
use std::{any::Any, io, net::SocketAddr};
|
||||
|
||||
use actix_http::CloneableExtensions;
|
||||
use actix_web::{rt::net::TcpStream, web, App, HttpServer};
|
||||
use actix_web::{dev::Extensions, rt::net::TcpStream, web, App, HttpServer};
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -24,7 +23,7 @@ async fn route_whoami(conn_info: web::ReqData<ConnectionInfo>) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
fn get_conn_info(connection: &dyn Any, data: &mut CloneableExtensions) {
|
||||
fn get_conn_info(connection: &dyn Any, data: &mut Extensions) {
|
||||
if let Some(sock) = connection.downcast_ref::<TcpStream>() {
|
||||
data.insert(ConnectionInfo {
|
||||
bind: sock.local_addr().unwrap(),
|
||||
|
@@ -14,5 +14,3 @@ cargo test --lib --tests -p=actix-test --all-features
|
||||
cargo test --lib --tests -p=actix-files
|
||||
cargo test --lib --tests -p=actix-multipart --all-features
|
||||
cargo test --lib --tests -p=actix-web-actors --all-features
|
||||
|
||||
cargo test --workspace --doc
|
||||
|
69
src/app.rs
69
src/app.rs
@@ -1,35 +1,37 @@
|
||||
use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, rc::Rc};
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_http::{
|
||||
body::{BoxBody, MessageBody},
|
||||
Extensions, Request,
|
||||
};
|
||||
use actix_http::body::{AnyBody, MessageBody};
|
||||
use actix_http::{Extensions, Request};
|
||||
use actix_service::boxed::{self, BoxServiceFactory};
|
||||
use actix_service::{
|
||||
apply, apply_fn_factory, boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt,
|
||||
Transform,
|
||||
apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform,
|
||||
};
|
||||
use futures_util::future::FutureExt as _;
|
||||
|
||||
use crate::{
|
||||
app_service::{AppEntry, AppInit, AppRoutingFactory},
|
||||
config::ServiceConfig,
|
||||
data::{Data, DataFactory, FnDataFactory},
|
||||
dev::ResourceDef,
|
||||
error::Error,
|
||||
resource::Resource,
|
||||
route::Route,
|
||||
service::{
|
||||
AppServiceFactory, BoxedHttpServiceFactory, HttpServiceFactory, ServiceFactoryWrapper,
|
||||
ServiceRequest, ServiceResponse,
|
||||
},
|
||||
use crate::app_service::{AppEntry, AppInit, AppRoutingFactory};
|
||||
use crate::config::ServiceConfig;
|
||||
use crate::data::{Data, DataFactory, FnDataFactory};
|
||||
use crate::dev::ResourceDef;
|
||||
use crate::error::Error;
|
||||
use crate::resource::Resource;
|
||||
use crate::route::Route;
|
||||
use crate::service::{
|
||||
AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest,
|
||||
ServiceResponse,
|
||||
};
|
||||
|
||||
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||
|
||||
/// Application builder - structure that follows the builder pattern
|
||||
/// for building application instances.
|
||||
pub struct App<T, B> {
|
||||
endpoint: T,
|
||||
services: Vec<Box<dyn AppServiceFactory>>,
|
||||
default: Option<Rc<BoxedHttpServiceFactory>>,
|
||||
default: Option<Rc<HttpNewService>>,
|
||||
factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
||||
data_factories: Vec<FnDataFactory>,
|
||||
external: Vec<ResourceDef>,
|
||||
@@ -37,7 +39,7 @@ pub struct App<T, B> {
|
||||
_phantom: PhantomData<B>,
|
||||
}
|
||||
|
||||
impl App<AppEntry, BoxBody> {
|
||||
impl App<AppEntry, AnyBody> {
|
||||
/// Create application builder. Application can be configured with a builder-like pattern.
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
@@ -140,6 +142,10 @@ where
|
||||
/// Add application data factory. This function is similar to `.data()` but it accepts a
|
||||
/// "data factory". Data values are constructed asynchronously during application
|
||||
/// initialization, before the server starts accepting requests.
|
||||
#[deprecated(
|
||||
since = "4.0.0",
|
||||
note = "Construct data value before starting server and use `.app_data(Data::new(val))` instead."
|
||||
)]
|
||||
pub fn data_factory<F, Out, D, E>(mut self, data: F) -> Self
|
||||
where
|
||||
F: Fn() -> Out + 'static,
|
||||
@@ -281,7 +287,7 @@ where
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub fn default_service<F, U>(mut self, svc: F) -> Self
|
||||
pub fn default_service<F, U>(mut self, f: F) -> Self
|
||||
where
|
||||
F: IntoServiceFactory<U, ServiceRequest>,
|
||||
U: ServiceFactory<
|
||||
@@ -292,12 +298,10 @@ where
|
||||
> + 'static,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
let svc = svc
|
||||
.into_factory()
|
||||
.map(|res| res.map_into_boxed_body())
|
||||
.map_init_err(|e| log::error!("Can not construct default service: {:?}", e));
|
||||
|
||||
self.default = Some(Rc::new(boxed::factory(svc)));
|
||||
// create and configure default resource
|
||||
self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err(
|
||||
|e| log::error!("Can not construct default service: {:?}", e),
|
||||
))));
|
||||
|
||||
self
|
||||
}
|
||||
@@ -353,7 +357,7 @@ where
|
||||
/// ```
|
||||
/// use actix_service::Service;
|
||||
/// use actix_web::{middleware, web, App};
|
||||
/// use actix_web::http::header::{CONTENT_TYPE, HeaderValue};
|
||||
/// use actix_web::http::{header::CONTENT_TYPE, HeaderValue};
|
||||
///
|
||||
/// async fn index() -> &'static str {
|
||||
/// "Welcome!"
|
||||
@@ -410,7 +414,7 @@ where
|
||||
/// ```
|
||||
/// use actix_service::Service;
|
||||
/// use actix_web::{web, App};
|
||||
/// use actix_web::http::header::{CONTENT_TYPE, HeaderValue};
|
||||
/// use actix_web::http::{header::CONTENT_TYPE, HeaderValue};
|
||||
///
|
||||
/// async fn index() -> &'static str {
|
||||
/// "Welcome!"
|
||||
@@ -494,10 +498,7 @@ mod tests {
|
||||
use bytes::Bytes;
|
||||
|
||||
use super::*;
|
||||
use crate::http::{
|
||||
header::{self, HeaderValue},
|
||||
Method, StatusCode,
|
||||
};
|
||||
use crate::http::{header, HeaderValue, Method, StatusCode};
|
||||
use crate::middleware::DefaultHeaders;
|
||||
use crate::service::ServiceRequest;
|
||||
use crate::test::{call_service, init_service, read_body, try_init_service, TestRequest};
|
||||
|
@@ -2,7 +2,10 @@ use std::{cell::RefCell, mem, rc::Rc};
|
||||
|
||||
use actix_http::{Extensions, Request};
|
||||
use actix_router::{Path, ResourceDef, Router, Url};
|
||||
use actix_service::{boxed, fn_service, Service, ServiceFactory};
|
||||
use actix_service::{
|
||||
boxed::{self, BoxService, BoxServiceFactory},
|
||||
fn_service, Service, ServiceFactory,
|
||||
};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use futures_util::future::join_all;
|
||||
|
||||
@@ -12,14 +15,13 @@ use crate::{
|
||||
guard::Guard,
|
||||
request::{HttpRequest, HttpRequestPool},
|
||||
rmap::ResourceMap,
|
||||
service::{
|
||||
AppServiceFactory, BoxedHttpService, BoxedHttpServiceFactory, ServiceRequest,
|
||||
ServiceResponse,
|
||||
},
|
||||
service::{AppServiceFactory, ServiceRequest, ServiceResponse},
|
||||
Error, HttpResponse,
|
||||
};
|
||||
|
||||
type Guards = Vec<Box<dyn Guard>>;
|
||||
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
|
||||
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||
|
||||
/// Service factory to convert `Request` to a `ServiceRequest<S>`.
|
||||
/// It also executes data factories.
|
||||
@@ -37,7 +39,7 @@ where
|
||||
pub(crate) extensions: RefCell<Option<Extensions>>,
|
||||
pub(crate) async_data_factories: Rc<[FnDataFactory]>,
|
||||
pub(crate) services: Rc<RefCell<Vec<Box<dyn AppServiceFactory>>>>,
|
||||
pub(crate) default: Option<Rc<BoxedHttpServiceFactory>>,
|
||||
pub(crate) default: Option<Rc<HttpNewService>>,
|
||||
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
||||
pub(crate) external: RefCell<Vec<ResourceDef>>,
|
||||
}
|
||||
@@ -228,14 +230,8 @@ where
|
||||
}
|
||||
|
||||
pub struct AppRoutingFactory {
|
||||
services: Rc<
|
||||
[(
|
||||
ResourceDef,
|
||||
BoxedHttpServiceFactory,
|
||||
RefCell<Option<Guards>>,
|
||||
)],
|
||||
>,
|
||||
default: Rc<BoxedHttpServiceFactory>,
|
||||
services: Rc<[(ResourceDef, HttpNewService, RefCell<Option<Guards>>)]>,
|
||||
default: Rc<HttpNewService>,
|
||||
}
|
||||
|
||||
impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
|
||||
@@ -283,8 +279,8 @@ impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
|
||||
|
||||
/// The Actix Web router default entry point.
|
||||
pub struct AppRouting {
|
||||
router: Router<BoxedHttpService, Guards>,
|
||||
default: BoxedHttpService,
|
||||
router: Router<HttpService, Guards>,
|
||||
default: HttpService,
|
||||
}
|
||||
|
||||
impl Service<ServiceRequest> for AppRouting {
|
||||
|
@@ -284,7 +284,7 @@ mod tests {
|
||||
async fn test_data_from_arc() {
|
||||
let data_new = Data::new(String::from("test-123"));
|
||||
let data_from_arc = Data::from(Arc::new(String::from("test-123")));
|
||||
assert_eq!(data_new.0, data_from_arc.0);
|
||||
assert_eq!(data_new.0, data_from_arc.0)
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
|
58
src/dev.rs
58
src/dev.rs
@@ -1,7 +1,7 @@
|
||||
//! Lower-level types and re-exports.
|
||||
//!
|
||||
//! Most users will not have to interact with the types in this module, but it is useful for those
|
||||
//! writing extractors, middleware, libraries, or interacting with the service API directly.
|
||||
//! writing extractors, middleware and libraries, or interacting with the service API directly.
|
||||
|
||||
pub use crate::config::{AppConfig, AppService};
|
||||
#[doc(hidden)]
|
||||
@@ -14,20 +14,20 @@ pub use crate::types::form::UrlEncoded;
|
||||
pub use crate::types::json::JsonBody;
|
||||
pub use crate::types::readlines::Readlines;
|
||||
|
||||
pub use actix_http::{
|
||||
CloneableExtensions, Extensions, Payload, PayloadStream, RequestHead, Response,
|
||||
ResponseHead,
|
||||
};
|
||||
#[allow(deprecated)]
|
||||
pub use actix_http::body::{AnyBody, Body, BodySize, MessageBody, SizedStream};
|
||||
|
||||
#[cfg(feature = "__compress")]
|
||||
pub use actix_http::encoding::Decoder as Decompress;
|
||||
pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, Response, ResponseHead};
|
||||
pub use actix_router::{Path, ResourceDef, ResourcePath, Url};
|
||||
pub use actix_server::{Server, ServerHandle};
|
||||
pub use actix_service::{
|
||||
always_ready, fn_factory, fn_service, forward_ready, Service, ServiceFactory, Transform,
|
||||
};
|
||||
|
||||
#[cfg(feature = "__compress")]
|
||||
pub use actix_http::encoding::Decoder as Decompress;
|
||||
|
||||
use crate::http::header::ContentEncoding;
|
||||
use actix_http::ResponseBuilder;
|
||||
|
||||
use actix_router::Patterns;
|
||||
|
||||
@@ -62,7 +62,7 @@ pub trait BodyEncoding {
|
||||
fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self;
|
||||
}
|
||||
|
||||
impl BodyEncoding for actix_http::ResponseBuilder {
|
||||
impl BodyEncoding for ResponseBuilder {
|
||||
fn get_encoding(&self) -> Option<ContentEncoding> {
|
||||
self.extensions().get::<Enc>().map(|enc| enc.0)
|
||||
}
|
||||
@@ -73,7 +73,7 @@ impl BodyEncoding for actix_http::ResponseBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> BodyEncoding for actix_http::Response<B> {
|
||||
impl<B> BodyEncoding for Response<B> {
|
||||
fn get_encoding(&self) -> Option<ContentEncoding> {
|
||||
self.extensions().get::<Enc>().map(|enc| enc.0)
|
||||
}
|
||||
@@ -105,41 +105,3 @@ impl<B> BodyEncoding for crate::HttpResponse<B> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove this if it doesn't appear to be needed
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum AnyBody {
|
||||
None,
|
||||
Full { body: crate::web::Bytes },
|
||||
Boxed { body: actix_http::body::BoxBody },
|
||||
}
|
||||
|
||||
impl crate::body::MessageBody for AnyBody {
|
||||
type Error = crate::BoxError;
|
||||
|
||||
/// Body size hint.
|
||||
fn size(&self) -> crate::body::BodySize {
|
||||
match self {
|
||||
AnyBody::None => crate::body::BodySize::None,
|
||||
AnyBody::Full { body } => body.size(),
|
||||
AnyBody::Boxed { body } => body.size(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to pull out the next chunk of body bytes.
|
||||
fn poll_next(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Result<crate::web::Bytes, Self::Error>>> {
|
||||
match self.get_mut() {
|
||||
AnyBody::None => std::task::Poll::Ready(None),
|
||||
AnyBody::Full { body } => {
|
||||
let bytes = std::mem::take(body);
|
||||
std::task::Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
AnyBody::Boxed { body } => body.as_pin_mut().poll_next(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use std::{error::Error as StdError, fmt};
|
||||
|
||||
use actix_http::{body::BoxBody, Response};
|
||||
use actix_http::{body::AnyBody, Response};
|
||||
|
||||
use crate::{HttpResponse, ResponseError};
|
||||
|
||||
@@ -69,8 +69,8 @@ impl<T: ResponseError + 'static> From<T> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for Response<BoxBody> {
|
||||
fn from(err: Error) -> Response<BoxBody> {
|
||||
impl From<Error> for Response<AnyBody> {
|
||||
fn from(err: Error) -> Response<AnyBody> {
|
||||
err.error_response().into()
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,6 @@
|
||||
use std::{cell::RefCell, fmt, io::Write as _};
|
||||
|
||||
use actix_http::{
|
||||
body::BoxBody,
|
||||
header::{self, IntoHeaderValue as _},
|
||||
StatusCode,
|
||||
};
|
||||
use actix_http::{body::AnyBody, header, StatusCode};
|
||||
use bytes::{BufMut as _, BytesMut};
|
||||
|
||||
use crate::{Error, HttpRequest, HttpResponse, Responder, ResponseError};
|
||||
@@ -88,10 +84,11 @@ where
|
||||
let mut buf = BytesMut::new().writer();
|
||||
let _ = write!(buf, "{}", self);
|
||||
|
||||
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
|
||||
res.set_body(BoxBody::new(buf.into_inner()))
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("text/plain; charset=utf-8"),
|
||||
);
|
||||
res.set_body(AnyBody::from(buf.into_inner()))
|
||||
}
|
||||
|
||||
InternalErrorType::Response(ref resp) => {
|
||||
@@ -109,9 +106,7 @@ impl<T> Responder for InternalError<T>
|
||||
where
|
||||
T: fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
type Body = BoxBody;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||
HttpResponse::from_error(self)
|
||||
}
|
||||
}
|
||||
|
@@ -97,7 +97,7 @@ mod tests {
|
||||
let resp_body: &mut dyn MB = &mut body;
|
||||
let body = resp_body.downcast_ref::<String>().unwrap();
|
||||
assert_eq!(body, "hello cast");
|
||||
let body = resp_body.downcast_mut::<String>().unwrap();
|
||||
let body = &mut resp_body.downcast_mut::<String>().unwrap();
|
||||
body.push('!');
|
||||
let body = resp_body.downcast_ref::<String>().unwrap();
|
||||
assert_eq!(body, "hello cast!");
|
||||
|
@@ -1,12 +1,6 @@
|
||||
//! Error and Result module
|
||||
|
||||
/// This is meant to be a glob import of the whole error module, but rustdoc can't handle
|
||||
/// shadowing `Error` type, so it is expanded manually.
|
||||
/// See https://github.com/rust-lang/rust/issues/83375
|
||||
pub use actix_http::error::{
|
||||
BlockingError, ContentTypeError, DispatchError, HttpError, ParseError, PayloadError,
|
||||
};
|
||||
|
||||
pub use actix_http::error::*;
|
||||
use derive_more::{Display, Error, From};
|
||||
use serde_json::error::Error as JsonError;
|
||||
use serde_urlencoded::de::Error as FormDeError;
|
||||
@@ -35,15 +29,15 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
#[derive(Debug, PartialEq, Display, Error, From)]
|
||||
#[non_exhaustive]
|
||||
pub enum UrlGenerationError {
|
||||
/// Resource not found.
|
||||
/// Resource not found
|
||||
#[display(fmt = "Resource not found")]
|
||||
ResourceNotFound,
|
||||
|
||||
/// Not all URL parameters covered.
|
||||
#[display(fmt = "Not all URL parameters covered")]
|
||||
/// Not all path pattern covered
|
||||
#[display(fmt = "Not all path pattern covered")]
|
||||
NotEnoughElements,
|
||||
|
||||
/// URL parse error.
|
||||
/// URL parse error
|
||||
#[display(fmt = "{}", _0)]
|
||||
ParseError(UrlParseError),
|
||||
}
|
||||
|
@@ -6,17 +6,11 @@ use std::{
|
||||
io::{self, Write as _},
|
||||
};
|
||||
|
||||
use actix_http::{
|
||||
body::BoxBody,
|
||||
header::{self, IntoHeaderValue},
|
||||
Response, StatusCode,
|
||||
};
|
||||
use actix_http::{body::AnyBody, header, Response, StatusCode};
|
||||
use bytes::BytesMut;
|
||||
|
||||
use crate::{
|
||||
error::{downcast_dyn, downcast_get_type_id},
|
||||
helpers, HttpResponse,
|
||||
};
|
||||
use crate::error::{downcast_dyn, downcast_get_type_id};
|
||||
use crate::{helpers, HttpResponse};
|
||||
|
||||
/// Errors that can generate responses.
|
||||
// TODO: add std::error::Error bound when replacement for Box<dyn Error> is found
|
||||
@@ -33,16 +27,18 @@ pub trait ResponseError: fmt::Debug + fmt::Display {
|
||||
///
|
||||
/// By default, the generated response uses a 500 Internal Server Error status code, a
|
||||
/// `Content-Type` of `text/plain`, and the body is set to `Self`'s `Display` impl.
|
||||
fn error_response(&self) -> HttpResponse<BoxBody> {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let mut res = HttpResponse::new(self.status_code());
|
||||
|
||||
let mut buf = BytesMut::new();
|
||||
let _ = write!(helpers::MutWriter(&mut buf), "{}", self);
|
||||
|
||||
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("text/plain; charset=utf-8"),
|
||||
);
|
||||
|
||||
res.set_body(BoxBody::new(buf))
|
||||
res.set_body(AnyBody::from(buf))
|
||||
}
|
||||
|
||||
downcast_get_type_id!();
|
||||
@@ -90,8 +86,8 @@ impl ResponseError for actix_http::Error {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse<BoxBody> {
|
||||
HttpResponse::with_body(self.status_code(), self.to_string()).map_into_boxed_body()
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::new(self.status_code()).set_body(self.to_string().into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,8 +123,8 @@ impl ResponseError for actix_http::error::ContentTypeError {
|
||||
}
|
||||
|
||||
impl ResponseError for actix_http::ws::HandshakeError {
|
||||
fn error_response(&self) -> HttpResponse<BoxBody> {
|
||||
Response::from(self).map_into_boxed_body().into()
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
Response::from(self).into()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,7 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_http::{Method, Uri};
|
||||
use actix_http::http::{Method, Uri};
|
||||
use actix_utils::future::{ok, Ready};
|
||||
use futures_core::ready;
|
||||
use pin_project_lite::pin_project;
|
||||
@@ -402,7 +402,7 @@ mod tuple_from_req {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_http::header;
|
||||
use actix_http::http::header;
|
||||
use bytes::Bytes;
|
||||
use serde::Deserialize;
|
||||
|
||||
|
32
src/guard.rs
32
src/guard.rs
@@ -24,13 +24,13 @@
|
||||
//! );
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::{convert::TryFrom, ops::Deref};
|
||||
|
||||
use actix_http::{header, uri::Uri, Method as HttpMethod, RequestHead};
|
||||
use actix_http::http::{self, header, uri::Uri};
|
||||
use actix_http::RequestHead;
|
||||
|
||||
/// Trait defines resource guards. Guards are used for route selection.
|
||||
///
|
||||
@@ -186,7 +186,7 @@ impl Guard for NotGuard {
|
||||
|
||||
/// HTTP method guard.
|
||||
#[doc(hidden)]
|
||||
pub struct MethodGuard(HttpMethod);
|
||||
pub struct MethodGuard(http::Method);
|
||||
|
||||
impl Guard for MethodGuard {
|
||||
fn check(&self, request: &RequestHead) -> bool {
|
||||
@@ -196,51 +196,51 @@ impl Guard for MethodGuard {
|
||||
|
||||
/// Guard to match *GET* HTTP method.
|
||||
pub fn Get() -> MethodGuard {
|
||||
MethodGuard(HttpMethod::GET)
|
||||
MethodGuard(http::Method::GET)
|
||||
}
|
||||
|
||||
/// Predicate to match *POST* HTTP method.
|
||||
pub fn Post() -> MethodGuard {
|
||||
MethodGuard(HttpMethod::POST)
|
||||
MethodGuard(http::Method::POST)
|
||||
}
|
||||
|
||||
/// Predicate to match *PUT* HTTP method.
|
||||
pub fn Put() -> MethodGuard {
|
||||
MethodGuard(HttpMethod::PUT)
|
||||
MethodGuard(http::Method::PUT)
|
||||
}
|
||||
|
||||
/// Predicate to match *DELETE* HTTP method.
|
||||
pub fn Delete() -> MethodGuard {
|
||||
MethodGuard(HttpMethod::DELETE)
|
||||
MethodGuard(http::Method::DELETE)
|
||||
}
|
||||
|
||||
/// Predicate to match *HEAD* HTTP method.
|
||||
pub fn Head() -> MethodGuard {
|
||||
MethodGuard(HttpMethod::HEAD)
|
||||
MethodGuard(http::Method::HEAD)
|
||||
}
|
||||
|
||||
/// Predicate to match *OPTIONS* HTTP method.
|
||||
pub fn Options() -> MethodGuard {
|
||||
MethodGuard(HttpMethod::OPTIONS)
|
||||
MethodGuard(http::Method::OPTIONS)
|
||||
}
|
||||
|
||||
/// Predicate to match *CONNECT* HTTP method.
|
||||
pub fn Connect() -> MethodGuard {
|
||||
MethodGuard(HttpMethod::CONNECT)
|
||||
MethodGuard(http::Method::CONNECT)
|
||||
}
|
||||
|
||||
/// Predicate to match *PATCH* HTTP method.
|
||||
pub fn Patch() -> MethodGuard {
|
||||
MethodGuard(HttpMethod::PATCH)
|
||||
MethodGuard(http::Method::PATCH)
|
||||
}
|
||||
|
||||
/// Predicate to match *TRACE* HTTP method.
|
||||
pub fn Trace() -> MethodGuard {
|
||||
MethodGuard(HttpMethod::TRACE)
|
||||
MethodGuard(http::Method::TRACE)
|
||||
}
|
||||
|
||||
/// Predicate to match specified HTTP method.
|
||||
pub fn Method(method: HttpMethod) -> MethodGuard {
|
||||
pub fn Method(method: http::Method) -> MethodGuard {
|
||||
MethodGuard(method)
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ impl Guard for HostGuard {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_http::{header, Method};
|
||||
use actix_http::http::{header, Method};
|
||||
|
||||
use super::*;
|
||||
use crate::test::TestRequest;
|
||||
|
@@ -1,21 +1,21 @@
|
||||
use std::future::Future;
|
||||
|
||||
use actix_service::{boxed, fn_service};
|
||||
use actix_service::{
|
||||
boxed::{self, BoxServiceFactory},
|
||||
fn_service,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
body::MessageBody,
|
||||
service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse},
|
||||
BoxError, FromRequest, HttpResponse, Responder,
|
||||
service::{ServiceRequest, ServiceResponse},
|
||||
Error, FromRequest, HttpResponse, Responder,
|
||||
};
|
||||
|
||||
/// A request handler is an async function that accepts zero or more parameters that can be
|
||||
/// extracted from a request (i.e., [`impl FromRequest`]) and returns a type that can be converted
|
||||
/// into an [`HttpResponse`] (that is, it impls the [`Responder`] trait).
|
||||
/// extracted from a request (i.e., [`impl FromRequest`](crate::FromRequest)) and returns a type
|
||||
/// that can be converted into an [`HttpResponse`] (that is, it impls the [`Responder`] trait).
|
||||
///
|
||||
/// If you got the error `the trait Handler<_, _, _> is not implemented`, then your function is not
|
||||
/// a valid handler. See <https://actix.rs/docs/handlers> for more information.
|
||||
///
|
||||
/// [`impl FromRequest`]: crate::FromRequest
|
||||
/// a valid handler. See [Request Handlers](https://actix.rs/docs/handlers/) for more information.
|
||||
pub trait Handler<T, R>: Clone + 'static
|
||||
where
|
||||
R: Future,
|
||||
@@ -24,44 +24,29 @@ where
|
||||
fn call(&self, param: T) -> R;
|
||||
}
|
||||
|
||||
pub(crate) fn handler_service<F, T, R>(handler: F) -> BoxedHttpServiceFactory
|
||||
pub fn handler_service<F, T, R>(
|
||||
handler: F,
|
||||
) -> BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>
|
||||
where
|
||||
F: Handler<T, R>,
|
||||
T: FromRequest,
|
||||
R: Future,
|
||||
R::Output: Responder,
|
||||
<R::Output as Responder>::Body: MessageBody,
|
||||
<<R::Output as Responder>::Body as MessageBody>::Error: Into<BoxError>,
|
||||
{
|
||||
boxed::factory(fn_service(move |req: ServiceRequest| {
|
||||
let handler = handler.clone();
|
||||
|
||||
async move {
|
||||
let (req, mut payload) = req.into_parts();
|
||||
|
||||
let res = match T::from_request(&req, &mut payload).await {
|
||||
Err(err) => HttpResponse::from_error(err),
|
||||
|
||||
Ok(data) => handler
|
||||
.call(data)
|
||||
.await
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body(),
|
||||
Ok(data) => handler.call(data).await.respond_to(&req),
|
||||
};
|
||||
|
||||
Ok(ServiceResponse::new(req, res))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
/// Generates a [`Handler`] trait impl for N-ary functions where N is specified with a sequence of
|
||||
/// space separated type parameters.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// factory_tuple! {} // implements Handler for types: fn() -> Res
|
||||
/// factory_tuple! { A B C } // implements Handler for types: fn(A, B, C) -> Res
|
||||
/// ```
|
||||
/// FromRequest trait impl for tuples
|
||||
macro_rules! factory_tuple ({ $($param:ident)* } => {
|
||||
impl<Func, $($param,)* Res> Handler<($($param,)*), Res> for Func
|
||||
where Func: Fn($($param),*) -> Res + Clone + 'static,
|
||||
|
@@ -2,7 +2,7 @@ use std::cmp::Ordering;
|
||||
|
||||
use mime::Mime;
|
||||
|
||||
use super::QualityItem;
|
||||
use super::{qitem, QualityItem};
|
||||
use crate::http::header;
|
||||
|
||||
crate::http::header::common_header! {
|
||||
@@ -34,40 +34,46 @@ crate::http::header::common_header! {
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{Accept, QualityItem};
|
||||
/// use actix_web::http::header::{Accept, qitem};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// Accept(vec![
|
||||
/// QualityItem::max(mime::TEXT_HTML),
|
||||
/// qitem(mime::TEXT_HTML),
|
||||
/// ])
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{Accept, QualityItem};
|
||||
/// use actix_web::http::header::{Accept, qitem};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// Accept(vec![
|
||||
/// QualityItem::max(mime::APPLICATION_JSON),
|
||||
/// qitem(mime::APPLICATION_JSON),
|
||||
/// ])
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{Accept, QualityItem, q};
|
||||
/// use actix_web::http::header::{Accept, QualityItem, q, qitem};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// Accept(vec![
|
||||
/// QualityItem::max(mime::TEXT_HTML),
|
||||
/// QualityItem::max("application/xhtml+xml".parse().unwrap()),
|
||||
/// QualityItem::new(mime::TEXT_XML, q(0.9)),
|
||||
/// QualityItem::max("image/webp".parse().unwrap()),
|
||||
/// QualityItem::new(mime::STAR_STAR, q(0.8)),
|
||||
/// qitem(mime::TEXT_HTML),
|
||||
/// qitem("application/xhtml+xml".parse().unwrap()),
|
||||
/// QualityItem::new(
|
||||
/// mime::TEXT_XML,
|
||||
/// q(900)
|
||||
/// ),
|
||||
/// qitem("image/webp".parse().unwrap()),
|
||||
/// QualityItem::new(
|
||||
/// mime::STAR_STAR,
|
||||
/// q(800)
|
||||
/// ),
|
||||
/// ])
|
||||
/// );
|
||||
/// ```
|
||||
@@ -79,20 +85,20 @@ crate::http::header::common_header! {
|
||||
test1,
|
||||
vec![b"audio/*; q=0.2, audio/basic"],
|
||||
Some(Accept(vec![
|
||||
QualityItem::new("audio/*".parse().unwrap(), q(0.2)),
|
||||
QualityItem::max("audio/basic".parse().unwrap()),
|
||||
QualityItem::new("audio/*".parse().unwrap(), q(200)),
|
||||
qitem("audio/basic".parse().unwrap()),
|
||||
])));
|
||||
|
||||
crate::http::header::common_header_test!(
|
||||
test2,
|
||||
vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"],
|
||||
Some(Accept(vec![
|
||||
QualityItem::new(mime::TEXT_PLAIN, q(0.5)),
|
||||
QualityItem::max(mime::TEXT_HTML),
|
||||
QualityItem::new(mime::TEXT_PLAIN, q(500)),
|
||||
qitem(mime::TEXT_HTML),
|
||||
QualityItem::new(
|
||||
"text/x-dvi".parse().unwrap(),
|
||||
q(0.8)),
|
||||
QualityItem::max("text/x-c".parse().unwrap()),
|
||||
q(800)),
|
||||
qitem("text/x-c".parse().unwrap()),
|
||||
])));
|
||||
|
||||
// Custom tests
|
||||
@@ -100,21 +106,20 @@ crate::http::header::common_header! {
|
||||
test3,
|
||||
vec![b"text/plain; charset=utf-8"],
|
||||
Some(Accept(vec![
|
||||
QualityItem::max(mime::TEXT_PLAIN_UTF_8),
|
||||
qitem(mime::TEXT_PLAIN_UTF_8),
|
||||
])));
|
||||
crate::http::header::common_header_test!(
|
||||
test4,
|
||||
vec![b"text/plain; charset=utf-8; q=0.5"],
|
||||
Some(Accept(vec![
|
||||
QualityItem::new(mime::TEXT_PLAIN_UTF_8,
|
||||
q(0.5)),
|
||||
q(500)),
|
||||
])));
|
||||
|
||||
#[test]
|
||||
fn test_fuzzing1() {
|
||||
let req = test::TestRequest::default()
|
||||
.insert_header((header::ACCEPT, "chunk#;e"))
|
||||
.finish();
|
||||
use actix_http::test::TestRequest;
|
||||
let req = TestRequest::default().insert_header((crate::http::header::ACCEPT, "chunk#;e")).finish();
|
||||
let header = Accept::parse(&req);
|
||||
assert!(header.is_ok());
|
||||
}
|
||||
@@ -124,27 +129,27 @@ crate::http::header::common_header! {
|
||||
impl Accept {
|
||||
/// Construct `Accept: */*`.
|
||||
pub fn star() -> Accept {
|
||||
Accept(vec![QualityItem::max(mime::STAR_STAR)])
|
||||
Accept(vec![qitem(mime::STAR_STAR)])
|
||||
}
|
||||
|
||||
/// Construct `Accept: application/json`.
|
||||
pub fn json() -> Accept {
|
||||
Accept(vec![QualityItem::max(mime::APPLICATION_JSON)])
|
||||
Accept(vec![qitem(mime::APPLICATION_JSON)])
|
||||
}
|
||||
|
||||
/// Construct `Accept: text/*`.
|
||||
pub fn text() -> Accept {
|
||||
Accept(vec![QualityItem::max(mime::TEXT_STAR)])
|
||||
Accept(vec![qitem(mime::TEXT_STAR)])
|
||||
}
|
||||
|
||||
/// Construct `Accept: image/*`.
|
||||
pub fn image() -> Accept {
|
||||
Accept(vec![QualityItem::max(mime::IMAGE_STAR)])
|
||||
Accept(vec![qitem(mime::IMAGE_STAR)])
|
||||
}
|
||||
|
||||
/// Construct `Accept: text/html`.
|
||||
pub fn html() -> Accept {
|
||||
Accept(vec![QualityItem::max(mime::TEXT_HTML)])
|
||||
Accept(vec![qitem(mime::TEXT_HTML)])
|
||||
}
|
||||
|
||||
/// Returns a sorted list of mime types from highest to lowest preference, accounting for
|
||||
@@ -202,15 +207,15 @@ impl Accept {
|
||||
/// If no q-factors are provided, the first mime type is chosen. Note that items without
|
||||
/// q-factors are given the maximum preference value.
|
||||
///
|
||||
/// As per the spec, will return [`mime::STAR_STAR`] (indicating no preference) if the contained
|
||||
/// As per the spec, will return [`Mime::STAR_STAR`] (indicating no preference) if the contained
|
||||
/// list is empty.
|
||||
///
|
||||
/// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2
|
||||
pub fn preference(&self) -> Mime {
|
||||
use actix_http::header::Quality;
|
||||
use actix_http::header::q;
|
||||
|
||||
let mut max_item = None;
|
||||
let mut max_pref = Quality::MIN;
|
||||
let mut max_pref = q(0);
|
||||
|
||||
// uses manual max lookup loop since we want the first occurrence in the case of same
|
||||
// preference but `Iterator::max_by_key` would give us the last occurrence
|
||||
@@ -238,11 +243,11 @@ mod tests {
|
||||
let test = Accept(vec![]);
|
||||
assert!(test.ranked().is_empty());
|
||||
|
||||
let test = Accept(vec![QualityItem::max(mime::APPLICATION_JSON)]);
|
||||
let test = Accept(vec![qitem(mime::APPLICATION_JSON)]);
|
||||
assert_eq!(test.ranked(), vec!(mime::APPLICATION_JSON));
|
||||
|
||||
let test = Accept(vec![
|
||||
QualityItem::max(mime::TEXT_HTML),
|
||||
qitem(mime::TEXT_HTML),
|
||||
"application/xhtml+xml".parse().unwrap(),
|
||||
QualityItem::new("application/xml".parse().unwrap(), q(0.9)),
|
||||
QualityItem::new(mime::STAR_STAR, q(0.8)),
|
||||
@@ -258,9 +263,9 @@ mod tests {
|
||||
);
|
||||
|
||||
let test = Accept(vec![
|
||||
QualityItem::max(mime::STAR_STAR),
|
||||
QualityItem::max(mime::IMAGE_STAR),
|
||||
QualityItem::max(mime::IMAGE_PNG),
|
||||
qitem(mime::STAR_STAR),
|
||||
qitem(mime::IMAGE_STAR),
|
||||
qitem(mime::IMAGE_PNG),
|
||||
]);
|
||||
assert_eq!(
|
||||
test.ranked(),
|
||||
@@ -271,7 +276,7 @@ mod tests {
|
||||
#[test]
|
||||
fn preference_selection() {
|
||||
let test = Accept(vec![
|
||||
QualityItem::max(mime::TEXT_HTML),
|
||||
qitem(mime::TEXT_HTML),
|
||||
"application/xhtml+xml".parse().unwrap(),
|
||||
QualityItem::new("application/xml".parse().unwrap(), q(0.9)),
|
||||
QualityItem::new(mime::STAR_STAR, q(0.8)),
|
||||
@@ -280,9 +285,9 @@ mod tests {
|
||||
|
||||
let test = Accept(vec![
|
||||
QualityItem::new("video/*".parse().unwrap(), q(0.8)),
|
||||
QualityItem::max(mime::IMAGE_PNG),
|
||||
qitem(mime::IMAGE_PNG),
|
||||
QualityItem::new(mime::STAR_STAR, q(0.5)),
|
||||
QualityItem::max(mime::IMAGE_SVG),
|
||||
qitem(mime::IMAGE_SVG),
|
||||
QualityItem::new(mime::IMAGE_STAR, q(0.8)),
|
||||
]);
|
||||
assert_eq!(test.preference(), mime::IMAGE_PNG);
|
||||
|
@@ -22,11 +22,11 @@ crate::http::header::common_header! {
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{AcceptCharset, Charset, QualityItem};
|
||||
/// use actix_web::http::header::{AcceptCharset, Charset, qitem};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// AcceptCharset(vec![QualityItem::max(Charset::Us_Ascii)])
|
||||
/// AcceptCharset(vec![qitem(Charset::Us_Ascii)])
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
@@ -37,19 +37,19 @@ crate::http::header::common_header! {
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// AcceptCharset(vec![
|
||||
/// QualityItem::new(Charset::Us_Ascii, q(0.9)),
|
||||
/// QualityItem::new(Charset::Iso_8859_10, q(0.2)),
|
||||
/// QualityItem::new(Charset::Us_Ascii, q(900)),
|
||||
/// QualityItem::new(Charset::Iso_8859_10, q(200)),
|
||||
/// ])
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{AcceptCharset, Charset, QualityItem};
|
||||
/// use actix_web::http::header::{AcceptCharset, Charset, qitem};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// AcceptCharset(vec![QualityItem::max(Charset::Ext("utf-8".to_owned()))])
|
||||
/// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))])
|
||||
/// );
|
||||
/// ```
|
||||
(AcceptCharset, ACCEPT_CHARSET) => (QualityItem<Charset>)+
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user