mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-03 17:41:30 +02:00
Compare commits
22 Commits
web-v4.0.0
...
http-v3.0.
Author | SHA1 | Date | |
---|---|---|---|
5611b98c0d | |||
dce9438518 | |||
be986d96b3 | |||
8ddb24b49b | |||
87f627cd5d | |||
03456b8a33 | |||
8c2fad3164 | |||
62fbd225bc | |||
0fa4d999d9 | |||
da4c849f62 | |||
49cd303c3b | |||
955c3ac0c4 | |||
56e5c19b85 | |||
3f03af1c59 | |||
25c0673278 | |||
e7a05f9892 | |||
2f13e5f675 | |||
9f964751f6 | |||
fcca515387 | |||
075932d823 | |||
cb379c0e0c | |||
d4a5d450de |
@ -1,6 +1,13 @@
|
||||
# Changes
|
||||
|
||||
## 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
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-files"
|
||||
version = "0.6.0-beta.16"
|
||||
version = "0.6.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"fakeshadow <24548779@qq.com>",
|
||||
@ -22,10 +22,10 @@ path = "src/lib.rs"
|
||||
experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
|
||||
|
||||
[dependencies]
|
||||
actix-http = "3.0.0"
|
||||
actix-http = "3"
|
||||
actix-service = "2"
|
||||
actix-utils = "3"
|
||||
actix-web = { version = "4.0.0", default-features = false }
|
||||
actix-web = { version = "4", default-features = false }
|
||||
|
||||
askama_escape = "0.10"
|
||||
bitflags = "1"
|
||||
|
@ -3,11 +3,11 @@
|
||||
> Static file serving for Actix Web
|
||||
|
||||
[](https://crates.io/crates/actix-files)
|
||||
[](https://docs.rs/actix-files/0.6.0-beta.16)
|
||||
[](https://docs.rs/actix-files/0.6.0)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-files/0.6.0-beta.16)
|
||||
[](https://deps.rs/crate/actix-files/0.6.0)
|
||||
[](https://crates.io/crates/actix-files)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@ -128,7 +128,7 @@ impl NamedFile {
|
||||
let ct = from_path(&path).first_or_octet_stream();
|
||||
|
||||
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::JAVASCRIPT | mime::JSON => DispositionType::Inline,
|
||||
name if name == "wasm" => DispositionType::Inline,
|
||||
|
@ -29,13 +29,13 @@ default = []
|
||||
openssl = ["tls-openssl", "awc/openssl"]
|
||||
|
||||
[dependencies]
|
||||
actix-service = "2.0.0"
|
||||
actix-service = "2"
|
||||
actix-codec = "0.5"
|
||||
actix-tls = "3"
|
||||
actix-utils = "3.0.0"
|
||||
actix-utils = "3"
|
||||
actix-rt = "2.2"
|
||||
actix-server = "2"
|
||||
awc = { version = "3.0.0-beta.21", default-features = false }
|
||||
awc = { version = "3", default-features = false }
|
||||
|
||||
base64 = "0.13"
|
||||
bytes = "1"
|
||||
@ -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", default-features = false, features = ["cookies"] }
|
||||
actix-http = "3.0.0"
|
||||
actix-web = { version = "4", default-features = false, features = ["cookies"] }
|
||||
actix-http = "3"
|
||||
|
@ -3,6 +3,30 @@
|
||||
## 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
|
||||
### Dependencies
|
||||
- Updated `actix-*` to Tokio v1-based versions. [#1813]
|
||||
@ -745,10 +769,10 @@
|
||||
- Remove `ResponseError` impl for `actix::actors::resolver::ResolverError`
|
||||
due to deprecate of resolver actor. [#1813]
|
||||
- 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]
|
||||
- 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]
|
||||
|
||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-http"
|
||||
version = "3.0.0"
|
||||
version = "3.0.4"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
@ -20,7 +20,7 @@ edition = "2018"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
# 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]
|
||||
name = "actix_http"
|
||||
|
@ -3,11 +3,11 @@
|
||||
> HTTP primitives for the Actix ecosystem.
|
||||
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://docs.rs/actix-http/3.0.0)
|
||||
[](https://docs.rs/actix-http/3.0.4)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-http/3.0.0)
|
||||
[](https://deps.rs/crate/actix-http/3.0.4)
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@ -18,7 +18,8 @@ async fn main() -> std::io::Result<()> {
|
||||
HttpService::build()
|
||||
// pass the app to service builder
|
||||
// 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()
|
||||
})?
|
||||
.run()
|
||||
|
@ -293,22 +293,35 @@ impl MessageType for ResponseHead {
|
||||
let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY;
|
||||
|
||||
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);
|
||||
match res.parse(src)? {
|
||||
let mut res = httparse::Response::new(&mut []);
|
||||
|
||||
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) => {
|
||||
let version = if res.version.unwrap() == 1 {
|
||||
Version::HTTP_11
|
||||
} else {
|
||||
Version::HTTP_10
|
||||
};
|
||||
|
||||
let status = StatusCode::from_u16(res.code.unwrap())
|
||||
.map_err(|_| ParseError::Status)?;
|
||||
HeaderIndex::record(src, res.headers, &mut headers);
|
||||
|
||||
(len, version, status, res.headers.len())
|
||||
}
|
||||
|
||||
httparse::Status::Partial => {
|
||||
return if src.len() >= MAX_BUFFER_SIZE {
|
||||
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] =
|
||||
[EMPTY_HEADER_INDEX; MAX_HEADERS];
|
||||
|
||||
pub(crate) const EMPTY_HEADER_ARRAY: [httparse::Header<'static>; MAX_HEADERS] =
|
||||
[httparse::EMPTY_HEADER; MAX_HEADERS];
|
||||
|
||||
impl HeaderIndex {
|
||||
pub(crate) fn record(
|
||||
bytes: &[u8],
|
||||
|
@ -375,8 +375,6 @@ where
|
||||
DispatchError::Io(err)
|
||||
})?;
|
||||
|
||||
this.flags.set(Flags::KEEP_ALIVE, this.codec.keep_alive());
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
|
||||
@ -459,7 +457,12 @@ where
|
||||
}
|
||||
|
||||
// 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 } => {
|
||||
@ -757,6 +760,7 @@ where
|
||||
|
||||
let mut updated = false;
|
||||
|
||||
// decode from read buf as many full requests as possible
|
||||
loop {
|
||||
match this.codec.decode(this.read_buf) {
|
||||
Ok(Some(msg)) => {
|
||||
|
@ -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() {
|
||||
buffer[index] = c & 0b1101_1111;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
|
||||
index += 1;
|
||||
@ -528,7 +529,7 @@ mod tests {
|
||||
use std::rc::Rc;
|
||||
|
||||
use bytes::Bytes;
|
||||
use http::header::AUTHORIZATION;
|
||||
use http::header::{AUTHORIZATION, UPGRADE_INSECURE_REQUESTS};
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
@ -559,6 +560,9 @@ mod tests {
|
||||
head.headers
|
||||
.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 _ = head.encode_headers(
|
||||
@ -574,6 +578,7 @@ mod tests {
|
||||
assert!(data.contains("Connection: close\r\n"));
|
||||
assert!(data.contains("Content-Type: plain/text\r\n"));
|
||||
assert!(data.contains("Date: date\r\n"));
|
||||
assert!(data.contains("Upgrade-Insecure-Requests: 1\r\n"));
|
||||
|
||||
let _ = head.encode_headers(
|
||||
&mut bytes,
|
||||
|
@ -13,7 +13,8 @@ use crate::error::PayloadError;
|
||||
/// A boxed payload stream.
|
||||
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;
|
||||
|
||||
#[cfg(not(feature = "http2"))]
|
||||
|
@ -144,7 +144,7 @@ impl ResponseBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set connection type to Upgrade
|
||||
/// Set connection type to `Upgrade`.
|
||||
#[inline]
|
||||
pub fn upgrade<V>(&mut self, value: V) -> &mut Self
|
||||
where
|
||||
@ -161,7 +161,7 @@ impl ResponseBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Force close connection, even if it is marked as keep-alive
|
||||
/// Force-close connection, even if it is marked as keep-alive.
|
||||
#[inline]
|
||||
pub fn force_close(&mut self) -> &mut Self {
|
||||
if let Some(parts) = self.inner() {
|
||||
|
@ -3,6 +3,10 @@
|
||||
## 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
|
||||
- No significant changes since `0.4.0-beta.12`.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-multipart"
|
||||
version = "0.4.0-beta.13"
|
||||
version = "0.4.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Multipart form support for Actix Web"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
@ -14,7 +14,7 @@ name = "actix_multipart"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-utils = "3.0.0"
|
||||
actix-utils = "3"
|
||||
actix-web = { version = "4.0.0", default-features = false }
|
||||
|
||||
bytes = "1"
|
||||
|
@ -3,11 +3,11 @@
|
||||
> Multipart form support for Actix Web.
|
||||
|
||||
[](https://crates.io/crates/actix-multipart)
|
||||
[](https://docs.rs/actix-multipart/0.4.0-beta.13)
|
||||
[](https://docs.rs/actix-multipart/0.4.0)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-multipart/0.4.0-beta.13)
|
||||
[](https://deps.rs/crate/actix-multipart/0.4.0)
|
||||
[](https://crates.io/crates/actix-multipart)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@ -29,13 +29,13 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.5"
|
||||
actix-http = "3.0.0"
|
||||
actix-http = "3"
|
||||
actix-http-test = "3.0.0-beta.13"
|
||||
actix-rt = "2.1"
|
||||
actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
actix-web = { version = "4.0.0", default-features = false, features = ["cookies"] }
|
||||
awc = { version = "3.0.0-beta.21", default-features = false, features = ["cookies"] }
|
||||
actix-service = "2"
|
||||
actix-utils = "3"
|
||||
actix-web = { version = "4", default-features = false, features = ["cookies"] }
|
||||
awc = { version = "3", default-features = false, features = ["cookies"] }
|
||||
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
|
||||
futures-util = { version = "0.3.7", default-features = false, features = [] }
|
||||
|
@ -3,6 +3,16 @@
|
||||
## 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
|
||||
- No significant changes since `4.0.0-beta.11`.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web-actors"
|
||||
version = "4.0.0-beta.12"
|
||||
version = "4.1.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix actors support for Actix Web"
|
||||
keywords = ["actix", "http", "web", "framework", "async"]
|
||||
@ -14,21 +14,21 @@ name = "actix_web_actors"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix = { version = "0.12.0", default-features = false }
|
||||
actix = { version = ">=0.12, <0.14", default-features = false }
|
||||
actix-codec = "0.5"
|
||||
actix-http = "3.0.0"
|
||||
actix-web = { version = "4.0.0", default-features = false }
|
||||
actix-http = "3"
|
||||
actix-web = { version = "4", default-features = false }
|
||||
|
||||
bytes = "1"
|
||||
bytestring = "1"
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
pin-project-lite = "0.2"
|
||||
tokio = { version = "1.8.4", features = ["sync"] }
|
||||
tokio = { version = "1.13.1", features = ["sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2"
|
||||
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"
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
|
@ -3,11 +3,11 @@
|
||||
> Actix actors support for Actix Web.
|
||||
|
||||
[](https://crates.io/crates/actix-web-actors)
|
||||
[](https://docs.rs/actix-web-actors/4.0.0-beta.12)
|
||||
[](https://docs.rs/actix-web-actors/4.1.0)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-web-actors/4.0.0-beta.12)
|
||||
[](https://deps.rs/crate/actix-web-actors/4.1.0)
|
||||
[](https://crates.io/crates/actix-web-actors)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@ -3,6 +3,11 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 4.0.1 - 2022-02-25
|
||||
### Fixed
|
||||
- Use stable version in readme example.
|
||||
|
||||
|
||||
## 4.0.0 - 2022-02-25
|
||||
### Dependencies
|
||||
- Updated `actix-*` to Tokio v1-based versions. [#1813]
|
||||
@ -10,7 +15,7 @@
|
||||
- Updated `cookie` to `0.16`. [#2555]
|
||||
- Updated `language-tags` to `0.3`.
|
||||
- Updated `rand` to `0.8`.
|
||||
- Updated `rustls` to `0.20.0`. [#2414]
|
||||
- Updated `rustls` to `0.20`. [#2414]
|
||||
- Updated `tokio` to `1`.
|
||||
|
||||
### Added
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "4.0.0"
|
||||
version = "4.0.1"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
@ -71,9 +71,9 @@ actix-service = "2"
|
||||
actix-utils = "3"
|
||||
actix-tls = { version = "3", default-features = false, optional = true }
|
||||
|
||||
actix-http = { version = "3.0.0", features = ["http2", "ws"] }
|
||||
actix-router = "0.5.0"
|
||||
actix-web-codegen = { version = "4.0.0", optional = true }
|
||||
actix-http = { version = "3", features = ["http2", "ws"] }
|
||||
actix-router = "0.5"
|
||||
actix-web-codegen = { version = "4", optional = true }
|
||||
|
||||
ahash = "0.7"
|
||||
bytes = "1"
|
||||
@ -90,7 +90,7 @@ once_cell = "1.5"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
pin-project-lite = "0.2.7"
|
||||
regex = "1.4"
|
||||
regex = "1.5.5"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_urlencoded = "0.7"
|
||||
@ -100,9 +100,9 @@ time = { version = "0.3", default-features = false, features = ["formatting"] }
|
||||
url = "2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-files = "0.6.0-beta.16"
|
||||
actix-files = "0.6"
|
||||
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"
|
||||
const-str = "0.3"
|
||||
|
@ -3,7 +3,7 @@
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@ -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)
|
||||
- [Guards API](#guards-api)
|
||||
- [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)
|
||||
|
||||
## 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();
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
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/)
|
||||
|
@ -6,10 +6,10 @@
|
||||
<p>
|
||||
|
||||
[](https://crates.io/crates/actix-web)
|
||||
[](https://docs.rs/actix-web/4.0.0)
|
||||
[](https://docs.rs/actix-web/4.0.1)
|
||||

|
||||

|
||||
[](https://deps.rs/crate/actix-web/4.0.0)
|
||||
[](https://deps.rs/crate/actix-web/4.0.1)
|
||||
<br />
|
||||
[](https://github.com/actix/actix-web/actions/workflows/ci.yml)
|
||||
[](https://codecov.io/gh/actix/actix-web)
|
||||
@ -48,7 +48,7 @@ Dependencies:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
actix-web = "4.0.0"
|
||||
actix-web = "4"
|
||||
```
|
||||
|
||||
Code:
|
||||
@ -56,18 +56,19 @@ Code:
|
||||
```rust
|
||||
use actix_web::{get, web, App, HttpServer, Responder};
|
||||
|
||||
#[get("/{id}/{name}/index.html")]
|
||||
async fn index(params: web::Path<(u32, String)>) -> impl Responder {
|
||||
let (id, name) = params.into_inner();
|
||||
format!("Hello {}! id:{}", name, id)
|
||||
#[get("/hello/{name}")]
|
||||
async fn greet(name: web::Path<String>) -> impl Responder {
|
||||
format!("Hello {name}!")
|
||||
}
|
||||
|
||||
#[actix_web::main] // or #[tokio::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
HttpServer::new(|| App::new().service(index))
|
||||
.bind(("127.0.0.1", 8080))?
|
||||
.run()
|
||||
.await
|
||||
HttpServer::new(|| {
|
||||
App::new().service(greet)
|
||||
})
|
||||
.bind(("127.0.0.1", 8080))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -159,7 +159,7 @@ impl ConnectionInfo {
|
||||
pub fn realip_remote_addr(&self) -> Option<&str> {
|
||||
self.realip_remote_addr
|
||||
.as_deref()
|
||||
.or_else(|| self.peer_addr.as_deref())
|
||||
.or(self.peer_addr.as_deref())
|
||||
}
|
||||
|
||||
/// Returns serialized IP address of the peer connection.
|
||||
|
@ -4,18 +4,19 @@
|
||||
//! ```no_run
|
||||
//! use actix_web::{get, web, App, HttpServer, Responder};
|
||||
//!
|
||||
//! #[get("/{id}/{name}/index.html")]
|
||||
//! async fn index(path: web::Path<(u32, String)>) -> impl Responder {
|
||||
//! let (id, name) = path.into_inner();
|
||||
//! format!("Hello {}! id:{}", name, id)
|
||||
//! #[get("/hello/{name}")]
|
||||
//! async fn greet(name: web::Path<String>) -> impl Responder {
|
||||
//! format!("Hello {}!", name)
|
||||
//! }
|
||||
//!
|
||||
//! #[actix_web::main]
|
||||
//! #[actix_web::main] // or #[tokio::main]
|
||||
//! async fn main() -> std::io::Result<()> {
|
||||
//! HttpServer::new(|| App::new().service(index))
|
||||
//! .bind("127.0.0.1:8080")?
|
||||
//! .run()
|
||||
//! .await
|
||||
//! HttpServer::new(|| {
|
||||
//! App::new().service(greet)
|
||||
//! })
|
||||
//! .bind(("127.0.0.1", 8080))?
|
||||
//! .run()
|
||||
//! .await
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
|
@ -151,7 +151,7 @@ impl ResourceMap {
|
||||
.char_indices()
|
||||
.filter_map(|(i, c)| (c == '/').then(|| i))
|
||||
.nth(2)
|
||||
.unwrap_or_else(|| path.len());
|
||||
.unwrap_or(path.len());
|
||||
|
||||
(
|
||||
Cow::Borrowed(&path[..third_slash_index]),
|
||||
|
@ -78,18 +78,18 @@ pub struct ServiceRequest {
|
||||
}
|
||||
|
||||
impl ServiceRequest {
|
||||
/// Construct service request
|
||||
/// Construct `ServiceRequest` from parts.
|
||||
pub(crate) fn new(req: HttpRequest, payload: Payload) -> Self {
|
||||
Self { req, payload }
|
||||
}
|
||||
|
||||
/// Deconstruct request into parts
|
||||
/// Deconstruct `ServiceRequest` into inner parts.
|
||||
#[inline]
|
||||
pub fn into_parts(self) -> (HttpRequest, Payload) {
|
||||
(self.req, self.payload)
|
||||
}
|
||||
|
||||
/// Get mutable access to inner `HttpRequest` and `Payload`
|
||||
/// Returns mutable accessors to inner parts.
|
||||
#[inline]
|
||||
pub fn parts_mut(&mut self) -> (&mut HttpRequest, &mut Payload) {
|
||||
(&mut self.req, &mut self.payload)
|
||||
@ -105,9 +105,7 @@ impl ServiceRequest {
|
||||
Self { req, payload }
|
||||
}
|
||||
|
||||
/// Construct request from request.
|
||||
///
|
||||
/// The returned `ServiceRequest` would have no payload.
|
||||
/// Construct `ServiceRequest` with no payload from given `HttpRequest`.
|
||||
#[inline]
|
||||
pub fn from_request(req: HttpRequest) -> Self {
|
||||
ServiceRequest {
|
||||
@ -116,63 +114,63 @@ impl ServiceRequest {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create service response
|
||||
/// Create `ServiceResponse` from this request and given response.
|
||||
#[inline]
|
||||
pub fn into_response<B, R: Into<Response<B>>>(self, res: R) -> ServiceResponse<B> {
|
||||
let res = HttpResponse::from(res.into());
|
||||
ServiceResponse::new(self.req, res)
|
||||
}
|
||||
|
||||
/// Create service response for error
|
||||
/// Create `ServiceResponse` from this request and given error.
|
||||
#[inline]
|
||||
pub fn error_response<E: Into<Error>>(self, err: E) -> ServiceResponse {
|
||||
let res = HttpResponse::from_error(err.into());
|
||||
ServiceResponse::new(self.req, res)
|
||||
}
|
||||
|
||||
/// This method returns reference to the request head
|
||||
/// Returns a reference to the request head.
|
||||
#[inline]
|
||||
pub fn head(&self) -> &RequestHead {
|
||||
self.req.head()
|
||||
}
|
||||
|
||||
/// This method returns reference to the request head
|
||||
/// Returns a mutable reference to the request head.
|
||||
#[inline]
|
||||
pub fn head_mut(&mut self) -> &mut RequestHead {
|
||||
self.req.head_mut()
|
||||
}
|
||||
|
||||
/// Request's uri.
|
||||
/// Returns the request URI.
|
||||
#[inline]
|
||||
pub fn uri(&self) -> &Uri {
|
||||
&self.head().uri
|
||||
}
|
||||
|
||||
/// Read the Request method.
|
||||
/// Returns the request method.
|
||||
#[inline]
|
||||
pub fn method(&self) -> &Method {
|
||||
&self.head().method
|
||||
}
|
||||
|
||||
/// Read the Request Version.
|
||||
/// Returns the request version.
|
||||
#[inline]
|
||||
pub fn version(&self) -> Version {
|
||||
self.head().version
|
||||
}
|
||||
|
||||
/// Returns a reference to request headers.
|
||||
#[inline]
|
||||
/// Returns request's headers.
|
||||
pub fn headers(&self) -> &HeaderMap {
|
||||
&self.head().headers
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to request headers.
|
||||
#[inline]
|
||||
/// Returns mutable request's headers.
|
||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||
&mut self.head_mut().headers
|
||||
}
|
||||
|
||||
/// The target path of this Request.
|
||||
/// Returns request path.
|
||||
#[inline]
|
||||
pub fn path(&self) -> &str {
|
||||
self.head().uri.path()
|
||||
@ -184,7 +182,7 @@ impl ServiceRequest {
|
||||
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
|
||||
/// the Actix Web server, then it would be address of this proxy.
|
||||
@ -197,24 +195,23 @@ impl ServiceRequest {
|
||||
self.head().peer_addr
|
||||
}
|
||||
|
||||
/// Get *ConnectionInfo* for the current request.
|
||||
/// Returns a reference to connection info.
|
||||
#[inline]
|
||||
pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> {
|
||||
self.req.connection_info()
|
||||
}
|
||||
|
||||
/// Returns a reference to the Path parameters.
|
||||
/// Returns reference to the Path parameters.
|
||||
///
|
||||
/// Params is a container for URL parameters.
|
||||
/// A variable segment is specified in the form `{identifier}`,
|
||||
/// where the identifier can be used later in a request handler to
|
||||
/// access the matched value for that segment.
|
||||
/// Params is a container for URL parameters. A variable segment is specified in the form
|
||||
/// `{identifier}`, where the identifier can be used later in a request handler to access the
|
||||
/// matched value for that segment.
|
||||
#[inline]
|
||||
pub fn match_info(&self) -> &Path<Url> {
|
||||
self.req.match_info()
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the Path parameters.
|
||||
/// Returns a mutable reference to the path match information.
|
||||
#[inline]
|
||||
pub fn match_info_mut(&mut self) -> &mut Path<Url> {
|
||||
self.req.match_info_mut()
|
||||
@ -232,13 +229,13 @@ impl ServiceRequest {
|
||||
self.req.match_pattern()
|
||||
}
|
||||
|
||||
/// Get a reference to a `ResourceMap` of current application.
|
||||
/// Returns a reference to the application's resource map.
|
||||
#[inline]
|
||||
pub fn resource_map(&self) -> &ResourceMap {
|
||||
self.req.resource_map()
|
||||
}
|
||||
|
||||
/// Service configuration
|
||||
/// Returns a reference to the application's configuration.
|
||||
#[inline]
|
||||
pub fn app_config(&self) -> &AppConfig {
|
||||
self.req.app_config()
|
||||
@ -262,6 +259,7 @@ impl ServiceRequest {
|
||||
self.req.conn_data()
|
||||
}
|
||||
|
||||
/// Return request cookies.
|
||||
#[cfg(feature = "cookies")]
|
||||
#[inline]
|
||||
pub fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
|
||||
|
@ -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.
|
||||
/// You can generate various types of request via TestRequest's methods:
|
||||
/// * `TestRequest::to_request` creates `actix_http::Request` instance.
|
||||
/// * `TestRequest::to_srv_request` creates `ServiceRequest` instance, which is used for testing middlewares and chain adapters.
|
||||
/// * `TestRequest::to_srv_response` creates `ServiceResponse` instance.
|
||||
/// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers.
|
||||
/// - [`TestRequest::to_request`] creates an [`actix_http::Request`](Request).
|
||||
/// - [`TestRequest::to_srv_request`] creates a [`ServiceRequest`], which is used for testing middlewares and chain adapters.
|
||||
/// - [`TestRequest::to_srv_response`] creates a [`ServiceResponse`].
|
||||
/// - [`TestRequest::to_http_request`] creates an [`HttpRequest`], which is used for testing handlers.
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage};
|
||||
@ -42,15 +42,17 @@ use crate::cookie::{Cookie, CookieJar};
|
||||
/// }
|
||||
///
|
||||
/// #[actix_web::test]
|
||||
/// # // force rustdoc to display the correct thing and also compile check the test
|
||||
/// # async fn _test() {}
|
||||
/// 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();
|
||||
///
|
||||
/// let resp = index(req).await.unwrap();
|
||||
/// let resp = index(req).await;
|
||||
/// assert_eq!(resp.status(), StatusCode::OK);
|
||||
///
|
||||
/// 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);
|
||||
/// }
|
||||
/// ```
|
||||
|
@ -3,6 +3,103 @@
|
||||
## 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
|
||||
- No significant changes since `3.0.0-beta.20`.
|
||||
|
||||
@ -170,6 +267,7 @@
|
||||
|
||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||
|
||||
</details>
|
||||
|
||||
## 2.0.3 - 2020-11-29
|
||||
### Fixed
|
||||
|
@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "awc"
|
||||
version = "3.0.0-beta.21"
|
||||
version = "3.0.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.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"]
|
||||
categories = [
|
||||
"network-programming",
|
||||
@ -59,11 +59,11 @@ dangerous-h2c = []
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.5"
|
||||
actix-service = "2.0.0"
|
||||
actix-http = { version = "3.0.0", features = ["http2", "ws"] }
|
||||
actix-service = "2"
|
||||
actix-http = { version = "3", features = ["http2", "ws"] }
|
||||
actix-rt = { version = "2.1", default-features = false }
|
||||
actix-tls = { version = "3", features = ["connect", "uri"] }
|
||||
actix-utils = "3.0.0"
|
||||
actix-utils = "3"
|
||||
|
||||
ahash = "0.7"
|
||||
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 }
|
||||
|
||||
[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-server = "2"
|
||||
actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] }
|
||||
actix-tls = { version = "3", features = ["openssl", "rustls"] }
|
||||
actix-utils = "3.0.0"
|
||||
actix-web = { version = "4.0.0", features = ["openssl"] }
|
||||
actix-utils = "3"
|
||||
actix-web = { version = "4", features = ["openssl"] }
|
||||
|
||||
brotli = "3.3.3"
|
||||
const-str = "0.3"
|
||||
|
@ -3,9 +3,9 @@
|
||||
> Async HTTP and WebSocket client library.
|
||||
|
||||
[](https://crates.io/crates/awc)
|
||||
[](https://docs.rs/awc/3.0.0-beta.21)
|
||||
[](https://docs.rs/awc/3.0.0)
|
||||

|
||||
[](https://deps.rs/crate/awc/3.0.0-beta.21)
|
||||
[](https://deps.rs/crate/awc/3.0.0)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Documentation & Resources
|
||||
|
@ -246,7 +246,12 @@ where
|
||||
///
|
||||
/// The default limit size is 100.
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -83,12 +83,12 @@ where
|
||||
false
|
||||
};
|
||||
|
||||
framed.send((head, body.size()).into()).await?;
|
||||
|
||||
let mut pin_framed = Pin::new(&mut framed);
|
||||
|
||||
// special handle for EXPECT request.
|
||||
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))
|
||||
.await
|
||||
.ok_or(ConnectError::Disconnected)??;
|
||||
@ -97,13 +97,17 @@ where
|
||||
// and current head would be used as final response head.
|
||||
(head.status == StatusCode::CONTINUE, Some(head))
|
||||
} else {
|
||||
pin_framed.feed((head, body.size()).into()).await?;
|
||||
|
||||
(true, None)
|
||||
};
|
||||
|
||||
if do_send {
|
||||
// send request body
|
||||
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?,
|
||||
};
|
||||
|
||||
|
@ -30,17 +30,35 @@ pub type BoxConnectorService = Rc<
|
||||
|
||||
pub type BoxedSocket = Box<dyn ConnectionIo>;
|
||||
|
||||
/// Combined HTTP and WebSocket request type received by connection service.
|
||||
pub enum ConnectRequest {
|
||||
/// Standard HTTP request.
|
||||
///
|
||||
/// Contains the request head, body type, and optional pre-resolved socket address.
|
||||
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>),
|
||||
}
|
||||
|
||||
/// Combined HTTP response & WebSocket tunnel type returned from connection service.
|
||||
pub enum ConnectResponse {
|
||||
/// Standard HTTP response.
|
||||
Client(ClientResponse),
|
||||
|
||||
/// Tunnel used for WebSocket communication.
|
||||
///
|
||||
/// Contains response head and framed HTTP/1.1 codec.
|
||||
Tunnel(ResponseHead, Framed<BoxedSocket, ClientCodec>),
|
||||
}
|
||||
|
||||
impl ConnectResponse {
|
||||
/// Unwraps type into HTTP response.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if enum variant is not `Client`.
|
||||
pub fn into_client_response(self) -> ClientResponse {
|
||||
match self {
|
||||
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>) {
|
||||
match self {
|
||||
ConnectResponse::Tunnel(head, framed) => (head, framed),
|
||||
@ -136,30 +158,37 @@ where
|
||||
ConnectRequestProj::Connection { fut, req } => {
|
||||
let connection = ready!(fut.poll(cx))?;
|
||||
let req = req.take().unwrap();
|
||||
|
||||
match req {
|
||||
ConnectRequest::Client(head, body, ..) => {
|
||||
// send request
|
||||
let fut = ConnectRequestFuture::Client {
|
||||
fut: connection.send_request(head, body),
|
||||
};
|
||||
|
||||
self.set(fut);
|
||||
}
|
||||
|
||||
ConnectRequest::Tunnel(head, ..) => {
|
||||
// send request
|
||||
let fut = ConnectRequestFuture::Tunnel {
|
||||
fut: connection.open_tunnel(RequestHeadType::from(head)),
|
||||
};
|
||||
|
||||
self.set(fut);
|
||||
}
|
||||
}
|
||||
|
||||
self.poll(cx)
|
||||
}
|
||||
|
||||
ConnectRequestProj::Client { fut } => {
|
||||
let (head, payload) = ready!(fut.as_mut().poll(cx))?;
|
||||
Poll::Ready(Ok(ConnectResponse::Client(ClientResponse::new(
|
||||
head, payload,
|
||||
))))
|
||||
}
|
||||
|
||||
ConnectRequestProj::Tunnel { fut } => {
|
||||
let (head, framed) = ready!(fut.as_mut().poll(cx))?;
|
||||
let framed = framed.into_map_io(|io| Box::new(io) as _);
|
||||
|
@ -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
|
||||
//! # #[actix_rt::main]
|
||||
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
||||
//! // create client
|
||||
//! 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(())
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! # Making POST requests
|
||||
//! ## Raw body contents
|
||||
//! # `POST` Requests
|
||||
//! ## Raw Body
|
||||
//! ```no_run
|
||||
//! # #[actix_rt::main]
|
||||
//! # 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(¶ms)
|
||||
//! .await?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ## JSON
|
||||
//! ```no_run
|
||||
//! # #[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(¶ms)
|
||||
//! .await?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! # Response Compression
|
||||
//! 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
|
||||
//!
|
||||
//! # WebSocket support
|
||||
//! # WebSockets
|
||||
//! ```no_run
|
||||
//! # #[actix_rt::main]
|
||||
//! # 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()
|
||||
//! .ws("ws://echo.websocket.org")
|
||||
//! .connect()
|
||||
@ -89,8 +93,9 @@
|
||||
//! connection
|
||||
//! .send(awc::ws::Message::Text("Echo".into()))
|
||||
//! .await?;
|
||||
//!
|
||||
//! 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(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
@ -161,7 +161,8 @@ where
|
||||
| StatusCode::SEE_OTHER
|
||||
| StatusCode::TEMPORARY_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
|
||||
|| res.head().status == StatusCode::PERMANENT_REDIRECT;
|
||||
@ -245,26 +246,32 @@ where
|
||||
}
|
||||
|
||||
fn build_next_uri(res: &ClientResponse, prev_uri: &Uri) -> Result<Uri, SendRequestError> {
|
||||
let uri = res
|
||||
.headers()
|
||||
.get(header::LOCATION)
|
||||
.map(|value| {
|
||||
// try to parse the location to a full uri
|
||||
let uri = Uri::try_from(value.as_bytes())
|
||||
.map_err(|e| SendRequestError::Url(InvalidUrl::HttpError(e.into())))?;
|
||||
if uri.scheme().is_none() || uri.authority().is_none() {
|
||||
let uri = Uri::builder()
|
||||
.scheme(prev_uri.scheme().cloned().unwrap())
|
||||
.authority(prev_uri.authority().cloned().unwrap())
|
||||
.path_and_query(value.as_bytes())
|
||||
.build()?;
|
||||
Ok::<_, SendRequestError>(uri)
|
||||
} else {
|
||||
Ok(uri)
|
||||
}
|
||||
})
|
||||
// TODO: this error type is wrong.
|
||||
.ok_or(SendRequestError::Url(InvalidUrl::MissingScheme))??;
|
||||
// responses without this header are not processed
|
||||
let location = res.headers().get(header::LOCATION).unwrap();
|
||||
|
||||
// try to parse the location and resolve to a full URI but fall back to default if it fails
|
||||
let uri = Uri::try_from(location.as_bytes()).unwrap_or_else(|_| Uri::default());
|
||||
|
||||
let uri = if uri.scheme().is_none() || uri.authority().is_none() {
|
||||
let builder = Uri::builder()
|
||||
.scheme(prev_uri.scheme().cloned().unwrap())
|
||||
.authority(prev_uri.authority().cloned().unwrap());
|
||||
|
||||
// when scheme or authority is missing treat the location value as path and query
|
||||
// recover error where location does not have leading slash
|
||||
let path = if location.as_bytes().starts_with(b"/") {
|
||||
location.as_bytes().to_owned()
|
||||
} else {
|
||||
[b"/", location.as_bytes()].concat()
|
||||
};
|
||||
|
||||
builder
|
||||
.path_and_query(path)
|
||||
.build()
|
||||
.map_err(|err| SendRequestError::Url(InvalidUrl::HttpError(err)))?
|
||||
} else {
|
||||
uri
|
||||
};
|
||||
|
||||
Ok(uri)
|
||||
}
|
||||
@ -287,10 +294,13 @@ mod tests {
|
||||
use actix_web::{web, App, Error, HttpRequest, HttpResponse};
|
||||
|
||||
use super::*;
|
||||
use crate::{http::header::HeaderValue, ClientBuilder};
|
||||
use crate::{
|
||||
http::{header::HeaderValue, StatusCode},
|
||||
ClientBuilder,
|
||||
};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_basic_redirect() {
|
||||
async fn basic_redirect() {
|
||||
let client = ClientBuilder::new()
|
||||
.disable_redirects()
|
||||
.wrap(Redirect::new().max_redirect_times(10))
|
||||
@ -315,6 +325,44 @@ mod tests {
|
||||
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]
|
||||
async fn test_redirect_limit() {
|
||||
let client = ClientBuilder::new()
|
||||
@ -328,14 +376,14 @@ mod tests {
|
||||
.service(web::resource("/").route(web::to(|| async {
|
||||
Ok::<_, Error>(
|
||||
HttpResponse::Found()
|
||||
.append_header(("location", "/test"))
|
||||
.insert_header(("location", "/test"))
|
||||
.finish(),
|
||||
)
|
||||
})))
|
||||
.service(web::resource("/test").route(web::to(|| async {
|
||||
Ok::<_, Error>(
|
||||
HttpResponse::Found()
|
||||
.append_header(("location", "/test2"))
|
||||
.insert_header(("location", "/test2"))
|
||||
.finish(),
|
||||
)
|
||||
})))
|
||||
@ -345,8 +393,15 @@ mod tests {
|
||||
});
|
||||
|
||||
let res = client.get(srv.url("/")).send().await.unwrap();
|
||||
|
||||
assert_eq!(res.status().as_u16(), 302);
|
||||
assert_eq!(res.status(), StatusCode::FOUND);
|
||||
assert_eq!(
|
||||
res.headers()
|
||||
.get(header::LOCATION)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"/test2"
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
|
@ -505,7 +505,7 @@ impl fmt::Debug for ClientRequest {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"\nClientRequest {:?} {}:{}",
|
||||
"\nClientRequest {:?} {} {}",
|
||||
self.head.version, self.head.method, self.head.uri
|
||||
)?;
|
||||
writeln!(f, " headers:")?;
|
||||
|
@ -160,7 +160,7 @@ where
|
||||
///
|
||||
/// # Errors
|
||||
/// `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
|
||||
/// ```no_run
|
||||
|
Reference in New Issue
Block a user