mirror of
https://github.com/actix/actix-web.git
synced 2024-11-22 09:31:10 +00:00
prepare awc release 3.0.0 (#2684)
This commit is contained in:
parent
87f627cd5d
commit
8ddb24b49b
19 changed files with 306 additions and 93 deletions
|
@ -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,13 @@
|
|||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 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]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "actix-http"
|
||||
version = "3.0.2"
|
||||
version = "3.0.3"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
> HTTP primitives for the Actix ecosystem.
|
||||
|
||||
[![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.2)](https://docs.rs/actix-http/3.0.2)
|
||||
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.3)](https://docs.rs/actix-http/3.0.3)
|
||||
[![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)
|
||||
<br />
|
||||
[![dependency status](https://deps.rs/crate/actix-http/3.0.2/status.svg)](https://deps.rs/crate/actix-http/3.0.2)
|
||||
[![dependency status](https://deps.rs/crate/actix-http/3.0.3/status.svg)](https://deps.rs/crate/actix-http/3.0.3)
|
||||
[![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)
|
||||
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 = [] }
|
||||
|
|
|
@ -28,7 +28,7 @@ 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 }
|
||||
|
|
|
@ -15,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
|
||||
|
|
|
@ -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"
|
||||
|
@ -100,9 +100,9 @@ time = { version = "0.3", default-features = false, features = ["formatting"] }
|
|||
url = "2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-files = "0.6.0"
|
||||
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,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,6 +1,6 @@
|
|||
[package]
|
||||
name = "awc"
|
||||
version = "3.0.0-beta.21"
|
||||
version = "3.0.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"fakeshadow <24548779@qq.com>",
|
||||
|
@ -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.
|
||||
|
||||
[![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)
|
||||
[![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)
|
||||
|
||||
## 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 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:")?;
|
||||
|
|
Loading…
Reference in a new issue