mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-10 12:54:20 +02:00
Compare commits
12 Commits
web-v4.0.0
...
web-v4.0.0
Author | SHA1 | Date | |
---|---|---|---|
3f2db9e75c | |||
074d18209d | |||
593fbde46a | |||
161861997c | |||
3d621677a5 | |||
0c144054cb | |||
b0fbe0dfd8 | |||
b653bf557f | |||
1d1a65282f | |||
b0a363a7ae | |||
b4d3c2394d | |||
5ca42df89a |
@ -22,10 +22,10 @@ path = "src/lib.rs"
|
||||
experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
|
||||
|
||||
[dependencies]
|
||||
actix-http = "3.0.0-rc.1"
|
||||
actix-http = "3.0.0-rc.2"
|
||||
actix-service = "2"
|
||||
actix-utils = "3"
|
||||
actix-web = { version = "4.0.0-rc.2", default-features = false }
|
||||
actix-web = { version = "4.0.0-rc.3", default-features = false }
|
||||
|
||||
askama_escape = "0.10"
|
||||
bitflags = "1"
|
||||
@ -44,5 +44,5 @@ tokio-uring = { version = "0.2", optional = true, features = ["bytes"] }
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2"
|
||||
actix-test = "0.1.0-beta.12"
|
||||
actix-web = "4.0.0-rc.2"
|
||||
actix-web = "4.0.0-rc.3"
|
||||
tempfile = "3.2"
|
||||
|
@ -75,7 +75,7 @@ pub(crate) fn directory_listing(
|
||||
if dir.is_visible(&entry) {
|
||||
let entry = entry.unwrap();
|
||||
let p = match entry.path().strip_prefix(&dir.path) {
|
||||
Ok(p) if cfg!(windows) => base.join(p).to_string_lossy().replace("\\", "/"),
|
||||
Ok(p) if cfg!(windows) => base.join(p).to_string_lossy().replace('\\', "/"),
|
||||
Ok(p) => base.join(p).to_string_lossy().into_owned(),
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
||||
tokio = { version = "1.8.4", features = ["sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-web = { version = "4.0.0-rc.2", default-features = false, features = ["cookies"] }
|
||||
actix-http = "3.0.0-rc.1"
|
||||
actix-web = { version = "4.0.0-rc.3", default-features = false, features = ["cookies"] }
|
||||
actix-http = "3.0.0-rc.2"
|
||||
|
@ -3,6 +3,20 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 3.0.0-rc.2 - 2022-02-08
|
||||
### Added
|
||||
- Implement `From<Vec<u8>>` for `Response<Vec<u8>>`. [#2625]
|
||||
|
||||
### Changed
|
||||
- `error::DispatcherError` enum is now marked `#[non_exhaustive]`. [#2624]
|
||||
|
||||
### Fixed
|
||||
- Issue where handlers that took payload but then dropped without reading it to EOF it would cause keep-alive connections to become stuck. [#2624]
|
||||
|
||||
[#2624]: https://github.com/actix/actix-web/pull/2624
|
||||
[#2625]: https://github.com/actix/actix-web/pull/2625
|
||||
|
||||
|
||||
## 3.0.0-rc.1 - 2022-01-31
|
||||
### Added
|
||||
- Implement `Default` for `KeepAlive`. [#2611]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-http"
|
||||
version = "3.0.0-rc.1"
|
||||
version = "3.0.0-rc.2"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
@ -100,7 +100,7 @@ zstd = { version = "0.10", optional = true }
|
||||
actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] }
|
||||
actix-server = "2"
|
||||
actix-tls = { version = "3.0.0", features = ["openssl"] }
|
||||
actix-web = "4.0.0-rc.2"
|
||||
actix-web = "4.0.0-rc.3"
|
||||
|
||||
async-stream = "0.3"
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
|
@ -3,11 +3,11 @@
|
||||
> HTTP primitives for the Actix ecosystem.
|
||||
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://docs.rs/actix-http/3.0.0-rc.1)
|
||||
[](https://docs.rs/actix-http/3.0.0-rc.2)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-http/3.0.0-rc.1)
|
||||
[](https://deps.rs/crate/actix-http/3.0.0-rc.2)
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@ -104,8 +104,13 @@ impl ServiceConfig {
|
||||
self.0.date_service.now()
|
||||
}
|
||||
|
||||
pub(crate) fn write_date_header(&self, dst: &mut BytesMut, camel_case: bool) {
|
||||
let mut buf: [u8; 39] = [0; 39];
|
||||
/// Writes date header to `dst` buffer.
|
||||
///
|
||||
/// Low-level method that utilizes the built-in efficient date service, requiring fewer syscalls
|
||||
/// than normal. Note that a CRLF (`\r\n`) is included in what is written.
|
||||
#[doc(hidden)]
|
||||
pub fn write_date_header(&self, dst: &mut BytesMut, camel_case: bool) {
|
||||
let mut buf: [u8; 37] = [0; 37];
|
||||
|
||||
buf[..6].copy_from_slice(if camel_case { b"Date: " } else { b"date: " });
|
||||
|
||||
@ -113,7 +118,7 @@ impl ServiceConfig {
|
||||
.date_service
|
||||
.with_date(|date| buf[6..35].copy_from_slice(&date.bytes));
|
||||
|
||||
buf[35..].copy_from_slice(b"\r\n\r\n");
|
||||
buf[35..].copy_from_slice(b"\r\n");
|
||||
dst.extend_from_slice(&buf);
|
||||
}
|
||||
|
||||
|
@ -340,6 +340,7 @@ impl From<PayloadError> for Error {
|
||||
|
||||
/// A set of errors that can occur during dispatching HTTP requests.
|
||||
#[derive(Debug, Display, From)]
|
||||
#[non_exhaustive]
|
||||
pub enum DispatchError {
|
||||
/// Service error.
|
||||
#[display(fmt = "Service Error")]
|
||||
@ -373,6 +374,10 @@ pub enum DispatchError {
|
||||
#[display(fmt = "Connection shutdown timeout")]
|
||||
DisconnectTimeout,
|
||||
|
||||
/// Handler dropped payload before reading EOF.
|
||||
#[display(fmt = "Handler dropped payload before reading EOF")]
|
||||
HandlerDroppedPayload,
|
||||
|
||||
/// Internal error.
|
||||
#[display(fmt = "Internal error")]
|
||||
InternalError,
|
||||
|
@ -128,7 +128,10 @@ impl Decoder for ClientCodec {
|
||||
type Error = ParseError;
|
||||
|
||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set");
|
||||
debug_assert!(
|
||||
self.inner.payload.is_none(),
|
||||
"Payload decoder should not be set"
|
||||
);
|
||||
|
||||
if let Some((req, payload)) = self.inner.decoder.decode(src)? {
|
||||
if let Some(conn_type) = req.conn_type() {
|
||||
|
@ -125,11 +125,13 @@ impl Decoder for Codec {
|
||||
self.flags.set(Flags::HEAD, head.method == Method::HEAD);
|
||||
self.version = head.version;
|
||||
self.conn_type = head.connection_type();
|
||||
|
||||
if self.conn_type == ConnectionType::KeepAlive
|
||||
&& !self.flags.contains(Flags::KEEP_ALIVE_ENABLED)
|
||||
{
|
||||
self.conn_type = ConnectionType::Close
|
||||
}
|
||||
|
||||
match payload {
|
||||
PayloadType::None => self.payload = None,
|
||||
PayloadType::Payload(pl) => self.payload = Some(pl),
|
||||
|
@ -209,15 +209,16 @@ impl MessageType for Request {
|
||||
|
||||
let (len, method, uri, ver, h_len) = {
|
||||
// SAFETY:
|
||||
// Create an uninitialized array of `MaybeUninit`. The `assume_init` is
|
||||
// safe because the type we are claiming to have initialized here is a
|
||||
// bunch of `MaybeUninit`s, which do not require initialization.
|
||||
// Create an uninitialized array of `MaybeUninit`. The `assume_init` is safe because the
|
||||
// type we are claiming to have initialized here is a bunch of `MaybeUninit`s, which
|
||||
// do not require initialization.
|
||||
let mut parsed = unsafe {
|
||||
MaybeUninit::<[MaybeUninit<httparse::Header<'_>>; MAX_HEADERS]>::uninit()
|
||||
.assume_init()
|
||||
};
|
||||
|
||||
let mut req = httparse::Request::new(&mut []);
|
||||
|
||||
match req.parse_with_uninit_headers(src, &mut parsed)? {
|
||||
httparse::Status::Complete(len) => {
|
||||
let method = Method::from_bytes(req.method.unwrap().as_bytes())
|
||||
@ -232,6 +233,7 @@ impl MessageType for Request {
|
||||
|
||||
(len, method, uri, version, req.headers.len())
|
||||
}
|
||||
|
||||
httparse::Status::Partial => {
|
||||
return if src.len() >= MAX_BUFFER_SIZE {
|
||||
trace!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
||||
|
@ -21,7 +21,7 @@ use crate::{
|
||||
config::ServiceConfig,
|
||||
error::{DispatchError, ParseError, PayloadError},
|
||||
service::HttpFlow,
|
||||
Error, Extensions, OnConnectData, Request, Response, StatusCode,
|
||||
ConnectionType, Error, Extensions, OnConnectData, Request, Response, StatusCode,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@ -151,7 +151,8 @@ pin_project! {
|
||||
error: Option<DispatchError>,
|
||||
|
||||
#[pin]
|
||||
state: State<S, B, X>,
|
||||
pub(super) state: State<S, B, X>,
|
||||
// when Some(_) dispatcher is in state of receiving request payload
|
||||
payload: Option<PayloadSender>,
|
||||
messages: VecDeque<DispatcherMessage>,
|
||||
|
||||
@ -174,7 +175,7 @@ enum DispatcherMessage {
|
||||
|
||||
pin_project! {
|
||||
#[project = StateProj]
|
||||
enum State<S, B, X>
|
||||
pub(super) enum State<S, B, X>
|
||||
where
|
||||
S: Service<Request>,
|
||||
X: Service<Request, Response = Request>,
|
||||
@ -194,7 +195,7 @@ where
|
||||
X: Service<Request, Response = Request>,
|
||||
B: MessageBody,
|
||||
{
|
||||
fn is_none(&self) -> bool {
|
||||
pub(super) fn is_none(&self) -> bool {
|
||||
matches!(self, State::None)
|
||||
}
|
||||
}
|
||||
@ -686,12 +687,74 @@ where
|
||||
let can_not_read = !self.can_read(cx);
|
||||
|
||||
// limit amount of non-processed requests
|
||||
if pipeline_queue_full || can_not_read {
|
||||
if pipeline_queue_full {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let mut this = self.as_mut().project();
|
||||
|
||||
if can_not_read {
|
||||
log::debug!("cannot read request payload");
|
||||
|
||||
if let Some(sender) = &this.payload {
|
||||
// ...maybe handler does not want to read any more payload...
|
||||
if let PayloadStatus::Dropped = sender.need_read(cx) {
|
||||
log::debug!("handler dropped payload early; attempt to clean connection");
|
||||
// ...in which case poll request payload a few times
|
||||
loop {
|
||||
match this.codec.decode(this.read_buf)? {
|
||||
Some(msg) => {
|
||||
match msg {
|
||||
// payload decoded did not yield EOF yet
|
||||
Message::Chunk(Some(_)) => {
|
||||
// if non-clean connection, next loop iter will detect empty
|
||||
// read buffer and close connection
|
||||
}
|
||||
|
||||
// connection is in clean state for next request
|
||||
Message::Chunk(None) => {
|
||||
log::debug!("connection successfully cleaned");
|
||||
|
||||
// reset dispatcher state
|
||||
let _ = this.payload.take();
|
||||
this.state.set(State::None);
|
||||
|
||||
// break out of payload decode loop
|
||||
break;
|
||||
}
|
||||
|
||||
// Either whole payload is read and loop is broken or more data
|
||||
// was expected in which case connection is closed. In both
|
||||
// situations dispatcher cannot get here.
|
||||
Message::Item(_) => {
|
||||
unreachable!("dispatcher is in payload receive state")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// not enough info to decide if connection is going to be clean or not
|
||||
None => {
|
||||
log::error!(
|
||||
"handler did not read whole payload and dispatcher could not \
|
||||
drain read buf; return 500 and close connection"
|
||||
);
|
||||
|
||||
this.flags.insert(Flags::SHUTDOWN);
|
||||
let mut res = Response::internal_server_error().drop_body();
|
||||
res.head_mut().set_connection_type(ConnectionType::Close);
|
||||
this.messages.push_back(DispatcherMessage::Error(res));
|
||||
*this.error = Some(DispatchError::HandlerDroppedPayload);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// can_not_read and no request payload
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
let mut updated = false;
|
||||
|
||||
loop {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::{future::Future, str, task::Poll, time::Duration};
|
||||
|
||||
use actix_rt::time::sleep;
|
||||
use actix_rt::{pin, time::sleep};
|
||||
use actix_service::fn_service;
|
||||
use actix_utils::future::{ready, Ready};
|
||||
use bytes::Bytes;
|
||||
@ -53,6 +53,14 @@ fn echo_path_service(
|
||||
})
|
||||
}
|
||||
|
||||
fn drop_payload_service(
|
||||
) -> impl Service<Request, Response = Response<&'static str>, Error = Error> {
|
||||
fn_service(|mut req: Request| async move {
|
||||
let _ = req.take_payload();
|
||||
Ok::<_, Error>(Response::with_body(StatusCode::OK, "payload dropped"))
|
||||
})
|
||||
}
|
||||
|
||||
fn echo_payload_service() -> impl Service<Request, Response = Response<Bytes>, Error = Error> {
|
||||
fn_service(|mut req: Request| {
|
||||
Box::pin(async move {
|
||||
@ -89,7 +97,7 @@ async fn late_request() {
|
||||
None,
|
||||
OnConnectData::default(),
|
||||
);
|
||||
actix_rt::pin!(h1);
|
||||
pin!(h1);
|
||||
|
||||
lazy(|cx| {
|
||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||
@ -156,7 +164,7 @@ async fn oneshot_connection() {
|
||||
None,
|
||||
OnConnectData::default(),
|
||||
);
|
||||
actix_rt::pin!(h1);
|
||||
pin!(h1);
|
||||
|
||||
lazy(|cx| {
|
||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||
@ -173,13 +181,16 @@ async fn oneshot_connection() {
|
||||
stabilize_date_header(&mut res);
|
||||
let res = &res[..];
|
||||
|
||||
let exp = b"\
|
||||
HTTP/1.1 200 OK\r\n\
|
||||
content-length: 5\r\n\
|
||||
connection: close\r\n\
|
||||
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
|
||||
/abcd\
|
||||
";
|
||||
let exp = http_msg(
|
||||
r"
|
||||
HTTP/1.1 200 OK
|
||||
content-length: 5
|
||||
connection: close
|
||||
date: Thu, 01 Jan 1970 12:34:56 UTC
|
||||
|
||||
/abcd
|
||||
",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
@ -188,7 +199,7 @@ async fn oneshot_connection() {
|
||||
response: {:?}\n\
|
||||
expected: {:?}",
|
||||
String::from_utf8_lossy(res),
|
||||
String::from_utf8_lossy(exp)
|
||||
String::from_utf8_lossy(&exp)
|
||||
);
|
||||
})
|
||||
.await;
|
||||
@ -214,7 +225,7 @@ async fn keep_alive_timeout() {
|
||||
None,
|
||||
OnConnectData::default(),
|
||||
);
|
||||
actix_rt::pin!(h1);
|
||||
pin!(h1);
|
||||
|
||||
lazy(|cx| {
|
||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||
@ -293,7 +304,7 @@ async fn keep_alive_follow_up_req() {
|
||||
None,
|
||||
OnConnectData::default(),
|
||||
);
|
||||
actix_rt::pin!(h1);
|
||||
pin!(h1);
|
||||
|
||||
lazy(|cx| {
|
||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||
@ -413,7 +424,7 @@ async fn req_parse_err() {
|
||||
OnConnectData::default(),
|
||||
);
|
||||
|
||||
actix_rt::pin!(h1);
|
||||
pin!(h1);
|
||||
|
||||
match h1.as_mut().poll(cx) {
|
||||
Poll::Pending => panic!(),
|
||||
@ -459,7 +470,7 @@ async fn pipelining_ok_then_ok() {
|
||||
OnConnectData::default(),
|
||||
);
|
||||
|
||||
actix_rt::pin!(h1);
|
||||
pin!(h1);
|
||||
|
||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||
|
||||
@ -529,7 +540,7 @@ async fn pipelining_ok_then_bad() {
|
||||
OnConnectData::default(),
|
||||
);
|
||||
|
||||
actix_rt::pin!(h1);
|
||||
pin!(h1);
|
||||
|
||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||
|
||||
@ -601,7 +612,7 @@ async fn expect_handling() {
|
||||
",
|
||||
);
|
||||
|
||||
actix_rt::pin!(h1);
|
||||
pin!(h1);
|
||||
|
||||
assert!(h1.as_mut().poll(cx).is_pending());
|
||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||
@ -678,7 +689,7 @@ async fn expect_eager() {
|
||||
",
|
||||
);
|
||||
|
||||
actix_rt::pin!(h1);
|
||||
pin!(h1);
|
||||
|
||||
assert!(h1.as_mut().poll(cx).is_ready());
|
||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||
@ -761,7 +772,7 @@ async fn upgrade_handling() {
|
||||
",
|
||||
);
|
||||
|
||||
actix_rt::pin!(h1);
|
||||
pin!(h1);
|
||||
|
||||
assert!(h1.as_mut().poll(cx).is_ready());
|
||||
assert!(matches!(&h1.inner, DispatcherState::Upgrade { .. }));
|
||||
@ -771,3 +782,192 @@ async fn upgrade_handling() {
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn handler_drop_payload() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let mut buf = TestBuffer::new(http_msg(
|
||||
r"
|
||||
POST /drop-payload HTTP/1.1
|
||||
Content-Length: 3
|
||||
|
||||
abc
|
||||
",
|
||||
));
|
||||
|
||||
let services = HttpFlow::new(
|
||||
drop_payload_service(),
|
||||
ExpectHandler,
|
||||
None::<UpgradeHandler>,
|
||||
);
|
||||
|
||||
let h1 = Dispatcher::new(
|
||||
buf.clone(),
|
||||
services,
|
||||
ServiceConfig::default(),
|
||||
None,
|
||||
OnConnectData::default(),
|
||||
);
|
||||
pin!(h1);
|
||||
|
||||
lazy(|cx| {
|
||||
assert!(h1.as_mut().poll(cx).is_pending());
|
||||
|
||||
// polls: manual
|
||||
assert_eq!(h1.poll_count, 1);
|
||||
|
||||
let mut res = BytesMut::from(buf.take_write_buf().as_ref());
|
||||
stabilize_date_header(&mut res);
|
||||
let res = &res[..];
|
||||
|
||||
let exp = http_msg(
|
||||
r"
|
||||
HTTP/1.1 200 OK
|
||||
content-length: 15
|
||||
date: Thu, 01 Jan 1970 12:34:56 UTC
|
||||
|
||||
payload dropped
|
||||
",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
exp,
|
||||
"\nexpected response not in write buffer:\n\
|
||||
response: {:?}\n\
|
||||
expected: {:?}",
|
||||
String::from_utf8_lossy(res),
|
||||
String::from_utf8_lossy(&exp)
|
||||
);
|
||||
|
||||
if let DispatcherStateProj::Normal { inner } = h1.as_mut().project().inner.project() {
|
||||
assert!(inner.state.is_none());
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
lazy(|cx| {
|
||||
// add message that claims to have payload longer than provided
|
||||
buf.extend_read_buf(http_msg(
|
||||
r"
|
||||
POST /drop-payload HTTP/1.1
|
||||
Content-Length: 200
|
||||
|
||||
abc
|
||||
",
|
||||
));
|
||||
|
||||
assert!(h1.as_mut().poll(cx).is_pending());
|
||||
|
||||
// polls: manual => manual
|
||||
assert_eq!(h1.poll_count, 2);
|
||||
|
||||
let mut res = BytesMut::from(buf.take_write_buf().as_ref());
|
||||
stabilize_date_header(&mut res);
|
||||
let res = &res[..];
|
||||
|
||||
// expect response immediately even though request side has not finished reading payload
|
||||
let exp = http_msg(
|
||||
r"
|
||||
HTTP/1.1 200 OK
|
||||
content-length: 15
|
||||
date: Thu, 01 Jan 1970 12:34:56 UTC
|
||||
|
||||
payload dropped
|
||||
",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
exp,
|
||||
"\nexpected response not in write buffer:\n\
|
||||
response: {:?}\n\
|
||||
expected: {:?}",
|
||||
String::from_utf8_lossy(res),
|
||||
String::from_utf8_lossy(&exp)
|
||||
);
|
||||
})
|
||||
.await;
|
||||
|
||||
lazy(|cx| {
|
||||
assert!(h1.as_mut().poll(cx).is_ready());
|
||||
|
||||
// polls: manual => manual => manual
|
||||
assert_eq!(h1.poll_count, 3);
|
||||
|
||||
let mut res = BytesMut::from(buf.take_write_buf().as_ref());
|
||||
stabilize_date_header(&mut res);
|
||||
let res = &res[..];
|
||||
|
||||
// expect that unrequested error response is sent back since connection could not be cleaned
|
||||
let exp = http_msg(
|
||||
r"
|
||||
HTTP/1.1 500 Internal Server Error
|
||||
content-length: 0
|
||||
connection: close
|
||||
date: Thu, 01 Jan 1970 12:34:56 UTC
|
||||
|
||||
",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
exp,
|
||||
"\nexpected response not in write buffer:\n\
|
||||
response: {:?}\n\
|
||||
expected: {:?}",
|
||||
String::from_utf8_lossy(res),
|
||||
String::from_utf8_lossy(&exp)
|
||||
);
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
fn http_msg(msg: impl AsRef<str>) -> BytesMut {
|
||||
let mut msg = msg
|
||||
.as_ref()
|
||||
.trim()
|
||||
.split('\n')
|
||||
.into_iter()
|
||||
.map(|line| [line.trim_start(), "\r"].concat())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
// remove trailing \r
|
||||
msg.pop();
|
||||
|
||||
if !msg.is_empty() && !msg.contains("\r\n\r\n") {
|
||||
msg.push_str("\r\n\r\n");
|
||||
}
|
||||
|
||||
BytesMut::from(msg.as_bytes())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_msg_creates_msg() {
|
||||
assert_eq!(http_msg(r""), "");
|
||||
|
||||
assert_eq!(
|
||||
http_msg(
|
||||
r"
|
||||
POST / HTTP/1.1
|
||||
Content-Length: 3
|
||||
|
||||
abc
|
||||
"
|
||||
),
|
||||
"POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
http_msg(
|
||||
r"
|
||||
GET / HTTP/1.1
|
||||
Content-Length: 3
|
||||
|
||||
"
|
||||
),
|
||||
"GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\n"
|
||||
);
|
||||
}
|
||||
|
@ -210,14 +210,14 @@ pub(crate) trait MessageType: Sized {
|
||||
dst.advance_mut(pos);
|
||||
}
|
||||
|
||||
// optimized date header, set_date writes \r\n
|
||||
if !has_date {
|
||||
// optimized date header, write_date_header writes its own \r\n
|
||||
config.write_date_header(dst, camel_case);
|
||||
} else {
|
||||
// msg eof
|
||||
dst.extend_from_slice(b"\r\n");
|
||||
}
|
||||
|
||||
// end-of-headers marker
|
||||
dst.extend_from_slice(b"\r\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -285,6 +285,24 @@ impl From<&'static [u8]> for Response<&'static [u8]> {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for Response<Vec<u8>> {
|
||||
fn from(val: Vec<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
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Vec<u8>> for Response<Vec<u8>> {
|
||||
fn from(val: &Vec<u8>) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val.clone());
|
||||
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Response<String> {
|
||||
fn from(val: String) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val);
|
||||
|
@ -15,7 +15,7 @@ path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-utils = "3.0.0"
|
||||
actix-web = { version = "4.0.0-rc.2", default-features = false }
|
||||
actix-web = { version = "4.0.0-rc.3", default-features = false }
|
||||
|
||||
bytes = "1"
|
||||
derive_more = "0.99.5"
|
||||
@ -28,7 +28,7 @@ twoway = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2"
|
||||
actix-http = "3.0.0-rc.1"
|
||||
actix-http = "3.0.0-rc.2"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
tokio = { version = "1.8.4", features = ["sync"] }
|
||||
tokio-stream = "0.1"
|
||||
|
@ -145,7 +145,8 @@ macro_rules! register {
|
||||
concat!("/user/keys"),
|
||||
concat!("/user/keys/", $p1),
|
||||
];
|
||||
std::array::IntoIter::new(arr)
|
||||
|
||||
IntoIterator::into_iter(arr)
|
||||
}};
|
||||
}
|
||||
|
||||
@ -158,7 +159,7 @@ fn call() -> impl Iterator<Item = &'static str> {
|
||||
"/repos/rust-lang/rust/releases/1.51.0",
|
||||
];
|
||||
|
||||
std::array::IntoIter::new(arr)
|
||||
IntoIterator::into_iter(arr)
|
||||
}
|
||||
|
||||
fn compare_routers(c: &mut Criterion) {
|
||||
|
@ -898,7 +898,7 @@ impl ResourceDef {
|
||||
}
|
||||
|
||||
let pattern_re_set = RegexSet::new(re_set).unwrap();
|
||||
let segments = segments.unwrap_or_else(Vec::new);
|
||||
let segments = segments.unwrap_or_default();
|
||||
|
||||
(
|
||||
PatternType::DynamicSet(pattern_re_set, pattern_data),
|
||||
|
@ -29,12 +29,12 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.4.1"
|
||||
actix-http = "3.0.0-rc.1"
|
||||
actix-http = "3.0.0-rc.2"
|
||||
actix-http-test = "3.0.0-beta.12"
|
||||
actix-rt = "2.1"
|
||||
actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
actix-web = { version = "4.0.0-rc.2", default-features = false, features = ["cookies"] }
|
||||
actix-web = { version = "4.0.0-rc.3", default-features = false, features = ["cookies"] }
|
||||
awc = { version = "3.0.0-beta.20", default-features = false, features = ["cookies"] }
|
||||
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
|
||||
|
@ -16,8 +16,8 @@ path = "src/lib.rs"
|
||||
[dependencies]
|
||||
actix = { version = "0.12.0", default-features = false }
|
||||
actix-codec = "0.4.1"
|
||||
actix-http = "3.0.0-rc.1"
|
||||
actix-web = { version = "4.0.0-rc.2", default-features = false }
|
||||
actix-http = "3.0.0-rc.2"
|
||||
actix-web = { version = "4.0.0-rc.3", default-features = false }
|
||||
|
||||
bytes = "1"
|
||||
bytestring = "1"
|
||||
|
@ -25,7 +25,7 @@ actix-macros = "0.2.3"
|
||||
actix-rt = "2.2"
|
||||
actix-test = "0.1.0-beta.12"
|
||||
actix-utils = "3.0.0"
|
||||
actix-web = "4.0.0-rc.2"
|
||||
actix-web = "4.0.0-rc.3"
|
||||
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
trybuild = "1"
|
||||
|
@ -152,6 +152,10 @@ method_macro!(Patch, patch);
|
||||
|
||||
/// Marks async main function as the Actix Web system entry-point.
|
||||
///
|
||||
/// Note that Actix Web also works under `#[tokio::main]` since version 4.0. However, this macro is
|
||||
/// still necessary for actor support (since actors use a `System`). Read more in the
|
||||
/// [`actix_web::rt`](https://docs.rs/actix-web/4/actix_web/rt) module docs.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// #[actix_web::main]
|
||||
|
@ -3,6 +3,18 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 4.0.0-rc.3 - 2022-02-08
|
||||
### Changed
|
||||
- `middleware::Condition` gained a broader compatibility; `Compat` is needed in fewer cases. [#2635]
|
||||
|
||||
### Added
|
||||
- Implement `Responder` for `Vec<u8>`. [#2625]
|
||||
- Re-export `KeepAlive` in `http` mod. [#2625]
|
||||
|
||||
[#2625]: https://github.com/actix/actix-web/pull/2625
|
||||
[#2635]: https://github.com/actix/actix-web/pull/2635
|
||||
|
||||
|
||||
## 4.0.0-rc.2 - 2022-02-02
|
||||
### Added
|
||||
- On-by-default `macros` feature flag to enable routing and runtime macros. [#2619]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "4.0.0-rc.2"
|
||||
version = "4.0.0-rc.3"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
@ -71,7 +71,7 @@ actix-service = "2"
|
||||
actix-utils = "3"
|
||||
actix-tls = { version = "3", default-features = false, optional = true }
|
||||
|
||||
actix-http = { version = "3.0.0-rc.1", features = ["http2", "ws"] }
|
||||
actix-http = { version = "3.0.0-rc.2", features = ["http2", "ws"] }
|
||||
actix-router = "0.5.0-rc.3"
|
||||
actix-web-codegen = { version = "0.5.0-rc.2", optional = true }
|
||||
|
||||
@ -116,6 +116,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
static_assertions = "1"
|
||||
tls-openssl = { package = "openssl", version = "0.10.9" }
|
||||
tls-rustls = { package = "rustls", version = "0.20.0" }
|
||||
tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] }
|
||||
zstd = "0.10"
|
||||
|
||||
[[test]]
|
||||
|
@ -9,6 +9,7 @@ Headings marked with :warning: are **breaking behavioral changes** and will prob
|
||||
## Table of Contents:
|
||||
|
||||
- [MSRV](#msrv)
|
||||
- [Server Settings](#server-settings)
|
||||
- [Module Structure](#module-structure)
|
||||
- [`NormalizePath` Middleware :warning:](#normalizepath-middleware-warning)
|
||||
- [`FromRequest` Trait](#fromrequest-trait)
|
||||
@ -20,6 +21,11 @@ Headings marked with :warning: are **breaking behavioral changes** and will prob
|
||||
|
||||
The MSRV of Actix Web has been raised from 1.42 to 1.54.
|
||||
|
||||
## Server Settings
|
||||
|
||||
Until actix-web v4, actix-server used the total number of available logical cores as the default number of worker threads. The new default number of worker threads for actix-server is the number of [physical CPU cores available](https://github.com/actix/actix-net/commit/3a3d654cea5e55b169f6fd05693b765799733b1b#diff-96893e8cb2125e6eefc96105a8462c4fd834943ef5129ffbead1a114133ebb78). For more information about this change, refer to [this analysis](https://github.com/actix/actix-web/issues/957).
|
||||
|
||||
|
||||
## Module Structure
|
||||
|
||||
Lots of modules has been organized in this release. If a compile error refers to "item XYZ not found in module..." or "module XYZ not found", refer to the [documentation on docs.rs](https://docs.rs/actix-web) to to search for items' new locations.
|
||||
@ -29,13 +35,13 @@ Lots of modules has been organized in this release. If a compile error refers to
|
||||
The default `NormalizePath` behavior now strips trailing slashes by default. This was previously documented to be the case in v3 but the behavior now matches. The effect is that routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning. It is advised that the `new` or `trim` methods be used instead.
|
||||
|
||||
```diff
|
||||
- #[get("/test/")]`
|
||||
+ #[get("/test")]`
|
||||
- #[get("/test/")]
|
||||
+ #[get("/test")]
|
||||
async fn handler() {
|
||||
|
||||
App::new()
|
||||
- .wrap(NormalizePath::default())`
|
||||
+ .wrap(NormalizePath::trim())`
|
||||
- .wrap(NormalizePath::default())
|
||||
+ .wrap(NormalizePath::trim())
|
||||
```
|
||||
|
||||
Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`.
|
||||
@ -46,10 +52,12 @@ The associated type `Config` of `FromRequest` was removed. If you have custom ex
|
||||
|
||||
```diff
|
||||
impl FromRequest for MyExtractor {
|
||||
- `type Config = ();`
|
||||
- type Config = ();
|
||||
}
|
||||
```
|
||||
|
||||
Consequently, the `FromRequest::configure` method was also removed. Config for extractors is still provided using `App::app_data` but should now be constructed in a standalone way.
|
||||
|
||||
## Compression Feature Flags
|
||||
|
||||
Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). By default, all compression algorithms are enabled. The new flags are:
|
||||
@ -131,3 +139,7 @@ TODO
|
||||
## HttpResponse no longer implements Future
|
||||
|
||||
TODO
|
||||
|
||||
## `#[actix_web::main]` and `#[tokio::main]`
|
||||
|
||||
TODO
|
||||
|
@ -6,10 +6,10 @@
|
||||
<p>
|
||||
|
||||
[](https://crates.io/crates/actix-web)
|
||||
[](https://docs.rs/actix-web/4.0.0-rc.2)
|
||||
[](https://docs.rs/actix-web/4.0.0-rc.3)
|
||||

|
||||

|
||||
[](https://deps.rs/crate/actix-web/4.0.0-rc.2)
|
||||
[](https://deps.rs/crate/actix-web/4.0.0-rc.3)
|
||||
<br />
|
||||
[](https://github.com/actix/actix-web/actions/workflows/ci.yml)
|
||||
[](https://codecov.io/gh/actix/actix-web)
|
||||
@ -32,7 +32,7 @@
|
||||
- Static assets
|
||||
- SSL support using OpenSSL or Rustls
|
||||
- Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
|
||||
- Includes an async [HTTP client](https://docs.rs/awc/)
|
||||
- Integrates with the [`awc` HTTP client](https://docs.rs/awc/)
|
||||
- Runs on stable Rust 1.54+
|
||||
|
||||
## Documentation
|
||||
|
@ -10,12 +10,16 @@ use crate::{
|
||||
/// The interface for request handlers.
|
||||
///
|
||||
/// # What Is A Request Handler
|
||||
/// A request handler has three requirements:
|
||||
/// In short, a handler is just an async function that receives request-based arguments, in any
|
||||
/// order, and returns something that can be converted to a response.
|
||||
///
|
||||
/// In particular, a request handler has three requirements:
|
||||
/// 1. It is an async function (or a function/closure that returns an appropriate future);
|
||||
/// 1. The function parameters (up to 12) implement [`FromRequest`];
|
||||
/// 1. The async function (or future) resolves to a type that can be converted into an
|
||||
/// [`HttpResponse`] (i.e., it implements the [`Responder`] trait).
|
||||
///
|
||||
///
|
||||
/// # Compiler Errors
|
||||
/// If you get the error `the trait Handler<_> is not implemented`, then your handler does not
|
||||
/// fulfill the _first_ of the above requirements. Missing other requirements manifest as errors on
|
||||
|
@ -62,18 +62,18 @@ crate::http::header::common_header! {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_http::test::TestRequest;
|
||||
|
||||
use super::IfNoneMatch;
|
||||
use crate::http::header::{EntityTag, Header, IF_NONE_MATCH};
|
||||
use actix_http::test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_if_none_match() {
|
||||
let mut if_none_match: Result<IfNoneMatch, _>;
|
||||
|
||||
let req = TestRequest::default()
|
||||
.insert_header((IF_NONE_MATCH, "*"))
|
||||
.finish();
|
||||
if_none_match = Header::parse(&req);
|
||||
|
||||
let mut if_none_match = IfNoneMatch::parse(&req);
|
||||
assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any));
|
||||
|
||||
let req = TestRequest::default()
|
||||
|
@ -3,4 +3,4 @@
|
||||
pub mod header;
|
||||
|
||||
// TODO: figure out how best to expose http::Error vs actix_http::Error
|
||||
pub use actix_http::{uri, ConnectionType, Error, Method, StatusCode, Uri, Version};
|
||||
pub use actix_http::{uri, ConnectionType, Error, KeepAlive, Method, StatusCode, Uri, Version};
|
||||
|
@ -42,28 +42,29 @@
|
||||
//! and otherwise utilizing them.
|
||||
//!
|
||||
//! # Features
|
||||
//! * Supports *HTTP/1.x* and *HTTP/2*
|
||||
//! * Streaming and pipelining
|
||||
//! * Keep-alive and slow requests handling
|
||||
//! * Client/server [WebSockets](https://actix.rs/docs/websockets/) support
|
||||
//! * Transparent content compression/decompression (br, gzip, deflate, zstd)
|
||||
//! * Powerful [request routing](https://actix.rs/docs/url-dispatch/)
|
||||
//! * Multipart streams
|
||||
//! * Static assets
|
||||
//! * SSL support using OpenSSL or Rustls
|
||||
//! * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
|
||||
//! * Includes an async [HTTP client](https://docs.rs/awc/)
|
||||
//! * Runs on stable Rust 1.54+
|
||||
//! - Supports HTTP/1.x and HTTP/2
|
||||
//! - Streaming and pipelining
|
||||
//! - Powerful [request routing](https://actix.rs/docs/url-dispatch/) with optional macros
|
||||
//! - Full [Tokio](https://tokio.rs) compatibility
|
||||
//! - Keep-alive and slow requests handling
|
||||
//! - Client/server [WebSockets](https://actix.rs/docs/websockets/) support
|
||||
//! - Transparent content compression/decompression (br, gzip, deflate, zstd)
|
||||
//! - Multipart streams
|
||||
//! - Static assets
|
||||
//! - SSL support using OpenSSL or Rustls
|
||||
//! - Middlewares ([Logger, Session, CORS, etc](middleware))
|
||||
//! - Integrates with the [`awc` HTTP client](https://docs.rs/awc/)
|
||||
//! - Runs on stable Rust 1.54+
|
||||
//!
|
||||
//! # Crate Features
|
||||
//! * `cookies` - cookies support (enabled by default)
|
||||
//! * `macros` - routing and runtime macros (enabled by default)
|
||||
//! * `compress-brotli` - brotli content encoding compression support (enabled by default)
|
||||
//! * `compress-gzip` - gzip and deflate content encoding compression support (enabled by default)
|
||||
//! * `compress-zstd` - zstd content encoding compression support (enabled by default)
|
||||
//! * `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2`
|
||||
//! * `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2`
|
||||
//! * `secure-cookies` - secure cookies support
|
||||
//! - `cookies` - cookies support (enabled by default)
|
||||
//! - `macros` - routing and runtime macros (enabled by default)
|
||||
//! - `compress-brotli` - brotli content encoding compression support (enabled by default)
|
||||
//! - `compress-gzip` - gzip and deflate content encoding compression support (enabled by default)
|
||||
//! - `compress-zstd` - zstd content encoding compression support (enabled by default)
|
||||
//! - `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2`
|
||||
//! - `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2`
|
||||
//! - `secure-cookies` - secure cookies support
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
|
@ -1,18 +1,22 @@
|
||||
//! For middleware documentation, see [`Condition`].
|
||||
|
||||
use std::task::{Context, Poll};
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_service::{Service, Transform};
|
||||
use actix_utils::future::Either;
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use futures_util::future::FutureExt as _;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::{
|
||||
body::EitherBody,
|
||||
dev::{Service, ServiceResponse, Transform},
|
||||
};
|
||||
|
||||
/// Middleware for conditionally enabling other middleware.
|
||||
///
|
||||
/// The controlled middleware must not change the `Service` interfaces. This means you cannot
|
||||
/// control such middlewares like `Logger` or `Compress` directly. See the [`Compat`](super::Compat)
|
||||
/// middleware for a workaround.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::middleware::{Condition, NormalizePath};
|
||||
@ -36,16 +40,16 @@ impl<T> Condition<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T, Req> Transform<S, Req> for Condition<T>
|
||||
impl<S, T, Req, BE, BD, Err> Transform<S, Req> for Condition<T>
|
||||
where
|
||||
S: Service<Req> + 'static,
|
||||
T: Transform<S, Req, Response = S::Response, Error = S::Error>,
|
||||
S: Service<Req, Response = ServiceResponse<BD>, Error = Err> + 'static,
|
||||
T: Transform<S, Req, Response = ServiceResponse<BE>, Error = Err>,
|
||||
T::Future: 'static,
|
||||
T::InitError: 'static,
|
||||
T::Transform: 'static,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Response = ServiceResponse<EitherBody<BE, BD>>;
|
||||
type Error = Err;
|
||||
type Transform = ConditionMiddleware<T::Transform, S>;
|
||||
type InitError = T::InitError;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Transform, Self::InitError>>;
|
||||
@ -69,14 +73,14 @@ pub enum ConditionMiddleware<E, D> {
|
||||
Disable(D),
|
||||
}
|
||||
|
||||
impl<E, D, Req> Service<Req> for ConditionMiddleware<E, D>
|
||||
impl<E, D, Req, BE, BD, Err> Service<Req> for ConditionMiddleware<E, D>
|
||||
where
|
||||
E: Service<Req>,
|
||||
D: Service<Req, Response = E::Response, Error = E::Error>,
|
||||
E: Service<Req, Response = ServiceResponse<BE>, Error = Err>,
|
||||
D: Service<Req, Response = ServiceResponse<BD>, Error = Err>,
|
||||
{
|
||||
type Response = E::Response;
|
||||
type Error = E::Error;
|
||||
type Future = Either<E::Future, D::Future>;
|
||||
type Response = ServiceResponse<EitherBody<BE, BD>>;
|
||||
type Error = Err;
|
||||
type Future = ConditionMiddlewareFuture<E::Future, D::Future>;
|
||||
|
||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
match self {
|
||||
@ -87,27 +91,59 @@ where
|
||||
|
||||
fn call(&self, req: Req) -> Self::Future {
|
||||
match self {
|
||||
ConditionMiddleware::Enable(service) => Either::left(service.call(req)),
|
||||
ConditionMiddleware::Disable(service) => Either::right(service.call(req)),
|
||||
ConditionMiddleware::Enable(service) => ConditionMiddlewareFuture::Enabled {
|
||||
fut: service.call(req),
|
||||
},
|
||||
ConditionMiddleware::Disable(service) => ConditionMiddlewareFuture::Disabled {
|
||||
fut: service.call(req),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[doc(hidden)]
|
||||
#[project = ConditionProj]
|
||||
pub enum ConditionMiddlewareFuture<E, D> {
|
||||
Enabled { #[pin] fut: E, },
|
||||
Disabled { #[pin] fut: D, },
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, D, BE, BD, Err> Future for ConditionMiddlewareFuture<E, D>
|
||||
where
|
||||
E: Future<Output = Result<ServiceResponse<BE>, Err>>,
|
||||
D: Future<Output = Result<ServiceResponse<BD>, Err>>,
|
||||
{
|
||||
type Output = Result<ServiceResponse<EitherBody<BE, BD>>, Err>;
|
||||
|
||||
#[inline]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let res = match self.project() {
|
||||
ConditionProj::Enabled { fut } => ready!(fut.poll(cx))?.map_into_left_body(),
|
||||
ConditionProj::Disabled { fut } => ready!(fut.poll(cx))?.map_into_right_body(),
|
||||
};
|
||||
|
||||
Poll::Ready(Ok(res))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_service::IntoService;
|
||||
use actix_utils::future::ok;
|
||||
use actix_service::IntoService as _;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
body::BoxBody,
|
||||
dev::{ServiceRequest, ServiceResponse},
|
||||
error::Result,
|
||||
http::{
|
||||
header::{HeaderValue, CONTENT_TYPE},
|
||||
StatusCode,
|
||||
},
|
||||
middleware::{err_handlers::*, Compat},
|
||||
middleware::{self, ErrorHandlerResponse, ErrorHandlers},
|
||||
test::{self, TestRequest},
|
||||
web::Bytes,
|
||||
HttpResponse,
|
||||
};
|
||||
|
||||
@ -120,40 +156,52 @@ mod tests {
|
||||
Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compat_with_builtin_middleware() {
|
||||
let _ = Condition::new(true, middleware::Compat::noop());
|
||||
let _ = Condition::new(true, middleware::Logger::default());
|
||||
let _ = Condition::new(true, middleware::Compress::default());
|
||||
let _ = Condition::new(true, middleware::NormalizePath::trim());
|
||||
let _ = Condition::new(true, middleware::DefaultHeaders::new());
|
||||
let _ = Condition::new(true, middleware::ErrorHandlers::<BoxBody>::new());
|
||||
let _ = Condition::new(true, middleware::ErrorHandlers::<Bytes>::new());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_handler_enabled() {
|
||||
let srv = |req: ServiceRequest| {
|
||||
ok(req.into_response(HttpResponse::InternalServerError().finish()))
|
||||
let srv = |req: ServiceRequest| async move {
|
||||
let resp = HttpResponse::InternalServerError().message_body(String::new())?;
|
||||
Ok(req.into_response(resp))
|
||||
};
|
||||
|
||||
let mw = Compat::new(
|
||||
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500),
|
||||
);
|
||||
let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
|
||||
|
||||
let mw = Condition::new(true, mw)
|
||||
.new_transform(srv.into_service())
|
||||
.await
|
||||
.unwrap();
|
||||
let resp = test::call_service(&mw, TestRequest::default().to_srv_request()).await;
|
||||
|
||||
let resp: ServiceResponse<EitherBody<EitherBody<_, _>, String>> =
|
||||
test::call_service(&mw, TestRequest::default().to_srv_request()).await;
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_handler_disabled() {
|
||||
let srv = |req: ServiceRequest| {
|
||||
ok(req.into_response(HttpResponse::InternalServerError().finish()))
|
||||
let srv = |req: ServiceRequest| async move {
|
||||
let resp = HttpResponse::InternalServerError().message_body(String::new())?;
|
||||
Ok(req.into_response(resp))
|
||||
};
|
||||
|
||||
let mw = Compat::new(
|
||||
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500),
|
||||
);
|
||||
let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
|
||||
|
||||
let mw = Condition::new(false, mw)
|
||||
.new_transform(srv.into_service())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let resp = test::call_service(&mw, TestRequest::default().to_srv_request()).await;
|
||||
let resp: ServiceResponse<EitherBody<EitherBody<_, _>, String>> =
|
||||
test::call_service(&mw, TestRequest::default().to_srv_request()).await;
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE), None);
|
||||
}
|
||||
}
|
||||
|
@ -132,6 +132,7 @@ macro_rules! impl_responder_by_forward_into_base_response {
|
||||
}
|
||||
|
||||
impl_responder_by_forward_into_base_response!(&'static [u8]);
|
||||
impl_responder_by_forward_into_base_response!(Vec<u8>);
|
||||
impl_responder_by_forward_into_base_response!(Bytes);
|
||||
impl_responder_by_forward_into_base_response!(BytesMut);
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
//! A selection of re-exports from [`actix-rt`] and [`tokio`].
|
||||
//! A selection of re-exports from [`tokio`] and [`actix-rt`].
|
||||
//!
|
||||
//! [`actix-rt`]: https://docs.rs/actix_rt
|
||||
//! [`tokio`]: https://docs.rs/tokio
|
||||
//! Actix Web runs on [Tokio], providing full[^compat] compatibility with its huge ecosystem of
|
||||
//! crates. Each of the server's workers uses a single-threaded runtime. Read more about the
|
||||
//! architecture in [`actix-rt`]'s docs.
|
||||
//!
|
||||
//! # Running Actix Web Macro-less
|
||||
//! # Running Actix Web Without Macros
|
||||
//! ```no_run
|
||||
//! use actix_web::{middleware, rt, web, App, HttpRequest, HttpServer};
|
||||
//!
|
||||
@ -12,19 +13,53 @@
|
||||
//! "Hello world!\r\n"
|
||||
//! }
|
||||
//!
|
||||
//! # fn main() -> std::io::Result<()> {
|
||||
//! rt::System::new().block_on(
|
||||
//! fn main() -> std::io::Result<()> {
|
||||
//! rt::System::new().block_on(
|
||||
//! HttpServer::new(|| {
|
||||
//! App::new().service(web::resource("/").route(web::get().to(index)))
|
||||
//! })
|
||||
//! .bind(("127.0.0.1", 8080))?
|
||||
//! .run()
|
||||
//! )
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! # Running Actix Web Using `#[tokio::main]`
|
||||
//! If you need to run something alongside Actix Web that uses Tokio's work stealing functionality,
|
||||
//! you can run Actix Web under `#[tokio::main]`. The [`Server`](crate::dev::Server) object returned
|
||||
//! from [`HttpServer::run`](crate::HttpServer::run) can also be [`spawn`]ed, if preferred.
|
||||
//!
|
||||
//! Note that `actix` actor support (and therefore WebSocket support through `actix-web-actors`)
|
||||
//! still require `#[actix_web::main]` since they require a [`System`] to be set up.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use actix_web::{get, middleware, rt, web, App, HttpRequest, HttpServer};
|
||||
//!
|
||||
//! #[get("/")]
|
||||
//! async fn index(req: HttpRequest) -> &'static str {
|
||||
//! println!("REQ: {:?}", req);
|
||||
//! "Hello world!\r\n"
|
||||
//! }
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() -> std::io::Result<()> {
|
||||
//! HttpServer::new(|| {
|
||||
//! App::new()
|
||||
//! .wrap(middleware::Logger::default())
|
||||
//! .service(web::resource("/").route(web::get().to(index)))
|
||||
//! App::new().service(index)
|
||||
//! })
|
||||
//! .bind(("127.0.0.1", 8080))?
|
||||
//! .workers(1)
|
||||
//! .run()
|
||||
//! )
|
||||
//! # }
|
||||
//! .await
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! [^compat]: Crates that use Tokio's [`block_in_place`] will not work with Actix Web. Fortunately,
|
||||
//! the vast majority of Tokio-based crates do not use it.
|
||||
//!
|
||||
//! [`actix-rt`]: https://docs.rs/actix-rt
|
||||
//! [`tokio`]: https://docs.rs/tokio
|
||||
//! [Tokio]: https://docs.rs/tokio
|
||||
//! [`spawn`]: https://docs.rs/tokio/1/tokio/fn.spawn.html
|
||||
//! [`block_in_place`]: https://docs.rs/tokio/1/tokio/task/fn.block_in_place.html
|
||||
|
||||
// In particular:
|
||||
// - Omit the `Arbiter` types because they have limited value here.
|
||||
|
@ -128,7 +128,7 @@ where
|
||||
|
||||
/// Set number of workers to start.
|
||||
///
|
||||
/// By default, server uses number of available logical CPU as thread count.
|
||||
/// By default, the number of available physical CPUs is used as the worker count.
|
||||
pub fn workers(mut self, num: usize) -> Self {
|
||||
self.builder = self.builder.workers(num);
|
||||
self
|
||||
|
@ -60,7 +60,7 @@ dangerous-h2c = []
|
||||
[dependencies]
|
||||
actix-codec = "0.4.1"
|
||||
actix-service = "2.0.0"
|
||||
actix-http = { version = "3.0.0-rc.1", features = ["http2", "ws"] }
|
||||
actix-http = { version = "3.0.0-rc.2", features = ["http2", "ws"] }
|
||||
actix-rt = { version = "2.1", default-features = false }
|
||||
actix-tls = { version = "3.0.0", features = ["connect", "uri"] }
|
||||
actix-utils = "3.0.0"
|
||||
@ -93,13 +93,13 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features
|
||||
trust-dns-resolver = { version = "0.20.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-http = { version = "3.0.0-rc.1", features = ["openssl"] }
|
||||
actix-http = { version = "3.0.0-rc.2", features = ["openssl"] }
|
||||
actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] }
|
||||
actix-server = "2"
|
||||
actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] }
|
||||
actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] }
|
||||
actix-utils = "3.0.0"
|
||||
actix-web = { version = "4.0.0-rc.2", features = ["openssl"] }
|
||||
actix-web = { version = "4.0.0-rc.3", features = ["openssl"] }
|
||||
|
||||
brotli = "3.3.3"
|
||||
const-str = "0.3"
|
||||
|
@ -337,7 +337,7 @@ where
|
||||
match self.get_mut() {
|
||||
Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_write(cx, buf),
|
||||
Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_write(cx, buf),
|
||||
_ => unreachable!(H2_UNREACHABLE_WRITE),
|
||||
_ => unreachable!("{}", H2_UNREACHABLE_WRITE),
|
||||
}
|
||||
}
|
||||
|
||||
@ -345,7 +345,7 @@ where
|
||||
match self.get_mut() {
|
||||
Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_flush(cx),
|
||||
Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_flush(cx),
|
||||
_ => unreachable!(H2_UNREACHABLE_WRITE),
|
||||
_ => unreachable!("{}", H2_UNREACHABLE_WRITE),
|
||||
}
|
||||
}
|
||||
|
||||
@ -353,7 +353,7 @@ where
|
||||
match self.get_mut() {
|
||||
Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_shutdown(cx),
|
||||
Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_shutdown(cx),
|
||||
_ => unreachable!(H2_UNREACHABLE_WRITE),
|
||||
_ => unreachable!("{}", H2_UNREACHABLE_WRITE),
|
||||
}
|
||||
}
|
||||
|
||||
@ -369,7 +369,7 @@ where
|
||||
Connection::Tls(ConnectionType::H1(conn)) => {
|
||||
Pin::new(conn).poll_write_vectored(cx, bufs)
|
||||
}
|
||||
_ => unreachable!(H2_UNREACHABLE_WRITE),
|
||||
_ => unreachable!("{}", H2_UNREACHABLE_WRITE),
|
||||
}
|
||||
}
|
||||
|
||||
@ -377,7 +377,7 @@ where
|
||||
match *self {
|
||||
Connection::Tcp(ConnectionType::H1(ref conn)) => conn.is_write_vectored(),
|
||||
Connection::Tls(ConnectionType::H1(ref conn)) => conn.is_write_vectored(),
|
||||
_ => unreachable!(H2_UNREACHABLE_WRITE),
|
||||
_ => unreachable!("{}", H2_UNREACHABLE_WRITE),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,16 @@ unreleased_for() {
|
||||
DIR=$1
|
||||
|
||||
CARGO_MANIFEST=$DIR/Cargo.toml
|
||||
CHANGELOG_FILE=$DIR/CHANGES.md
|
||||
|
||||
# determine changelog file name
|
||||
if [ -f "$DIR/CHANGES.md" ]; then
|
||||
CHANGELOG_FILE=$DIR/CHANGES.md
|
||||
elif [ -f "$DIR/CHANGELOG.md" ]; then
|
||||
CHANGELOG_FILE=$DIR/CHANGELOG.md
|
||||
else
|
||||
echo "No changelog file found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# get current version
|
||||
PACKAGE_NAME="$(sed -nE 's/^name ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST" | head -n 1)"
|
||||
@ -36,6 +45,6 @@ unreleased_for() {
|
||||
cat "$CHANGE_CHUNK_FILE"
|
||||
}
|
||||
|
||||
for f in $(fd --absolute-path CHANGES.md); do
|
||||
for f in $(fd --absolute-path 'CHANGE\w+.md'); do
|
||||
unreleased_for $(dirname $f)
|
||||
done
|
||||
|
Reference in New Issue
Block a user