1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-04 09:56:22 +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
## 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

View File

@ -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"

View File

@ -3,11 +3,11 @@
> Static file serving for Actix Web
[![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)
![License](https://img.shields.io/crates/l/actix-files.svg)
<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)
[![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 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,

View File

@ -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"

View File

@ -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]

View File

@ -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"

View File

@ -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.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)
![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.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)
[![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()
// 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()

View File

@ -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],

View File

@ -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)) => {

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() {
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,

View File

@ -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"))]

View File

@ -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() {

View File

@ -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`.

View File

@ -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"

View File

@ -3,11 +3,11 @@
> Multipart form support for Actix Web.
[![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)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
<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)
[![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]
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 = [] }

View File

@ -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`.

View File

@ -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 }

View File

@ -3,11 +3,11 @@
> 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)
[![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)
![License](https://img.shields.io/crates/l/actix-web-actors.svg)
<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)
[![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
## 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

View File

@ -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"

View File

@ -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/)

View File

@ -6,10 +6,10 @@
<p>
[![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)
![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 />
[![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)
@ -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
}
```

View File

@ -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.

View File

@ -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
//! }
//! ```
//!

View File

@ -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]),

View File

@ -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> {

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.
/// 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);
/// }
/// ```

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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
}

View File

@ -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?,
};

View File

@ -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 _);

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
//! # #[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(&params)
//! .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(&params)
//! .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(())
//! # }
//! ```

View File

@ -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]

View File

@ -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:")?;

View File

@ -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