1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-04 01:51:30 +02:00

Compare commits

...

22 Commits

Author SHA1 Message Date
5611b98c0d prepare actix-http release 3.0.4 2022-03-09 18:13:39 +00:00
dce9438518 document with ws feature 2022-03-09 18:11:12 +00:00
be986d96b3 bump regex requirement to 1.5.5 due to security advisory (#2687) 2022-03-08 17:42:42 +00:00
8ddb24b49b prepare awc release 3.0.0 (#2684) 2022-03-08 16:51:40 +00:00
87f627cd5d improve servicerequest docs 2022-03-07 16:48:04 +00:00
03456b8a33 update actix-web-in-http example 2022-03-05 23:43:31 +00:00
8c2fad3164 align hello-world examples 2022-03-05 23:15:33 +00:00
62fbd225bc prepare actix-http release 3.0.2 2022-03-05 22:26:19 +00:00
0fa4d999d9 fix(actix-http): encode correctly camel case header with n+2 hyphens (#2683)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-03-05 22:24:21 +00:00
da4c849f62 prepare actix-http release 3.0.1 2022-03-04 03:16:02 +00:00
49cd303c3b fix dispatcher panic when conbining pipelining and keepalive
fixes #2678
2022-03-04 03:12:38 +00:00
955c3ac0c4 Add support for audio files streaming (#2645) 2022-03-03 00:29:59 +00:00
56e5c19b85 add actix 0.13 support (#2675) 2022-03-02 17:53:47 +00:00
3f03af1c59 clippy 2022-03-02 03:25:30 +00:00
25c0673278 Update MIGRATION-4.0.md 2022-03-02 02:20:48 +00:00
e7a05f9892 fix(docs): TestRequest example fixed (#2643)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-03-01 00:02:08 +00:00
2f13e5f675 Update MIGRATION-4.0.md 2022-02-26 17:13:42 +00:00
9f964751f6 tweak migration doc 2022-02-25 21:40:23 +00:00
fcca515387 prepare actix-multipart release 0.4.0 2022-02-25 20:41:57 +00:00
075932d823 prepare actix-web-actors release 4.0.0 2022-02-25 20:41:33 +00:00
cb379c0e0c prepare actix-files release 0.6.0 2022-02-25 20:36:16 +00:00
d4a5d450de prepare actix-web release 4.0.1 2022-02-25 20:31:46 +00:00
40 changed files with 453 additions and 182 deletions

View File

@ -1,6 +1,13 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
- Add support for streaming audio files by setting the `content-disposition` header `inline` instead of `attachement`. [#2645]
[#2645]: https://github.com/actix/actix-web/pull/2645
## 0.6.0 - 2022-02-25
- No significant changes since `0.6.0-beta.16`.
## 0.6.0-beta.16 - 2022-01-31 ## 0.6.0-beta.16 - 2022-01-31

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-files" name = "actix-files"
version = "0.6.0-beta.16" version = "0.6.0"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>", "fakeshadow <24548779@qq.com>",
@ -22,10 +22,10 @@ path = "src/lib.rs"
experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
[dependencies] [dependencies]
actix-http = "3.0.0" actix-http = "3"
actix-service = "2" actix-service = "2"
actix-utils = "3" actix-utils = "3"
actix-web = { version = "4.0.0", default-features = false } actix-web = { version = "4", default-features = false }
askama_escape = "0.10" askama_escape = "0.10"
bitflags = "1" bitflags = "1"

View File

@ -3,11 +3,11 @@
> Static file serving for Actix Web > Static file serving for Actix Web
[![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files)
[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.16)](https://docs.rs/actix-files/0.6.0-beta.16) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0)](https://docs.rs/actix-files/0.6.0)
[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
![License](https://img.shields.io/crates/l/actix-files.svg) ![License](https://img.shields.io/crates/l/actix-files.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.16/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.16) [![dependency status](https://deps.rs/crate/actix-files/0.6.0/status.svg)](https://deps.rs/crate/actix-files/0.6.0)
[![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@ -128,7 +128,7 @@ impl NamedFile {
let ct = from_path(&path).first_or_octet_stream(); let ct = from_path(&path).first_or_octet_stream();
let disposition = match ct.type_() { let disposition = match ct.type_() {
mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, mime::IMAGE | mime::TEXT | mime::AUDIO | mime::VIDEO => DispositionType::Inline,
mime::APPLICATION => match ct.subtype() { mime::APPLICATION => match ct.subtype() {
mime::JAVASCRIPT | mime::JSON => DispositionType::Inline, mime::JAVASCRIPT | mime::JSON => DispositionType::Inline,
name if name == "wasm" => DispositionType::Inline, name if name == "wasm" => DispositionType::Inline,

View File

@ -29,13 +29,13 @@ default = []
openssl = ["tls-openssl", "awc/openssl"] openssl = ["tls-openssl", "awc/openssl"]
[dependencies] [dependencies]
actix-service = "2.0.0" actix-service = "2"
actix-codec = "0.5" actix-codec = "0.5"
actix-tls = "3" actix-tls = "3"
actix-utils = "3.0.0" actix-utils = "3"
actix-rt = "2.2" actix-rt = "2.2"
actix-server = "2" actix-server = "2"
awc = { version = "3.0.0-beta.21", default-features = false } awc = { version = "3", default-features = false }
base64 = "0.13" base64 = "0.13"
bytes = "1" bytes = "1"
@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
tokio = { version = "1.8.4", features = ["sync"] } tokio = { version = "1.8.4", features = ["sync"] }
[dev-dependencies] [dev-dependencies]
actix-web = { version = "4.0.0", default-features = false, features = ["cookies"] } actix-web = { version = "4", default-features = false, features = ["cookies"] }
actix-http = "3.0.0" actix-http = "3"

View File

@ -3,6 +3,30 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 3.0.4 - 2022-03-09
### Fixed
- Document on docs.rs with `ws` feature enabled.
## 3.0.3 - 2022-03-08
### Fixed
- Allow spaces between header name and colon when parsing responses. [#2684]
[#2684]: https://github.com/actix/actix-web/issues/2684
## 3.0.2 - 2022-03-05
### Fixed
- Fix encoding camel-case header names with more than one hyphen. [#2683]
[#2683]: https://github.com/actix/actix-web/issues/2683
## 3.0.1 - 2022-03-04
- Fix panic in H1 dispatcher when pipelining is used with keep-alive. [#2678]
[#2678]: https://github.com/actix/actix-web/issues/2678
## 3.0.0 - 2022-02-25 ## 3.0.0 - 2022-02-25
### Dependencies ### Dependencies
- Updated `actix-*` to Tokio v1-based versions. [#1813] - Updated `actix-*` to Tokio v1-based versions. [#1813]
@ -745,10 +769,10 @@
- Remove `ResponseError` impl for `actix::actors::resolver::ResolverError` - Remove `ResponseError` impl for `actix::actors::resolver::ResolverError`
due to deprecate of resolver actor. [#1813] due to deprecate of resolver actor. [#1813]
- Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. - Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`.
due to the removal of this type from `tokio-openssl` crate. openssl handshake due to the removal of this type from `tokio-openssl` crate. openssl handshake
error would return as `ConnectError::SslError`. [#1813] error would return as `ConnectError::SslError`. [#1813]
- Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`. - Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`.
Due to this change `actix_threadpool::BlockingError` type is moved into Due to this change `actix_threadpool::BlockingError` type is moved into
`actix_http::error` module. [#1878] `actix_http::error` module. [#1878]
[#1813]: https://github.com/actix/actix-web/pull/1813 [#1813]: https://github.com/actix/actix-web/pull/1813

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-http" name = "actix-http"
version = "3.0.0" version = "3.0.4"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>", "Rob Ede <robjtede@icloud.com>",
@ -20,7 +20,7 @@ edition = "2018"
[package.metadata.docs.rs] [package.metadata.docs.rs]
# features that docs.rs will build with # features that docs.rs will build with
features = ["http2", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"] features = ["http2", "ws", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"]
[lib] [lib]
name = "actix_http" name = "actix_http"

View File

@ -3,11 +3,11 @@
> HTTP primitives for the Actix ecosystem. > HTTP primitives for the Actix ecosystem.
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0)](https://docs.rs/actix-http/3.0.0) [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.4)](https://docs.rs/actix-http/3.0.4)
[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-http/3.0.0/status.svg)](https://deps.rs/crate/actix-http/3.0.0) [![dependency status](https://deps.rs/crate/actix-http/3.0.4/status.svg)](https://deps.rs/crate/actix-http/3.0.4)
[![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@ -18,7 +18,8 @@ async fn main() -> std::io::Result<()> {
HttpService::build() HttpService::build()
// pass the app to service builder // pass the app to service builder
// map_config is used to map App's configuration to ServiceBuilder // map_config is used to map App's configuration to ServiceBuilder
.finish(map_config(app, |_| AppConfig::default())) // h1 will configure server to only use HTTP/1.1
.h1(map_config(app, |_| AppConfig::default()))
.tcp() .tcp()
})? })?
.run() .run()

View File

@ -293,22 +293,35 @@ impl MessageType for ResponseHead {
let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY; let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY;
let (len, ver, status, h_len) = { let (len, ver, status, h_len) = {
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = EMPTY_HEADER_ARRAY; // 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.
let mut parsed = unsafe {
MaybeUninit::<[MaybeUninit<httparse::Header<'_>>; MAX_HEADERS]>::uninit()
.assume_init()
};
let mut res = httparse::Response::new(&mut parsed); let mut res = httparse::Response::new(&mut []);
match res.parse(src)? {
let mut config = httparse::ParserConfig::default();
config.allow_spaces_after_header_name_in_responses(true);
match config.parse_response_with_uninit_headers(&mut res, src, &mut parsed)? {
httparse::Status::Complete(len) => { httparse::Status::Complete(len) => {
let version = if res.version.unwrap() == 1 { let version = if res.version.unwrap() == 1 {
Version::HTTP_11 Version::HTTP_11
} else { } else {
Version::HTTP_10 Version::HTTP_10
}; };
let status = StatusCode::from_u16(res.code.unwrap()) let status = StatusCode::from_u16(res.code.unwrap())
.map_err(|_| ParseError::Status)?; .map_err(|_| ParseError::Status)?;
HeaderIndex::record(src, res.headers, &mut headers); HeaderIndex::record(src, res.headers, &mut headers);
(len, version, status, res.headers.len()) (len, version, status, res.headers.len())
} }
httparse::Status::Partial => { httparse::Status::Partial => {
return if src.len() >= MAX_BUFFER_SIZE { return if src.len() >= MAX_BUFFER_SIZE {
error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
@ -360,9 +373,6 @@ pub(crate) const EMPTY_HEADER_INDEX: HeaderIndex = HeaderIndex {
pub(crate) const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] = pub(crate) const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] =
[EMPTY_HEADER_INDEX; MAX_HEADERS]; [EMPTY_HEADER_INDEX; MAX_HEADERS];
pub(crate) const EMPTY_HEADER_ARRAY: [httparse::Header<'static>; MAX_HEADERS] =
[httparse::EMPTY_HEADER; MAX_HEADERS];
impl HeaderIndex { impl HeaderIndex {
pub(crate) fn record( pub(crate) fn record(
bytes: &[u8], bytes: &[u8],

View File

@ -375,8 +375,6 @@ where
DispatchError::Io(err) DispatchError::Io(err)
})?; })?;
this.flags.set(Flags::KEEP_ALIVE, this.codec.keep_alive());
Ok(size) Ok(size)
} }
@ -459,7 +457,12 @@ where
} }
// all messages are dealt with // all messages are dealt with
None => return Ok(PollResponse::DoNothing), None => {
// start keep-alive if last request allowed it
this.flags.set(Flags::KEEP_ALIVE, this.codec.keep_alive());
return Ok(PollResponse::DoNothing);
}
}, },
StateProj::ServiceCall { fut } => { StateProj::ServiceCall { fut } => {
@ -757,6 +760,7 @@ where
let mut updated = false; let mut updated = false;
// decode from read buf as many full requests as possible
loop { loop {
match this.codec.decode(this.read_buf) { match this.codec.decode(this.read_buf) {
Ok(Some(msg)) => { Ok(Some(msg)) => {

View File

@ -517,6 +517,7 @@ unsafe fn write_camel_case(value: &[u8], buf: *mut u8, len: usize) {
if let Some(c @ b'a'..=b'z') = iter.next() { if let Some(c @ b'a'..=b'z') = iter.next() {
buffer[index] = c & 0b1101_1111; buffer[index] = c & 0b1101_1111;
} }
index += 1;
} }
index += 1; index += 1;
@ -528,7 +529,7 @@ mod tests {
use std::rc::Rc; use std::rc::Rc;
use bytes::Bytes; use bytes::Bytes;
use http::header::AUTHORIZATION; use http::header::{AUTHORIZATION, UPGRADE_INSECURE_REQUESTS};
use super::*; use super::*;
use crate::{ use crate::{
@ -559,6 +560,9 @@ mod tests {
head.headers head.headers
.insert(CONTENT_TYPE, HeaderValue::from_static("plain/text")); .insert(CONTENT_TYPE, HeaderValue::from_static("plain/text"));
head.headers
.insert(UPGRADE_INSECURE_REQUESTS, HeaderValue::from_static("1"));
let mut head = RequestHeadType::Owned(head); let mut head = RequestHeadType::Owned(head);
let _ = head.encode_headers( let _ = head.encode_headers(
@ -574,6 +578,7 @@ mod tests {
assert!(data.contains("Connection: close\r\n")); assert!(data.contains("Connection: close\r\n"));
assert!(data.contains("Content-Type: plain/text\r\n")); assert!(data.contains("Content-Type: plain/text\r\n"));
assert!(data.contains("Date: date\r\n")); assert!(data.contains("Date: date\r\n"));
assert!(data.contains("Upgrade-Insecure-Requests: 1\r\n"));
let _ = head.encode_headers( let _ = head.encode_headers(
&mut bytes, &mut bytes,

View File

@ -13,7 +13,8 @@ use crate::error::PayloadError;
/// A boxed payload stream. /// A boxed payload stream.
pub type BoxedPayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>>>>; pub type BoxedPayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>>>>;
#[deprecated(since = "4.0.0", note = "Renamed to `BoxedPayloadStream`.")] #[doc(hidden)]
#[deprecated(since = "3.0.0", note = "Renamed to `BoxedPayloadStream`.")]
pub type PayloadStream = BoxedPayloadStream; pub type PayloadStream = BoxedPayloadStream;
#[cfg(not(feature = "http2"))] #[cfg(not(feature = "http2"))]

View File

@ -144,7 +144,7 @@ impl ResponseBuilder {
self self
} }
/// Set connection type to Upgrade /// Set connection type to `Upgrade`.
#[inline] #[inline]
pub fn upgrade<V>(&mut self, value: V) -> &mut Self pub fn upgrade<V>(&mut self, value: V) -> &mut Self
where where
@ -161,7 +161,7 @@ impl ResponseBuilder {
self self
} }
/// Force close connection, even if it is marked as keep-alive /// Force-close connection, even if it is marked as keep-alive.
#[inline] #[inline]
pub fn force_close(&mut self) -> &mut Self { pub fn force_close(&mut self) -> &mut Self {
if let Some(parts) = self.inner() { if let Some(parts) = self.inner() {

View File

@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 0.4.0 - 2022-02-25
- No significant changes since `0.4.0-beta.13`.
## 0.4.0-beta.13 - 2022-01-31 ## 0.4.0-beta.13 - 2022-01-31
- No significant changes since `0.4.0-beta.12`. - No significant changes since `0.4.0-beta.12`.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-multipart" name = "actix-multipart"
version = "0.4.0-beta.13" version = "0.4.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Multipart form support for Actix Web" description = "Multipart form support for Actix Web"
keywords = ["http", "web", "framework", "async", "futures"] keywords = ["http", "web", "framework", "async", "futures"]
@ -14,7 +14,7 @@ name = "actix_multipart"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-utils = "3.0.0" actix-utils = "3"
actix-web = { version = "4.0.0", default-features = false } actix-web = { version = "4.0.0", default-features = false }
bytes = "1" bytes = "1"

View File

@ -3,11 +3,11 @@
> Multipart form support for Actix Web. > Multipart form support for Actix Web.
[![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart)
[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.13)](https://docs.rs/actix-multipart/0.4.0-beta.13) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0)](https://docs.rs/actix-multipart/0.4.0)
[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.13/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.13) [![dependency status](https://deps.rs/crate/actix-multipart/0.4.0/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0)
[![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@ -29,13 +29,13 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
[dependencies] [dependencies]
actix-codec = "0.5" actix-codec = "0.5"
actix-http = "3.0.0" actix-http = "3"
actix-http-test = "3.0.0-beta.13" actix-http-test = "3.0.0-beta.13"
actix-rt = "2.1" actix-rt = "2.1"
actix-service = "2.0.0" actix-service = "2"
actix-utils = "3.0.0" actix-utils = "3"
actix-web = { version = "4.0.0", default-features = false, features = ["cookies"] } actix-web = { version = "4", default-features = false, features = ["cookies"] }
awc = { version = "3.0.0-beta.21", default-features = false, features = ["cookies"] } awc = { version = "3", default-features = false, features = ["cookies"] }
futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
futures-util = { version = "0.3.7", default-features = false, features = [] } futures-util = { version = "0.3.7", default-features = false, features = [] }

View File

@ -3,6 +3,16 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 4.1.0 - 2022-03-02
- Add support for `actix` version `0.13`. [#2675]
[#2675]: https://github.com/actix/actix-web/pull/2675
## 4.0.0 - 2022-02-25
- No significant changes since `4.0.0-beta.12`.
## 4.0.0-beta.12 - 2022-02-16 ## 4.0.0-beta.12 - 2022-02-16
- No significant changes since `4.0.0-beta.11`. - No significant changes since `4.0.0-beta.11`.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web-actors" name = "actix-web-actors"
version = "4.0.0-beta.12" version = "4.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for Actix Web" description = "Actix actors support for Actix Web"
keywords = ["actix", "http", "web", "framework", "async"] keywords = ["actix", "http", "web", "framework", "async"]
@ -14,21 +14,21 @@ name = "actix_web_actors"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix = { version = "0.12.0", default-features = false } actix = { version = ">=0.12, <0.14", default-features = false }
actix-codec = "0.5" actix-codec = "0.5"
actix-http = "3.0.0" actix-http = "3"
actix-web = { version = "4.0.0", default-features = false } actix-web = { version = "4", default-features = false }
bytes = "1" bytes = "1"
bytestring = "1" bytestring = "1"
futures-core = { version = "0.3.7", default-features = false } futures-core = { version = "0.3.7", default-features = false }
pin-project-lite = "0.2" pin-project-lite = "0.2"
tokio = { version = "1.8.4", features = ["sync"] } tokio = { version = "1.13.1", features = ["sync"] }
[dev-dependencies] [dev-dependencies]
actix-rt = "2.2" actix-rt = "2.2"
actix-test = "0.1.0-beta.13" actix-test = "0.1.0-beta.13"
awc = { version = "3.0.0-beta.21", default-features = false } awc = { version = "3", default-features = false }
env_logger = "0.9" env_logger = "0.9"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }

View File

@ -3,11 +3,11 @@
> Actix actors support for Actix Web. > Actix actors support for Actix Web.
[![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors)
[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.12)](https://docs.rs/actix-web-actors/4.0.0-beta.12) [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.1.0)](https://docs.rs/actix-web-actors/4.1.0)
[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
![License](https://img.shields.io/crates/l/actix-web-actors.svg) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.12/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.12) [![dependency status](https://deps.rs/crate/actix-web-actors/4.1.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.1.0)
[![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@ -3,6 +3,11 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 4.0.1 - 2022-02-25
### Fixed
- Use stable version in readme example.
## 4.0.0 - 2022-02-25 ## 4.0.0 - 2022-02-25
### Dependencies ### Dependencies
- Updated `actix-*` to Tokio v1-based versions. [#1813] - Updated `actix-*` to Tokio v1-based versions. [#1813]
@ -10,7 +15,7 @@
- Updated `cookie` to `0.16`. [#2555] - Updated `cookie` to `0.16`. [#2555]
- Updated `language-tags` to `0.3`. - Updated `language-tags` to `0.3`.
- Updated `rand` to `0.8`. - Updated `rand` to `0.8`.
- Updated `rustls` to `0.20.0`. [#2414] - Updated `rustls` to `0.20`. [#2414]
- Updated `tokio` to `1`. - Updated `tokio` to `1`.
### Added ### Added

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "4.0.0" version = "4.0.1"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>", "Rob Ede <robjtede@icloud.com>",
@ -71,9 +71,9 @@ actix-service = "2"
actix-utils = "3" actix-utils = "3"
actix-tls = { version = "3", default-features = false, optional = true } actix-tls = { version = "3", default-features = false, optional = true }
actix-http = { version = "3.0.0", features = ["http2", "ws"] } actix-http = { version = "3", features = ["http2", "ws"] }
actix-router = "0.5.0" actix-router = "0.5"
actix-web-codegen = { version = "4.0.0", optional = true } actix-web-codegen = { version = "4", optional = true }
ahash = "0.7" ahash = "0.7"
bytes = "1" bytes = "1"
@ -90,7 +90,7 @@ once_cell = "1.5"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
pin-project-lite = "0.2.7" pin-project-lite = "0.2.7"
regex = "1.4" regex = "1.5.5"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_urlencoded = "0.7" serde_urlencoded = "0.7"
@ -100,9 +100,9 @@ time = { version = "0.3", default-features = false, features = ["formatting"] }
url = "2.1" url = "2.1"
[dev-dependencies] [dev-dependencies]
actix-files = "0.6.0-beta.16" actix-files = "0.6"
actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] }
awc = { version = "3.0.0-beta.21", features = ["openssl"] } awc = { version = "3", features = ["openssl"] }
brotli = "3.3.3" brotli = "3.3.3"
const-str = "0.3" const-str = "0.3"

View File

@ -3,7 +3,7 @@
This guide walks you through the process of migrating from v3.x.y to v4.x.y. This guide walks you through the process of migrating from v3.x.y to v4.x.y.
If you are migrating to v4.x.y from an older version of Actix Web (v2.x.y or earlier), check out the other historical migration notes in this folder. If you are migrating to v4.x.y from an older version of Actix Web (v2.x.y or earlier), check out the other historical migration notes in this folder.
This document is not designed to be exhaustive—it focuses on the most significant changes coming in v4. You can find an exhaustive changelog in the changelogs for [`actix-web`](./CHANGES.md#400---2022-02-25) and [`actix-http`](../actix-http/CHANGES.md#300---2022-02-25), complete of PR links. If you think that some of the changes that we omitted deserve to be called out in this document, please open an issue or submit a PR. This document is not designed to be exhaustive—it focuses on the most significant changes in v4. You can find an exhaustive changelog in the changelogs for [`actix-web`](./CHANGES.md#400---2022-02-25) and [`actix-http`](../actix-http/CHANGES.md#300---2022-02-25), complete with PR links. If you think there are any changes that deserve to be called out in this document, please open an issue or pull request.
Headings marked with :warning: are **breaking behavioral changes**. They will probably not surface as compile-time errors though automated tests _might_ detect their effects on your app. Headings marked with :warning: are **breaking behavioral changes**. They will probably not surface as compile-time errors though automated tests _might_ detect their effects on your app.
@ -29,7 +29,7 @@ Headings marked with :warning: are **breaking behavioral changes**. They will pr
- [Server Must Be Polled :warning:](#server-must-be-polled-warning) - [Server Must Be Polled :warning:](#server-must-be-polled-warning)
- [Guards API](#guards-api) - [Guards API](#guards-api)
- [Returning `HttpResponse` synchronously](#returning-httpresponse-synchronously) - [Returning `HttpResponse` synchronously](#returning-httpresponse-synchronously)
- [`#[actix_web::main]` and `#[tokio::main]`](#actixwebmain-and-tokiomain) - [`#[actix_web::main]` and `#[tokio::main]`](#actix_webmain-and-tokiomain)
- [`web::block`](#webblock) - [`web::block`](#webblock)
## MSRV ## MSRV
@ -111,6 +111,8 @@ The inner field for `web::Path` is now private. It was causing ambiguity when tr
+ let (foo, bar) = params.into_inner(); + let (foo, bar) = params.into_inner();
``` ```
An alternative [path param type with public field but no `Deref` impl is available in `actix-web-lab`](https://docs.rs/actix-web-lab/0.12.0/actix_web_lab/extract/struct.Path.html).
## Rustls Crate Upgrade ## Rustls Crate Upgrade
Actix Web now depends on version 0.20 of `rustls`. As a result, the server config builder has changed. [See the updated example project.](https://github.com/actix/examples/tree/master/https-tls/rustls/) Actix Web now depends on version 0.20 of `rustls`. As a result, the server config builder has changed. [See the updated example project.](https://github.com/actix/examples/tree/master/https-tls/rustls/)

View File

@ -6,10 +6,10 @@
<p> <p>
[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0)](https://docs.rs/actix-web/4.0.0) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.1)](https://docs.rs/actix-web/4.0.1)
![MSRV](https://img.shields.io/badge/rustc-1.54+-ab6000.svg) ![MSRV](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg)
[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0/status.svg)](https://deps.rs/crate/actix-web/4.0.0) [![Dependency Status](https://deps.rs/crate/actix-web/4.0.1/status.svg)](https://deps.rs/crate/actix-web/4.0.1)
<br /> <br />
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web)
@ -48,7 +48,7 @@ Dependencies:
```toml ```toml
[dependencies] [dependencies]
actix-web = "4.0.0" actix-web = "4"
``` ```
Code: Code:
@ -56,18 +56,19 @@ Code:
```rust ```rust
use actix_web::{get, web, App, HttpServer, Responder}; use actix_web::{get, web, App, HttpServer, Responder};
#[get("/{id}/{name}/index.html")] #[get("/hello/{name}")]
async fn index(params: web::Path<(u32, String)>) -> impl Responder { async fn greet(name: web::Path<String>) -> impl Responder {
let (id, name) = params.into_inner(); format!("Hello {name}!")
format!("Hello {}! id:{}", name, id)
} }
#[actix_web::main] // or #[tokio::main] #[actix_web::main] // or #[tokio::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(index)) HttpServer::new(|| {
.bind(("127.0.0.1", 8080))? App::new().service(greet)
.run() })
.await .bind(("127.0.0.1", 8080))?
.run()
.await
} }
``` ```

View File

@ -159,7 +159,7 @@ impl ConnectionInfo {
pub fn realip_remote_addr(&self) -> Option<&str> { pub fn realip_remote_addr(&self) -> Option<&str> {
self.realip_remote_addr self.realip_remote_addr
.as_deref() .as_deref()
.or_else(|| self.peer_addr.as_deref()) .or(self.peer_addr.as_deref())
} }
/// Returns serialized IP address of the peer connection. /// Returns serialized IP address of the peer connection.

View File

@ -4,18 +4,19 @@
//! ```no_run //! ```no_run
//! use actix_web::{get, web, App, HttpServer, Responder}; //! use actix_web::{get, web, App, HttpServer, Responder};
//! //!
//! #[get("/{id}/{name}/index.html")] //! #[get("/hello/{name}")]
//! async fn index(path: web::Path<(u32, String)>) -> impl Responder { //! async fn greet(name: web::Path<String>) -> impl Responder {
//! let (id, name) = path.into_inner(); //! format!("Hello {}!", name)
//! format!("Hello {}! id:{}", name, id)
//! } //! }
//! //!
//! #[actix_web::main] //! #[actix_web::main] // or #[tokio::main]
//! async fn main() -> std::io::Result<()> { //! async fn main() -> std::io::Result<()> {
//! HttpServer::new(|| App::new().service(index)) //! HttpServer::new(|| {
//! .bind("127.0.0.1:8080")? //! App::new().service(greet)
//! .run() //! })
//! .await //! .bind(("127.0.0.1", 8080))?
//! .run()
//! .await
//! } //! }
//! ``` //! ```
//! //!

View File

@ -151,7 +151,7 @@ impl ResourceMap {
.char_indices() .char_indices()
.filter_map(|(i, c)| (c == '/').then(|| i)) .filter_map(|(i, c)| (c == '/').then(|| i))
.nth(2) .nth(2)
.unwrap_or_else(|| path.len()); .unwrap_or(path.len());
( (
Cow::Borrowed(&path[..third_slash_index]), Cow::Borrowed(&path[..third_slash_index]),

View File

@ -78,18 +78,18 @@ pub struct ServiceRequest {
} }
impl ServiceRequest { impl ServiceRequest {
/// Construct service request /// Construct `ServiceRequest` from parts.
pub(crate) fn new(req: HttpRequest, payload: Payload) -> Self { pub(crate) fn new(req: HttpRequest, payload: Payload) -> Self {
Self { req, payload } Self { req, payload }
} }
/// Deconstruct request into parts /// Deconstruct `ServiceRequest` into inner parts.
#[inline] #[inline]
pub fn into_parts(self) -> (HttpRequest, Payload) { pub fn into_parts(self) -> (HttpRequest, Payload) {
(self.req, self.payload) (self.req, self.payload)
} }
/// Get mutable access to inner `HttpRequest` and `Payload` /// Returns mutable accessors to inner parts.
#[inline] #[inline]
pub fn parts_mut(&mut self) -> (&mut HttpRequest, &mut Payload) { pub fn parts_mut(&mut self) -> (&mut HttpRequest, &mut Payload) {
(&mut self.req, &mut self.payload) (&mut self.req, &mut self.payload)
@ -105,9 +105,7 @@ impl ServiceRequest {
Self { req, payload } Self { req, payload }
} }
/// Construct request from request. /// Construct `ServiceRequest` with no payload from given `HttpRequest`.
///
/// The returned `ServiceRequest` would have no payload.
#[inline] #[inline]
pub fn from_request(req: HttpRequest) -> Self { pub fn from_request(req: HttpRequest) -> Self {
ServiceRequest { ServiceRequest {
@ -116,63 +114,63 @@ impl ServiceRequest {
} }
} }
/// Create service response /// Create `ServiceResponse` from this request and given response.
#[inline] #[inline]
pub fn into_response<B, R: Into<Response<B>>>(self, res: R) -> ServiceResponse<B> { pub fn into_response<B, R: Into<Response<B>>>(self, res: R) -> ServiceResponse<B> {
let res = HttpResponse::from(res.into()); let res = HttpResponse::from(res.into());
ServiceResponse::new(self.req, res) ServiceResponse::new(self.req, res)
} }
/// Create service response for error /// Create `ServiceResponse` from this request and given error.
#[inline] #[inline]
pub fn error_response<E: Into<Error>>(self, err: E) -> ServiceResponse { pub fn error_response<E: Into<Error>>(self, err: E) -> ServiceResponse {
let res = HttpResponse::from_error(err.into()); let res = HttpResponse::from_error(err.into());
ServiceResponse::new(self.req, res) ServiceResponse::new(self.req, res)
} }
/// This method returns reference to the request head /// Returns a reference to the request head.
#[inline] #[inline]
pub fn head(&self) -> &RequestHead { pub fn head(&self) -> &RequestHead {
self.req.head() self.req.head()
} }
/// This method returns reference to the request head /// Returns a mutable reference to the request head.
#[inline] #[inline]
pub fn head_mut(&mut self) -> &mut RequestHead { pub fn head_mut(&mut self) -> &mut RequestHead {
self.req.head_mut() self.req.head_mut()
} }
/// Request's uri. /// Returns the request URI.
#[inline] #[inline]
pub fn uri(&self) -> &Uri { pub fn uri(&self) -> &Uri {
&self.head().uri &self.head().uri
} }
/// Read the Request method. /// Returns the request method.
#[inline] #[inline]
pub fn method(&self) -> &Method { pub fn method(&self) -> &Method {
&self.head().method &self.head().method
} }
/// Read the Request Version. /// Returns the request version.
#[inline] #[inline]
pub fn version(&self) -> Version { pub fn version(&self) -> Version {
self.head().version self.head().version
} }
/// Returns a reference to request headers.
#[inline] #[inline]
/// Returns request's headers.
pub fn headers(&self) -> &HeaderMap { pub fn headers(&self) -> &HeaderMap {
&self.head().headers &self.head().headers
} }
/// Returns a mutable reference to request headers.
#[inline] #[inline]
/// Returns mutable request's headers.
pub fn headers_mut(&mut self) -> &mut HeaderMap { pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.head_mut().headers &mut self.head_mut().headers
} }
/// The target path of this Request. /// Returns request path.
#[inline] #[inline]
pub fn path(&self) -> &str { pub fn path(&self) -> &str {
self.head().uri.path() self.head().uri.path()
@ -184,7 +182,7 @@ impl ServiceRequest {
self.req.query_string() self.req.query_string()
} }
/// Peer socket address. /// Returns peer's socket address.
/// ///
/// Peer address is the directly connected peer's socket address. If a proxy is used in front of /// Peer address is the directly connected peer's socket address. If a proxy is used in front of
/// the Actix Web server, then it would be address of this proxy. /// the Actix Web server, then it would be address of this proxy.
@ -197,24 +195,23 @@ impl ServiceRequest {
self.head().peer_addr self.head().peer_addr
} }
/// Get *ConnectionInfo* for the current request. /// Returns a reference to connection info.
#[inline] #[inline]
pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> { pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> {
self.req.connection_info() self.req.connection_info()
} }
/// Returns a reference to the Path parameters. /// Returns reference to the Path parameters.
/// ///
/// Params is a container for URL parameters. /// Params is a container for URL parameters. A variable segment is specified in the form
/// A variable segment is specified in the form `{identifier}`, /// `{identifier}`, where the identifier can be used later in a request handler to access the
/// where the identifier can be used later in a request handler to /// matched value for that segment.
/// access the matched value for that segment.
#[inline] #[inline]
pub fn match_info(&self) -> &Path<Url> { pub fn match_info(&self) -> &Path<Url> {
self.req.match_info() self.req.match_info()
} }
/// Returns a mutable reference to the Path parameters. /// Returns a mutable reference to the path match information.
#[inline] #[inline]
pub fn match_info_mut(&mut self) -> &mut Path<Url> { pub fn match_info_mut(&mut self) -> &mut Path<Url> {
self.req.match_info_mut() self.req.match_info_mut()
@ -232,13 +229,13 @@ impl ServiceRequest {
self.req.match_pattern() self.req.match_pattern()
} }
/// Get a reference to a `ResourceMap` of current application. /// Returns a reference to the application's resource map.
#[inline] #[inline]
pub fn resource_map(&self) -> &ResourceMap { pub fn resource_map(&self) -> &ResourceMap {
self.req.resource_map() self.req.resource_map()
} }
/// Service configuration /// Returns a reference to the application's configuration.
#[inline] #[inline]
pub fn app_config(&self) -> &AppConfig { pub fn app_config(&self) -> &AppConfig {
self.req.app_config() self.req.app_config()
@ -262,6 +259,7 @@ impl ServiceRequest {
self.req.conn_data() self.req.conn_data()
} }
/// Return request cookies.
#[cfg(feature = "cookies")] #[cfg(feature = "cookies")]
#[inline] #[inline]
pub fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> { pub fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {

View File

@ -24,10 +24,10 @@ use crate::cookie::{Cookie, CookieJar};
/// ///
/// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern. /// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern.
/// You can generate various types of request via TestRequest's methods: /// You can generate various types of request via TestRequest's methods:
/// * `TestRequest::to_request` creates `actix_http::Request` instance. /// - [`TestRequest::to_request`] creates an [`actix_http::Request`](Request).
/// * `TestRequest::to_srv_request` creates `ServiceRequest` instance, which is used for testing middlewares and chain adapters. /// - [`TestRequest::to_srv_request`] creates a [`ServiceRequest`], which is used for testing middlewares and chain adapters.
/// * `TestRequest::to_srv_response` creates `ServiceResponse` instance. /// - [`TestRequest::to_srv_response`] creates a [`ServiceResponse`].
/// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers. /// - [`TestRequest::to_http_request`] creates an [`HttpRequest`], which is used for testing handlers.
/// ///
/// ``` /// ```
/// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; /// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage};
@ -42,15 +42,17 @@ use crate::cookie::{Cookie, CookieJar};
/// } /// }
/// ///
/// #[actix_web::test] /// #[actix_web::test]
/// # // force rustdoc to display the correct thing and also compile check the test
/// # async fn _test() {}
/// async fn test_index() { /// async fn test_index() {
/// let req = test::TestRequest::default().insert_header("content-type", "text/plain") /// let req = test::TestRequest::default().insert_header(header::ContentType::plaintext())
/// .to_http_request(); /// .to_http_request();
/// ///
/// let resp = index(req).await.unwrap(); /// let resp = index(req).await;
/// assert_eq!(resp.status(), StatusCode::OK); /// assert_eq!(resp.status(), StatusCode::OK);
/// ///
/// let req = test::TestRequest::default().to_http_request(); /// let req = test::TestRequest::default().to_http_request();
/// let resp = index(req).await.unwrap(); /// let resp = index(req).await;
/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
/// } /// }
/// ``` /// ```

View File

@ -3,6 +3,103 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 3.0.0 - 2022-03-07
### Dependencies
- Updated `actix-*` to Tokio v1-based versions. [#1813]
- Updated `bytes` to `1.0`. [#1813]
- Updated `cookie` to `0.16`. [#2555]
- Updated `rand` to `0.8`.
- Updated `rustls` to `0.20`. [#2414]
- Updated `tokio` to `1`.
### Added
- `trust-dns` crate feature to enable `trust-dns-resolver` as client DNS resolver; disabled by default. [#1969]
- `cookies` crate feature; enabled by default. [#2619]
- `compress-brotli` crate feature; enabled by default. [#2250]
- `compress-gzip` crate feature; enabled by default. [#2250]
- `compress-zstd` crate feature; enabled by default. [#2250]
- `client::Connector::handshake_timeout()` for customizing TLS connection handshake timeout. [#2081]
- `client::ConnectorService` as `client::Connector::finish` method's return type [#2081]
- `client::ConnectionIo` trait alias [#2081]
- `Client::headers()` to get default mut reference of `HeaderMap` of client object. [#2114]
- `ClientResponse::timeout()` for set the timeout of collecting response body. [#1931]
- `ClientBuilder::local_address()` for binding to a local IP address for this client. [#2024]
- `ClientRequest::insert_header()` method which allows using typed and untyped headers. [#1869]
- `ClientRequest::append_header()` method which allows using typed and untyped headers. [#1869]
- `ClientBuilder::add_default_header()` (and deprecate `ClientBuilder::header()`). [#2510]
### Changed
- `client::Connector` type now only has one generic type for `actix_service::Service`. [#2063]
- `client::error::ConnectError` Resolver variant contains `Box<dyn std::error::Error>` type. [#1905]
- `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905]
- `ConnectorService` type is renamed to `BoxConnectorService`. [#2081]
- Fix http/https encoding when enabling `compress` feature. [#2116]
- Rename `TestResponse::{header => append_header, set => insert_header}`. These methods now take a `TryIntoHeaderPair`. [#2094]
- `ClientBuilder::connector()` method now takes `Connector<T, U>` type. [#2008]
- Basic auth now accepts blank passwords as an empty string instead of an `Option`. [#2050]
- Relax default timeout for `Connector` to 5 seconds (up from 1 second). [#1905]
- `*::send_json()` and `*::send_form()` methods now receive `impl Serialize`. [#2553]
- `FrozenClientRequest::extra_header()` now uses receives an `impl TryIntoHeaderPair`. [#2553]
- Rename `Connector::{ssl => openssl}()`. [#2503]
- `ClientRequest::send_body` now takes an `impl MessageBody`. [#2546]
- Rename `MessageBody => ResponseBody` to avoid conflicts with `MessageBody` trait. [#2546]
- Minimum supported Rust version (MSRV) is now 1.54.
### Fixed
- Send headers along with redirected requests. [#2310]
- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503]
- Remove unnecessary `Unpin` bounds on `*::send_stream`. [#2553]
- `impl Future` for `ResponseBody` no longer requires the body type be `Unpin`. [#2546]
- `impl Future` for `JsonBody` no longer requires the body type be `Unpin`. [#2546]
- `impl Stream` for `ClientResponse` no longer requires the body type be `Unpin`. [#2546]
### Removed
- `compress` crate feature. [#2250]
- `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869]
- `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869]
- `ClientRequest::set_header_if_none`; use `ClientRequest::insert_header_if_none`. [#1869]
- `ClientRequest::header`; use `ClientRequest::append_header`. [#1869]
- Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148]
- `ClientBuilder::default` function [#2008]
### Security
- `cookie` upgrade addresses [`RUSTSEC-2020-0071`].
[`RUSTSEC-2020-0071`]: https://rustsec.org/advisories/RUSTSEC-2020-0071.html
[#1813]: https://github.com/actix/actix-web/pull/1813
[#1869]: https://github.com/actix/actix-web/pull/1869
[#1905]: https://github.com/actix/actix-web/pull/1905
[#1905]: https://github.com/actix/actix-web/pull/1905
[#1931]: https://github.com/actix/actix-web/pull/1931
[#1969]: https://github.com/actix/actix-web/pull/1969
[#1969]: https://github.com/actix/actix-web/pull/1969
[#1981]: https://github.com/actix/actix-web/pull/1981
[#2008]: https://github.com/actix/actix-web/pull/2008
[#2024]: https://github.com/actix/actix-web/pull/2024
[#2050]: https://github.com/actix/actix-web/pull/2050
[#2063]: https://github.com/actix/actix-web/pull/2063
[#2081]: https://github.com/actix/actix-web/pull/2081
[#2081]: https://github.com/actix/actix-web/pull/2081
[#2094]: https://github.com/actix/actix-web/pull/2094
[#2114]: https://github.com/actix/actix-web/pull/2114
[#2116]: https://github.com/actix/actix-web/pull/2116
[#2148]: https://github.com/actix/actix-web/pull/2148
[#2250]: https://github.com/actix/actix-web/pull/2250
[#2310]: https://github.com/actix/actix-web/pull/2310
[#2414]: https://github.com/actix/actix-web/pull/2414
[#2425]: https://github.com/actix/actix-web/pull/2425
[#2474]: https://github.com/actix/actix-web/pull/2474
[#2503]: https://github.com/actix/actix-web/pull/2503
[#2510]: https://github.com/actix/actix-web/pull/2510
[#2546]: https://github.com/actix/actix-web/pull/2546
[#2553]: https://github.com/actix/actix-web/pull/2553
[#2555]: https://github.com/actix/actix-web/pull/2555
<details>
<summary>3.0.0 Pre-Releases</summary>
## 3.0.0-beta.21 - 2022-02-16 ## 3.0.0-beta.21 - 2022-02-16
- No significant changes since `3.0.0-beta.20`. - No significant changes since `3.0.0-beta.20`.
@ -170,6 +267,7 @@
[#1813]: https://github.com/actix/actix-web/pull/1813 [#1813]: https://github.com/actix/actix-web/pull/1813
</details>
## 2.0.3 - 2020-11-29 ## 2.0.3 - 2020-11-29
### Fixed ### Fixed

View File

@ -1,11 +1,11 @@
[package] [package]
name = "awc" name = "awc"
version = "3.0.0-beta.21" version = "3.0.0"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>", "fakeshadow <24548779@qq.com>",
] ]
description = "Async HTTP and WebSocket client library built on the Actix ecosystem" description = "Async HTTP and WebSocket client library"
keywords = ["actix", "http", "framework", "async", "web"] keywords = ["actix", "http", "framework", "async", "web"]
categories = [ categories = [
"network-programming", "network-programming",
@ -59,11 +59,11 @@ dangerous-h2c = []
[dependencies] [dependencies]
actix-codec = "0.5" actix-codec = "0.5"
actix-service = "2.0.0" actix-service = "2"
actix-http = { version = "3.0.0", features = ["http2", "ws"] } actix-http = { version = "3", features = ["http2", "ws"] }
actix-rt = { version = "2.1", default-features = false } actix-rt = { version = "2.1", default-features = false }
actix-tls = { version = "3", features = ["connect", "uri"] } actix-tls = { version = "3", features = ["connect", "uri"] }
actix-utils = "3.0.0" actix-utils = "3"
ahash = "0.7" ahash = "0.7"
base64 = "0.13" base64 = "0.13"
@ -93,13 +93,13 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features
trust-dns-resolver = { version = "0.20.0", optional = true } trust-dns-resolver = { version = "0.20.0", optional = true }
[dev-dependencies] [dev-dependencies]
actix-http = { version = "3.0.0", features = ["openssl"] } actix-http = { version = "3", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] }
actix-server = "2" actix-server = "2"
actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] }
actix-tls = { version = "3", features = ["openssl", "rustls"] } actix-tls = { version = "3", features = ["openssl", "rustls"] }
actix-utils = "3.0.0" actix-utils = "3"
actix-web = { version = "4.0.0", features = ["openssl"] } actix-web = { version = "4", features = ["openssl"] }
brotli = "3.3.3" brotli = "3.3.3"
const-str = "0.3" const-str = "0.3"

View File

@ -3,9 +3,9 @@
> Async HTTP and WebSocket client library. > Async HTTP and WebSocket client library.
[![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc)
[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.21)](https://docs.rs/awc/3.0.0-beta.21) [![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0)](https://docs.rs/awc/3.0.0)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc)
[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.21/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.21) [![Dependency Status](https://deps.rs/crate/awc/3.0.0/status.svg)](https://deps.rs/crate/awc/3.0.0)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources ## Documentation & Resources

View File

@ -246,7 +246,12 @@ where
/// ///
/// The default limit size is 100. /// The default limit size is 100.
pub fn limit(mut self, limit: usize) -> Self { pub fn limit(mut self, limit: usize) -> Self {
self.config.limit = limit; if limit == 0 {
self.config.limit = u32::MAX as usize;
} else {
self.config.limit = limit;
}
self self
} }

View File

@ -83,12 +83,12 @@ where
false false
}; };
framed.send((head, body.size()).into()).await?;
let mut pin_framed = Pin::new(&mut framed); let mut pin_framed = Pin::new(&mut framed);
// special handle for EXPECT request. // special handle for EXPECT request.
let (do_send, mut res_head) = if is_expect { let (do_send, mut res_head) = if is_expect {
pin_framed.send((head, body.size()).into()).await?;
let head = poll_fn(|cx| pin_framed.as_mut().poll_next(cx)) let head = poll_fn(|cx| pin_framed.as_mut().poll_next(cx))
.await .await
.ok_or(ConnectError::Disconnected)??; .ok_or(ConnectError::Disconnected)??;
@ -97,13 +97,17 @@ where
// and current head would be used as final response head. // and current head would be used as final response head.
(head.status == StatusCode::CONTINUE, Some(head)) (head.status == StatusCode::CONTINUE, Some(head))
} else { } else {
pin_framed.feed((head, body.size()).into()).await?;
(true, None) (true, None)
}; };
if do_send { if do_send {
// send request body // send request body
match body.size() { match body.size() {
BodySize::None | BodySize::Sized(0) => {} BodySize::None | BodySize::Sized(0) => {
poll_fn(|cx| pin_framed.as_mut().flush(cx)).await?;
}
_ => send_body(body, pin_framed.as_mut()).await?, _ => send_body(body, pin_framed.as_mut()).await?,
}; };

View File

@ -30,17 +30,35 @@ pub type BoxConnectorService = Rc<
pub type BoxedSocket = Box<dyn ConnectionIo>; pub type BoxedSocket = Box<dyn ConnectionIo>;
/// Combined HTTP and WebSocket request type received by connection service.
pub enum ConnectRequest { pub enum ConnectRequest {
/// Standard HTTP request.
///
/// Contains the request head, body type, and optional pre-resolved socket address.
Client(RequestHeadType, AnyBody, Option<net::SocketAddr>), Client(RequestHeadType, AnyBody, Option<net::SocketAddr>),
/// Tunnel used by WebSocket connection requests.
///
/// Contains the request head and optional pre-resolved socket address.
Tunnel(RequestHead, Option<net::SocketAddr>), Tunnel(RequestHead, Option<net::SocketAddr>),
} }
/// Combined HTTP response & WebSocket tunnel type returned from connection service.
pub enum ConnectResponse { pub enum ConnectResponse {
/// Standard HTTP response.
Client(ClientResponse), Client(ClientResponse),
/// Tunnel used for WebSocket communication.
///
/// Contains response head and framed HTTP/1.1 codec.
Tunnel(ResponseHead, Framed<BoxedSocket, ClientCodec>), Tunnel(ResponseHead, Framed<BoxedSocket, ClientCodec>),
} }
impl ConnectResponse { impl ConnectResponse {
/// Unwraps type into HTTP response.
///
/// # Panics
/// Panics if enum variant is not `Client`.
pub fn into_client_response(self) -> ClientResponse { pub fn into_client_response(self) -> ClientResponse {
match self { match self {
ConnectResponse::Client(res) => res, ConnectResponse::Client(res) => res,
@ -50,6 +68,10 @@ impl ConnectResponse {
} }
} }
/// Unwraps type into WebSocket tunnel response.
///
/// # Panics
/// Panics if enum variant is not `Tunnel`.
pub fn into_tunnel_response(self) -> (ResponseHead, Framed<BoxedSocket, ClientCodec>) { pub fn into_tunnel_response(self) -> (ResponseHead, Framed<BoxedSocket, ClientCodec>) {
match self { match self {
ConnectResponse::Tunnel(head, framed) => (head, framed), ConnectResponse::Tunnel(head, framed) => (head, framed),
@ -136,30 +158,37 @@ where
ConnectRequestProj::Connection { fut, req } => { ConnectRequestProj::Connection { fut, req } => {
let connection = ready!(fut.poll(cx))?; let connection = ready!(fut.poll(cx))?;
let req = req.take().unwrap(); let req = req.take().unwrap();
match req { match req {
ConnectRequest::Client(head, body, ..) => { ConnectRequest::Client(head, body, ..) => {
// send request // send request
let fut = ConnectRequestFuture::Client { let fut = ConnectRequestFuture::Client {
fut: connection.send_request(head, body), fut: connection.send_request(head, body),
}; };
self.set(fut); self.set(fut);
} }
ConnectRequest::Tunnel(head, ..) => { ConnectRequest::Tunnel(head, ..) => {
// send request // send request
let fut = ConnectRequestFuture::Tunnel { let fut = ConnectRequestFuture::Tunnel {
fut: connection.open_tunnel(RequestHeadType::from(head)), fut: connection.open_tunnel(RequestHeadType::from(head)),
}; };
self.set(fut); self.set(fut);
} }
} }
self.poll(cx) self.poll(cx)
} }
ConnectRequestProj::Client { fut } => { ConnectRequestProj::Client { fut } => {
let (head, payload) = ready!(fut.as_mut().poll(cx))?; let (head, payload) = ready!(fut.as_mut().poll(cx))?;
Poll::Ready(Ok(ConnectResponse::Client(ClientResponse::new( Poll::Ready(Ok(ConnectResponse::Client(ClientResponse::new(
head, payload, head, payload,
)))) ))))
} }
ConnectRequestProj::Tunnel { fut } => { ConnectRequestProj::Tunnel { fut } => {
let (head, framed) = ready!(fut.as_mut().poll(cx))?; let (head, framed) = ready!(fut.as_mut().poll(cx))?;
let framed = framed.into_map_io(|io| Box::new(io) as _); let framed = framed.into_map_io(|io| Box::new(io) as _);

View File

@ -1,22 +1,25 @@
//! `awc` is a HTTP and WebSocket client library built on the Actix ecosystem. //! `awc` is an asynchronous HTTP and WebSocket client library.
//! //!
//! # Making a GET request //! # `GET` Requests
//! ```no_run //! ```no_run
//! # #[actix_rt::main] //! # #[actix_rt::main]
//! # async fn main() -> Result<(), awc::error::SendRequestError> { //! # async fn main() -> Result<(), awc::error::SendRequestError> {
//! // create client
//! let mut client = awc::Client::default(); //! let mut client = awc::Client::default();
//! let response = client.get("http://www.rust-lang.org") // <- Create request builder
//! .insert_header(("User-Agent", "Actix-web"))
//! .send() // <- Send http request
//! .await?;
//! //!
//! println!("Response: {:?}", response); //! // construct request
//! let req = client.get("http://www.rust-lang.org")
//! .insert_header(("User-Agent", "awc/3.0"));
//!
//! // send request and await response
//! let res = req.send().await?;
//! println!("Response: {:?}", res);
//! # Ok(()) //! # Ok(())
//! # } //! # }
//! ``` //! ```
//! //!
//! # Making POST requests //! # `POST` Requests
//! ## Raw body contents //! ## Raw Body
//! ```no_run //! ```no_run
//! # #[actix_rt::main] //! # #[actix_rt::main]
//! # async fn main() -> Result<(), awc::error::SendRequestError> { //! # async fn main() -> Result<(), awc::error::SendRequestError> {
@ -28,20 +31,6 @@
//! # } //! # }
//! ``` //! ```
//! //!
//! ## Forms
//! ```no_run
//! # #[actix_rt::main]
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
//! let params = [("foo", "bar"), ("baz", "quux")];
//!
//! let mut client = awc::Client::default();
//! let response = client.post("http://httpbin.org/post")
//! .send_form(&params)
//! .await?;
//! # Ok(())
//! # }
//! ```
//!
//! ## JSON //! ## JSON
//! ```no_run //! ```no_run
//! # #[actix_rt::main] //! # #[actix_rt::main]
@ -59,6 +48,20 @@
//! # } //! # }
//! ``` //! ```
//! //!
//! ## URL Encoded Form
//! ```no_run
//! # #[actix_rt::main]
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
//! let params = [("foo", "bar"), ("baz", "quux")];
//!
//! let mut client = awc::Client::default();
//! let response = client.post("http://httpbin.org/post")
//! .send_form(&params)
//! .await?;
//! # Ok(())
//! # }
//! ```
//!
//! # Response Compression //! # Response Compression
//! All [official][iana-encodings] and common content encoding codecs are supported, optionally. //! All [official][iana-encodings] and common content encoding codecs are supported, optionally.
//! //!
@ -76,11 +79,12 @@
//! //!
//! [iana-encodings]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding //! [iana-encodings]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding
//! //!
//! # WebSocket support //! # WebSockets
//! ```no_run //! ```no_run
//! # #[actix_rt::main] //! # #[actix_rt::main]
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> { //! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use futures_util::{sink::SinkExt, stream::StreamExt}; //! use futures_util::{sink::SinkExt as _, stream::StreamExt as _};
//!
//! let (_resp, mut connection) = awc::Client::new() //! let (_resp, mut connection) = awc::Client::new()
//! .ws("ws://echo.websocket.org") //! .ws("ws://echo.websocket.org")
//! .connect() //! .connect()
@ -89,8 +93,9 @@
//! connection //! connection
//! .send(awc::ws::Message::Text("Echo".into())) //! .send(awc::ws::Message::Text("Echo".into()))
//! .await?; //! .await?;
//!
//! let response = connection.next().await.unwrap()?; //! let response = connection.next().await.unwrap()?;
//! # assert_eq!(response, awc::ws::Frame::Text("Echo".as_bytes().into())); //! assert_eq!(response, awc::ws::Frame::Text("Echo".into()));
//! # Ok(()) //! # Ok(())
//! # } //! # }
//! ``` //! ```

View File

@ -161,7 +161,8 @@ where
| StatusCode::SEE_OTHER | StatusCode::SEE_OTHER
| StatusCode::TEMPORARY_REDIRECT | StatusCode::TEMPORARY_REDIRECT
| StatusCode::PERMANENT_REDIRECT | StatusCode::PERMANENT_REDIRECT
if *max_redirect_times > 0 => if *max_redirect_times > 0
&& res.headers().contains_key(header::LOCATION) =>
{ {
let reuse_body = res.head().status == StatusCode::TEMPORARY_REDIRECT let reuse_body = res.head().status == StatusCode::TEMPORARY_REDIRECT
|| res.head().status == StatusCode::PERMANENT_REDIRECT; || res.head().status == StatusCode::PERMANENT_REDIRECT;
@ -245,26 +246,32 @@ where
} }
fn build_next_uri(res: &ClientResponse, prev_uri: &Uri) -> Result<Uri, SendRequestError> { fn build_next_uri(res: &ClientResponse, prev_uri: &Uri) -> Result<Uri, SendRequestError> {
let uri = res // responses without this header are not processed
.headers() let location = res.headers().get(header::LOCATION).unwrap();
.get(header::LOCATION)
.map(|value| { // try to parse the location and resolve to a full URI but fall back to default if it fails
// try to parse the location to a full uri let uri = Uri::try_from(location.as_bytes()).unwrap_or_else(|_| Uri::default());
let uri = Uri::try_from(value.as_bytes())
.map_err(|e| SendRequestError::Url(InvalidUrl::HttpError(e.into())))?; let uri = if uri.scheme().is_none() || uri.authority().is_none() {
if uri.scheme().is_none() || uri.authority().is_none() { let builder = Uri::builder()
let uri = Uri::builder() .scheme(prev_uri.scheme().cloned().unwrap())
.scheme(prev_uri.scheme().cloned().unwrap()) .authority(prev_uri.authority().cloned().unwrap());
.authority(prev_uri.authority().cloned().unwrap())
.path_and_query(value.as_bytes()) // when scheme or authority is missing treat the location value as path and query
.build()?; // recover error where location does not have leading slash
Ok::<_, SendRequestError>(uri) let path = if location.as_bytes().starts_with(b"/") {
} else { location.as_bytes().to_owned()
Ok(uri) } else {
} [b"/", location.as_bytes()].concat()
}) };
// TODO: this error type is wrong.
.ok_or(SendRequestError::Url(InvalidUrl::MissingScheme))??; builder
.path_and_query(path)
.build()
.map_err(|err| SendRequestError::Url(InvalidUrl::HttpError(err)))?
} else {
uri
};
Ok(uri) Ok(uri)
} }
@ -287,10 +294,13 @@ mod tests {
use actix_web::{web, App, Error, HttpRequest, HttpResponse}; use actix_web::{web, App, Error, HttpRequest, HttpResponse};
use super::*; use super::*;
use crate::{http::header::HeaderValue, ClientBuilder}; use crate::{
http::{header::HeaderValue, StatusCode},
ClientBuilder,
};
#[actix_rt::test] #[actix_rt::test]
async fn test_basic_redirect() { async fn basic_redirect() {
let client = ClientBuilder::new() let client = ClientBuilder::new()
.disable_redirects() .disable_redirects()
.wrap(Redirect::new().max_redirect_times(10)) .wrap(Redirect::new().max_redirect_times(10))
@ -315,6 +325,44 @@ mod tests {
assert_eq!(res.status().as_u16(), 400); assert_eq!(res.status().as_u16(), 400);
} }
#[actix_rt::test]
async fn redirect_relative_without_leading_slash() {
let client = ClientBuilder::new().finish();
let srv = actix_test::start(|| {
App::new()
.service(web::resource("/").route(web::to(|| async {
HttpResponse::Found()
.insert_header(("location", "abc/"))
.finish()
})))
.service(
web::resource("/abc/")
.route(web::to(|| async { HttpResponse::Accepted().finish() })),
)
});
let res = client.get(srv.url("/")).send().await.unwrap();
assert_eq!(res.status(), StatusCode::ACCEPTED);
}
#[actix_rt::test]
async fn redirect_without_location() {
let client = ClientBuilder::new()
.disable_redirects()
.wrap(Redirect::new().max_redirect_times(10))
.finish();
let srv = actix_test::start(|| {
App::new().service(web::resource("/").route(web::to(|| async {
Ok::<_, Error>(HttpResponse::Found().finish())
})))
});
let res = client.get(srv.url("/")).send().await.unwrap();
assert_eq!(res.status(), StatusCode::FOUND);
}
#[actix_rt::test] #[actix_rt::test]
async fn test_redirect_limit() { async fn test_redirect_limit() {
let client = ClientBuilder::new() let client = ClientBuilder::new()
@ -328,14 +376,14 @@ mod tests {
.service(web::resource("/").route(web::to(|| async { .service(web::resource("/").route(web::to(|| async {
Ok::<_, Error>( Ok::<_, Error>(
HttpResponse::Found() HttpResponse::Found()
.append_header(("location", "/test")) .insert_header(("location", "/test"))
.finish(), .finish(),
) )
}))) })))
.service(web::resource("/test").route(web::to(|| async { .service(web::resource("/test").route(web::to(|| async {
Ok::<_, Error>( Ok::<_, Error>(
HttpResponse::Found() HttpResponse::Found()
.append_header(("location", "/test2")) .insert_header(("location", "/test2"))
.finish(), .finish(),
) )
}))) })))
@ -345,8 +393,15 @@ mod tests {
}); });
let res = client.get(srv.url("/")).send().await.unwrap(); let res = client.get(srv.url("/")).send().await.unwrap();
assert_eq!(res.status(), StatusCode::FOUND);
assert_eq!(res.status().as_u16(), 302); assert_eq!(
res.headers()
.get(header::LOCATION)
.unwrap()
.to_str()
.unwrap(),
"/test2"
);
} }
#[actix_rt::test] #[actix_rt::test]

View File

@ -505,7 +505,7 @@ impl fmt::Debug for ClientRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!( writeln!(
f, f,
"\nClientRequest {:?} {}:{}", "\nClientRequest {:?} {} {}",
self.head.version, self.head.method, self.head.uri self.head.version, self.head.method, self.head.uri
)?; )?;
writeln!(f, " headers:")?; writeln!(f, " headers:")?;

View File

@ -160,7 +160,7 @@ where
/// ///
/// # Errors /// # Errors
/// `Future` implementation returns error if: /// `Future` implementation returns error if:
/// - content length is greater than [limit](JsonBody::limit) (default: 2 MiB) /// - content length is greater than [limit](ResponseBody::limit) (default: 2 MiB)
/// ///
/// # Examples /// # Examples
/// ```no_run /// ```no_run