mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-12 13:33:41 +02:00
Compare commits
41 Commits
http-test-
...
http-v3.0.
Author | SHA1 | Date | |
---|---|---|---|
8ddb24b49b | |||
87f627cd5d | |||
03456b8a33 | |||
8c2fad3164 | |||
62fbd225bc | |||
0fa4d999d9 | |||
da4c849f62 | |||
49cd303c3b | |||
955c3ac0c4 | |||
56e5c19b85 | |||
3f03af1c59 | |||
25c0673278 | |||
e7a05f9892 | |||
2f13e5f675 | |||
9f964751f6 | |||
fcca515387 | |||
075932d823 | |||
cb379c0e0c | |||
d4a5d450de | |||
542200cbc2 | |||
d0c08dbb7d | |||
d0b5fb18d2 | |||
12fb3412a5 | |||
2665357a0c | |||
693271e571 | |||
10ef9b0751 | |||
ce00c88963 | |||
75e6ffb057 | |||
ad38973767 | |||
1c1d6477ef | |||
53509a5361 | |||
a6f27baff1 | |||
218e34ee17 | |||
11bfa84926 | |||
5aa6f713c7 | |||
151a15da74 | |||
1ce58ecb30 | |||
f940653981 | |||
b291e29882 | |||
f843776f36 | |||
52f7d96358 |
@ -1,4 +1,4 @@
|
|||||||
# Changes
|
# Changelog
|
||||||
|
|
||||||
Changelogs are kept separately for each crate in this repo.
|
Changelogs are kept separately for each crate in this repo.
|
||||||
|
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
- Add support for streaming audio files by setting the `content-disposition` header `inline` instead of `attachement`. [#2645]
|
||||||
|
|
||||||
|
[#2645]: https://github.com/actix/actix-web/pull/2645
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.0 - 2022-02-25
|
||||||
|
- No significant changes since `0.6.0-beta.16`.
|
||||||
|
|
||||||
|
|
||||||
## 0.6.0-beta.16 - 2022-01-31
|
## 0.6.0-beta.16 - 2022-01-31
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-files"
|
name = "actix-files"
|
||||||
version = "0.6.0-beta.16"
|
version = "0.6.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"fakeshadow <24548779@qq.com>",
|
"fakeshadow <24548779@qq.com>",
|
||||||
@ -22,10 +22,10 @@ path = "src/lib.rs"
|
|||||||
experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
|
experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-http = "3.0.0-rc.3"
|
actix-http = "3"
|
||||||
actix-service = "2"
|
actix-service = "2"
|
||||||
actix-utils = "3"
|
actix-utils = "3"
|
||||||
actix-web = { version = "4.0.0-rc.3", default-features = false }
|
actix-web = { version = "4", default-features = false }
|
||||||
|
|
||||||
askama_escape = "0.10"
|
askama_escape = "0.10"
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
@ -44,5 +44,5 @@ tokio-uring = { version = "0.2", optional = true, features = ["bytes"] }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-test = "0.1.0-beta.13"
|
actix-test = "0.1.0-beta.13"
|
||||||
actix-web = "4.0.0-rc.3"
|
actix-web = "4.0.0"
|
||||||
tempfile = "3.2"
|
tempfile = "3.2"
|
||||||
|
@ -3,16 +3,16 @@
|
|||||||
> Static file serving for Actix Web
|
> Static file serving for Actix Web
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-files)
|
[](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)
|
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||||

|

|
||||||
<br />
|
<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://crates.io/crates/actix-files)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
## Documentation & Resources
|
## Documentation & Resources
|
||||||
|
|
||||||
- [API Documentation](https://docs.rs/actix-files/)
|
- [API Documentation](https://docs.rs/actix-files)
|
||||||
- [Example Project](https://github.com/actix/examples/tree/master/basics/static_index)
|
- [Example Project](https://github.com/actix/examples/tree/master/basics/static-files)
|
||||||
- Minimum Supported Rust Version (MSRV): 1.54
|
- Minimum Supported Rust Version (MSRV): 1.54
|
||||||
|
@ -81,7 +81,7 @@ async fn chunked_read_file_callback(
|
|||||||
) -> Result<(File, Bytes), Error> {
|
) -> Result<(File, Bytes), Error> {
|
||||||
use io::{Read as _, Seek as _};
|
use io::{Read as _, Seek as _};
|
||||||
|
|
||||||
let res = actix_web::rt::task::spawn_blocking(move || {
|
let res = actix_web::web::block(move || {
|
||||||
let mut buf = Vec::with_capacity(max_bytes);
|
let mut buf = Vec::with_capacity(max_bytes);
|
||||||
|
|
||||||
file.seek(io::SeekFrom::Start(offset))?;
|
file.seek(io::SeekFrom::Start(offset))?;
|
||||||
@ -94,8 +94,7 @@ async fn chunked_read_file_callback(
|
|||||||
Ok((file, Bytes::from(buf)))
|
Ok((file, Bytes::from(buf)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await
|
.await??;
|
||||||
.map_err(|_| actix_web::error::BlockingError)??;
|
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
@ -96,18 +96,18 @@ impl NamedFile {
|
|||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
|
/// use std::{
|
||||||
|
/// io::{self, Write as _},
|
||||||
|
/// env,
|
||||||
|
/// fs::File
|
||||||
|
/// };
|
||||||
/// use actix_files::NamedFile;
|
/// use actix_files::NamedFile;
|
||||||
/// use std::io::{self, Write};
|
|
||||||
/// use std::env;
|
|
||||||
/// use std::fs::File;
|
|
||||||
///
|
///
|
||||||
/// fn main() -> io::Result<()> {
|
/// let mut file = File::create("foo.txt")?;
|
||||||
/// let mut file = File::create("foo.txt")?;
|
/// file.write_all(b"Hello, world!")?;
|
||||||
/// file.write_all(b"Hello, world!")?;
|
/// let named_file = NamedFile::from_file(file, "bar.txt")?;
|
||||||
/// let named_file = NamedFile::from_file(file, "bar.txt")?;
|
/// # std::fs::remove_file("foo.txt");
|
||||||
/// # std::fs::remove_file("foo.txt");
|
/// Ok(())
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn from_file<P: AsRef<Path>>(file: File, path: P) -> io::Result<NamedFile> {
|
pub fn from_file<P: AsRef<Path>>(file: File, path: P) -> io::Result<NamedFile> {
|
||||||
let path = path.as_ref().to_path_buf();
|
let path = path.as_ref().to_path_buf();
|
||||||
@ -128,7 +128,7 @@ impl NamedFile {
|
|||||||
let ct = from_path(&path).first_or_octet_stream();
|
let ct = from_path(&path).first_or_octet_stream();
|
||||||
|
|
||||||
let disposition = match ct.type_() {
|
let disposition = match ct.type_() {
|
||||||
mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline,
|
mime::IMAGE | mime::TEXT | mime::AUDIO | mime::VIDEO => DispositionType::Inline,
|
||||||
mime::APPLICATION => match ct.subtype() {
|
mime::APPLICATION => match ct.subtype() {
|
||||||
mime::JAVASCRIPT | mime::JSON => DispositionType::Inline,
|
mime::JAVASCRIPT | mime::JSON => DispositionType::Inline,
|
||||||
name if name == "wasm" => DispositionType::Inline,
|
name if name == "wasm" => DispositionType::Inline,
|
||||||
|
@ -29,13 +29,13 @@ default = []
|
|||||||
openssl = ["tls-openssl", "awc/openssl"]
|
openssl = ["tls-openssl", "awc/openssl"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-service = "2.0.0"
|
actix-service = "2"
|
||||||
actix-codec = "0.5"
|
actix-codec = "0.5"
|
||||||
actix-tls = "3"
|
actix-tls = "3"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3"
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-server = "2"
|
actix-server = "2"
|
||||||
awc = { version = "3.0.0-beta.21", default-features = false }
|
awc = { version = "3", default-features = false }
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
|||||||
tokio = { version = "1.8.4", features = ["sync"] }
|
tokio = { version = "1.8.4", features = ["sync"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-web = { version = "4.0.0-rc.3", default-features = false, features = ["cookies"] }
|
actix-web = { version = "4", default-features = false, features = ["cookies"] }
|
||||||
actix-http = "3.0.0-rc.3"
|
actix-http = "3"
|
||||||
|
@ -3,6 +3,316 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## 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]
|
||||||
|
|
||||||
|
[#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]
|
||||||
|
- Updated `bytes` to `1.0`. [#1813]
|
||||||
|
- Updated `h2` to `0.3`. [#1813]
|
||||||
|
- Updated `rustls` to `0.20.0`. [#2414]
|
||||||
|
- Updated `language-tags` to `0.3`.
|
||||||
|
- Updated `tokio` to `1`.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Crate Features:
|
||||||
|
- `ws`; disabled by default. [#2618]
|
||||||
|
- `http2`; disabled by default. [#2618]
|
||||||
|
- `compress-brotli`; disabled by default. [#2618]
|
||||||
|
- `compress-gzip`; disabled by default. [#2618]
|
||||||
|
- `compress-zstd`; disabled by default. [#2618]
|
||||||
|
- Functions:
|
||||||
|
- `body::to_bytes` for async collecting message body into Bytes. [#2158]
|
||||||
|
- Traits:
|
||||||
|
- `TryIntoHeaderPair`; allows using typed and untyped headers in the same methods. [#1869]
|
||||||
|
- Types:
|
||||||
|
- `body::BoxBody`; a boxed message body with boxed errors. [#2183]
|
||||||
|
- `body::EitherBody` enum. [#2468]
|
||||||
|
- `body::None` struct. [#2468]
|
||||||
|
- Re-export `http` crate's `Error` type as `error::HttpError`. [#2171]
|
||||||
|
- Variants:
|
||||||
|
- `ContentEncoding::Zstd` along with . [#2244]
|
||||||
|
- `Protocol::Http3` for future compatibility and also mark `#[non_exhaustive]`. [00ba8d55]
|
||||||
|
- Methods:
|
||||||
|
- `ContentEncoding::to_header_value()`. [#2501]
|
||||||
|
- `header::QualityItem::{max, min}()`. [#2486]
|
||||||
|
- `header::QualityItem::zero()` that uses `Quality::ZERO`. [#2501]
|
||||||
|
- `HeaderMap::drain()` as an efficient draining iterator. [#1964]
|
||||||
|
- `HeaderMap::len_keys()` has the behavior of the old `len` method. [#1964]
|
||||||
|
- `MessageBody::boxed` trait method for wrapping boxing types efficiently. [#2520]
|
||||||
|
- `MessageBody::try_into_bytes` trait method, with default implementation, for optimizations on body types that complete in exactly one poll. [#2522]
|
||||||
|
- `Request::conn_data()`. [#2491]
|
||||||
|
- `Request::take_conn_data()`. [#2491]
|
||||||
|
- `Request::take_req_data()`. [#2487]
|
||||||
|
- `Response::{ok, bad_request, not_found, internal_server_error}()`. [#2159]
|
||||||
|
- `Response::into_body()` that consumes response and returns body type. [#2201]
|
||||||
|
- `Response::map_into_boxed_body()`. [#2468]
|
||||||
|
- `ResponseBuilder::append_header()` method which allows using typed and untyped headers. [#1869]
|
||||||
|
- `ResponseBuilder::insert_header()` method which allows using typed and untyped headers. [#1869]
|
||||||
|
- `ResponseHead::set_camel_case_headers()`. [#2587]
|
||||||
|
- `TestRequest::insert_header()` method which allows using typed and untyped headers. [#1869]
|
||||||
|
- Implementations:
|
||||||
|
- Implement `Clone for ws::HandshakeError`. [#2468]
|
||||||
|
- Implement `Clone` for `body::AnyBody<S> where S: Clone`. [#2448]
|
||||||
|
- Implement `Clone` for `RequestHead`. [#2487]
|
||||||
|
- Implement `Clone` for `ResponseHead`. [#2585]
|
||||||
|
- Implement `Copy` for `QualityItem<T> where T: Copy`. [#2501]
|
||||||
|
- Implement `Default` for `ContentEncoding`. [#1912]
|
||||||
|
- Implement `Default` for `HttpServiceBuilder`. [#2611]
|
||||||
|
- Implement `Default` for `KeepAlive`. [#2611]
|
||||||
|
- Implement `Default` for `Response`. [#2201]
|
||||||
|
- Implement `Default` for `ws::Codec`. [#1920]
|
||||||
|
- Implement `Display` for `header::Quality`. [#2486]
|
||||||
|
- Implement `Eq` for `header::ContentEncoding`. [#2501]
|
||||||
|
- Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470]
|
||||||
|
- Implement `From<Duration>` for `KeepAlive`. [#2611]
|
||||||
|
- Implement `From<Option<Duration>>` for `KeepAlive`. [#2611]
|
||||||
|
- Implement `From<Vec<u8>>` for `Response<Vec<u8>>`. [#2625]
|
||||||
|
- Implement `FromStr` for `ContentEncoding`. [#1912]
|
||||||
|
- Implement `Header` for `ContentEncoding`. [#1912]
|
||||||
|
- Implement `IntoHeaderValue` for `ContentEncoding`. [#1912]
|
||||||
|
- Implement `IntoIterator` for `HeaderMap`. [#1964]
|
||||||
|
- Implement `MessageBody` for `bytestring::ByteString`. [#2468]
|
||||||
|
- Implement `MessageBody` for `Pin<Box<T>> where T: MessageBody`. [#2152]
|
||||||
|
- Misc:
|
||||||
|
- Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171]
|
||||||
|
- Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171]
|
||||||
|
- `Quality::ZERO` associated constant equivalent to `q=0`. [#2501]
|
||||||
|
- `header::Quality::{MAX, MIN}` associated constants equivalent to `q=1` and `q=0.001`, respectively. [#2486]
|
||||||
|
- Timeout for canceling HTTP/2 server side connection handshake. Configurable with `ServiceConfig::client_timeout`; defaults to 5 seconds. [#2483]
|
||||||
|
- `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Traits:
|
||||||
|
- Rename `IntoHeaderValue => TryIntoHeaderValue`. [#2510]
|
||||||
|
- `MessageBody` now has an associated `Error` type. [#2183]
|
||||||
|
- Types:
|
||||||
|
- `Protocol` enum is now marked `#[non_exhaustive]`.
|
||||||
|
- `error::DispatcherError` enum is now marked `#[non_exhaustive]`. [#2624]
|
||||||
|
- `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377]
|
||||||
|
- Error enums are marked `#[non_exhaustive]`. [#2161]
|
||||||
|
- Rename `PayloadStream` to `BoxedPayloadStream`. [#2545]
|
||||||
|
- The body type parameter of `Response` no longer has a default. [#2152]
|
||||||
|
- Enum Variants:
|
||||||
|
- Rename `ContentEncoding::{Br => Brotli}`. [#2501]
|
||||||
|
- `Payload` inner fields are now named. [#2545]
|
||||||
|
- `ws::Message::Text` now contains a `bytestring::ByteString`. [#1864]
|
||||||
|
- Methods:
|
||||||
|
- Rename `ServiceConfig::{client_timer_expire => client_request_deadline}`. [#2611]
|
||||||
|
- Rename `ServiceConfig::{client_disconnect_timer => client_disconnect_deadline}`. [#2611]
|
||||||
|
- Rename `h1::Codec::{keepalive => keep_alive}`. [#2611]
|
||||||
|
- Rename `h1::Codec::{keepalive_enabled => keep_alive_enabled}`. [#2611]
|
||||||
|
- Rename `h1::ClientCodec::{keepalive => keep_alive}`. [#2611]
|
||||||
|
- Rename `h1::ClientPayloadCodec::{keepalive => keep_alive}`. [#2611]
|
||||||
|
- Rename `header::EntityTag::{weak => new_weak, strong => new_strong}`. [#2565]
|
||||||
|
- Rename `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std `TryInto` trait. [#1894]
|
||||||
|
- Deadline methods in `ServiceConfig` now return `std::time::Instant`s instead of Tokio's wrapper type. [#2611]
|
||||||
|
- Places in `Response` where `ResponseBody<B>` was received or returned now simply use `B`. [#2201]
|
||||||
|
- `encoding::Encoder::response` now returns `AnyBody<Encoder<B>>`. [#2448]
|
||||||
|
- `Extensions::insert` returns replaced item. [#1904]
|
||||||
|
- `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527]
|
||||||
|
- `HeaderMap::insert` now returns iterator of removed values. [#1964]
|
||||||
|
- `HeaderMap::len` now returns number of values instead of number of keys. [#1964]
|
||||||
|
- `HeaderMap::remove` now returns iterator of removed values. [#1964]
|
||||||
|
- `ResponseBuilder::body(B)` now returns `Response<EitherBody<B>>`. [#2468]
|
||||||
|
- `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed `mime` types. [#1894]
|
||||||
|
- `ResponseBuilder::finish()` now returns `Response<EitherBody<()>>`. [#2468]
|
||||||
|
- `ResponseBuilder::json` now takes `impl Serialize`. [#2052]
|
||||||
|
- `ResponseBuilder::message_body` now returns a `Result`. [#2201]∑
|
||||||
|
- `ServiceConfig::keep_alive` now returns a `KeepAlive`. [#2611]
|
||||||
|
- `ws::hash_key` now returns array. [#2035]
|
||||||
|
- Trait Implementations:
|
||||||
|
- Implementation of `Stream` for `Payload` no longer requires the `Stream` variant be `Unpin`. [#2545]
|
||||||
|
- Implementation of `Future` for `h1::SendResponse` no longer requires the body type be `Unpin`. [#2545]
|
||||||
|
- Implementation of `Stream` for `encoding::Decoder` no longer requires the stream type be `Unpin`. [#2545]
|
||||||
|
- Implementation of `From` for error types now return a `Response<BoxBody>`. [#2468]
|
||||||
|
- Misc:
|
||||||
|
- `header` module is now public. [#2171]
|
||||||
|
- `uri` module is now public. [#2171]
|
||||||
|
- Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487]
|
||||||
|
- All error trait bounds in server service builders have changed from `Into<Error>` to `Into<Response<BoxBody>>`. [#2253]
|
||||||
|
- All error trait bounds in message body and stream impls changed from `Into<Error>` to `Into<Box<dyn std::error::Error>>`. [#2253]
|
||||||
|
- Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467]
|
||||||
|
- Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491]
|
||||||
|
- Brotli (de)compression support is now provided by the `brotli` crate. [#2538]
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- A `Vary` header is now correctly sent along with compressed content. [#2501]
|
||||||
|
- HTTP/1.1 dispatcher correctly uses client request timeout. [#2611]
|
||||||
|
- Fixed issue where handlers that took payload but then dropped without reading it to EOF it would cause keep-alive connections to become stuck. [#2624]
|
||||||
|
- `ContentEncoding`'s `Identity` variant can now be parsed from a string. [#2501]
|
||||||
|
- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuration parameter. [#2226]
|
||||||
|
- Remove unnecessary `Into<Error>` bound on `Encoder` body types. [#2375]
|
||||||
|
- Remove unnecessary `Unpin` bound on `ResponseBuilder::streaming`. [#2253]
|
||||||
|
- `BodyStream` and `SizedStream` are no longer restricted to `Unpin` types. [#2152]
|
||||||
|
- Fixed slice creation pointing to potential uninitialized data on h1 encoder. [#2364]
|
||||||
|
- Fixed quality parse error in Accept-Encoding header. [#2344]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Crate Features:
|
||||||
|
- `compress` feature. [#2065]
|
||||||
|
- `cookies` feature. [#2065]
|
||||||
|
- `trust-dns` feature. [#2425]
|
||||||
|
- `actors` optional feature and trait implementation for `actix` types. [#1969]
|
||||||
|
- Functions:
|
||||||
|
- `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486]
|
||||||
|
- Types:
|
||||||
|
- `body::Body`; replaced with `EitherBody` and `BoxBody`. [#2468]
|
||||||
|
- `body::ResponseBody`. [#2446]
|
||||||
|
- `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. Due to the removal of this type from `tokio-openssl` crate. OpenSSL handshake error now returns `ConnectError::SslError`. [#1813]
|
||||||
|
- `error::Canceled` re-export. [#1994]
|
||||||
|
- `error::Result` type alias. [#2201]
|
||||||
|
- `error::BlockingError` [#2660]
|
||||||
|
- `InternalError` and all the error types it constructed were moved up to `actix-web`. [#2215]
|
||||||
|
- Typed HTTP headers; they have moved up to `actix-web`. [2094]
|
||||||
|
- Re-export of `http` crate's `HeaderMap` types in addition to ours. [#2171]
|
||||||
|
- Enum Variants:
|
||||||
|
- `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446]
|
||||||
|
- `ContentEncoding::Auto`. [#2501]
|
||||||
|
- `EncoderError::Boxed`. [#2446]
|
||||||
|
- Methods:
|
||||||
|
- `ContentEncoding::is_compression()`. [#2501]
|
||||||
|
- `h1::Payload::readany()`. [#2545]
|
||||||
|
- `HttpMessage::cookie[s]()` trait methods. [#2065]
|
||||||
|
- `HttpServiceBuilder::new()`; use `default` instead. [#2611]
|
||||||
|
- `on_connect` (previously deprecated) methods have been removed; use `on_connect_ext`. [#1857]
|
||||||
|
- `Response::build_from()`. [#2159]
|
||||||
|
- `Response::error()` [#2205]
|
||||||
|
- `Response::take_body()` and old `Response::into_body()` method that casted body type. [#2201]
|
||||||
|
- `Response`'s status code builders. [#2159]
|
||||||
|
- `ResponseBuilder::{if_true, if_some}()` (previously deprecated). [#2148]
|
||||||
|
- `ResponseBuilder::{set, set_header}()`; use `ResponseBuilder::insert_header()`. [#1869]
|
||||||
|
- `ResponseBuilder::extensions[_mut]()`. [#2585]
|
||||||
|
- `ResponseBuilder::header()`; use `ResponseBuilder::append_header()`. [#1869]
|
||||||
|
- `ResponseBuilder::json()`. [#2148]
|
||||||
|
- `ResponseBuilder::json2()`. [#1903]
|
||||||
|
- `ResponseBuilder::streaming()`. [#2468]
|
||||||
|
- `ResponseHead::extensions[_mut]()`. [#2585]
|
||||||
|
- `ServiceConfig::{client_timer, keep_alive_timer}()`. [#2611]
|
||||||
|
- `TestRequest::with_hdr()`; use `TestRequest::default().insert_header()`. [#1869]
|
||||||
|
- `TestRequest::with_header()`; use `TestRequest::default().insert_header()`. [#1869]
|
||||||
|
- Trait implementations:
|
||||||
|
- Implementation of `Copy` for `ws::Codec`. [#1920]
|
||||||
|
- Implementation of `From<Option<usize>> for KeepAlive`; use `Duration`s instead. [#2611]
|
||||||
|
- Implementation of `From<serde_json::Value>` for `Body`. [#2148]
|
||||||
|
- Implementation of `From<usize> for KeepAlive`; use `Duration`s instead. [#2611]
|
||||||
|
- Implementation of `Future` for `Response`. [#2201]
|
||||||
|
- Implementation of `Future` for `ResponseBuilder`. [#2468]
|
||||||
|
- Implementation of `Into<Error>` for `Response<Body>`. [#2215]
|
||||||
|
- Implementation of `Into<Error>` for `ResponseBuilder`. [#2215]
|
||||||
|
- Implementation of `ResponseError` for `actix_utils::timeout::TimeoutError`. [#2127]
|
||||||
|
- Implementation of `ResponseError` for `CookieParseError`. [#2065]
|
||||||
|
- Implementation of `TryFrom<u16>` for `header::Quality`. [#2486]
|
||||||
|
- Misc:
|
||||||
|
- `http` module; most everything it contained is exported at the crate root. [#2488]
|
||||||
|
- `cookies` module (re-export). [#2065]
|
||||||
|
- `client` module. Connector types now live in `awc`. [#2425]
|
||||||
|
- `error` field from `Response`. [#2205]
|
||||||
|
- `downcast` and `downcast_get_type_id` macros. [#2291]
|
||||||
|
- Down-casting for `MessageBody` types; use standard `Any` trait. [#2183]
|
||||||
|
|
||||||
|
|
||||||
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
|
[#1845]: https://github.com/actix/actix-web/pull/1845
|
||||||
|
[#1857]: https://github.com/actix/actix-web/pull/1857
|
||||||
|
[#1864]: https://github.com/actix/actix-web/pull/1864
|
||||||
|
[#1869]: https://github.com/actix/actix-web/pull/1869
|
||||||
|
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||||
|
[#1894]: https://github.com/actix/actix-web/pull/1894
|
||||||
|
[#1903]: https://github.com/actix/actix-web/pull/1903
|
||||||
|
[#1904]: https://github.com/actix/actix-web/pull/1904
|
||||||
|
[#1912]: https://github.com/actix/actix-web/pull/1912
|
||||||
|
[#1920]: https://github.com/actix/actix-web/pull/1920
|
||||||
|
[#1964]: https://github.com/actix/actix-web/pull/1964
|
||||||
|
[#1969]: https://github.com/actix/actix-web/pull/1969
|
||||||
|
[#1981]: https://github.com/actix/actix-web/pull/1981
|
||||||
|
[#1994]: https://github.com/actix/actix-web/pull/1994
|
||||||
|
[#2035]: https://github.com/actix/actix-web/pull/2035
|
||||||
|
[#2052]: https://github.com/actix/actix-web/pull/2052
|
||||||
|
[#2065]: https://github.com/actix/actix-web/pull/2065
|
||||||
|
[#2094]: https://github.com/actix/actix-web/pull/2094
|
||||||
|
[#2127]: https://github.com/actix/actix-web/pull/2127
|
||||||
|
[#2148]: https://github.com/actix/actix-web/pull/2148
|
||||||
|
[#2152]: https://github.com/actix/actix-web/pull/2152
|
||||||
|
[#2158]: https://github.com/actix/actix-web/pull/2158
|
||||||
|
[#2159]: https://github.com/actix/actix-web/pull/2159
|
||||||
|
[#2161]: https://github.com/actix/actix-web/pull/2161
|
||||||
|
[#2171]: https://github.com/actix/actix-web/pull/2171
|
||||||
|
[#2183]: https://github.com/actix/actix-web/pull/2183
|
||||||
|
[#2196]: https://github.com/actix/actix-web/pull/2196
|
||||||
|
[#2201]: https://github.com/actix/actix-web/pull/2201
|
||||||
|
[#2205]: https://github.com/actix/actix-web/pull/2205
|
||||||
|
[#2215]: https://github.com/actix/actix-web/pull/2215
|
||||||
|
[#2244]: https://github.com/actix/actix-web/pull/2244
|
||||||
|
[#2250]: https://github.com/actix/actix-web/pull/2250
|
||||||
|
[#2253]: https://github.com/actix/actix-web/pull/2253
|
||||||
|
[#2291]: https://github.com/actix/actix-web/pull/2291
|
||||||
|
[#2344]: https://github.com/actix/actix-web/pull/2344
|
||||||
|
[#2364]: https://github.com/actix/actix-web/pull/2364
|
||||||
|
[#2375]: https://github.com/actix/actix-web/pull/2375
|
||||||
|
[#2377]: https://github.com/actix/actix-web/pull/2377
|
||||||
|
[#2414]: https://github.com/actix/actix-web/pull/2414
|
||||||
|
[#2425]: https://github.com/actix/actix-web/pull/2425
|
||||||
|
[#2442]: https://github.com/actix/actix-web/pull/2442
|
||||||
|
[#2446]: https://github.com/actix/actix-web/pull/2446
|
||||||
|
[#2448]: https://github.com/actix/actix-web/pull/2448
|
||||||
|
[#2456]: https://github.com/actix/actix-web/pull/2456
|
||||||
|
[#2467]: https://github.com/actix/actix-web/pull/2467
|
||||||
|
[#2468]: https://github.com/actix/actix-web/pull/2468
|
||||||
|
[#2470]: https://github.com/actix/actix-web/pull/2470
|
||||||
|
[#2474]: https://github.com/actix/actix-web/pull/2474
|
||||||
|
[#2483]: https://github.com/actix/actix-web/pull/2483
|
||||||
|
[#2486]: https://github.com/actix/actix-web/pull/2486
|
||||||
|
[#2487]: https://github.com/actix/actix-web/pull/2487
|
||||||
|
[#2488]: https://github.com/actix/actix-web/pull/2488
|
||||||
|
[#2491]: https://github.com/actix/actix-web/pull/2491
|
||||||
|
[#2497]: https://github.com/actix/actix-web/pull/2497
|
||||||
|
[#2501]: https://github.com/actix/actix-web/pull/2501
|
||||||
|
[#2510]: https://github.com/actix/actix-web/pull/2510
|
||||||
|
[#2520]: https://github.com/actix/actix-web/pull/2520
|
||||||
|
[#2522]: https://github.com/actix/actix-web/pull/2522
|
||||||
|
[#2527]: https://github.com/actix/actix-web/pull/2527
|
||||||
|
[#2538]: https://github.com/actix/actix-web/pull/2538
|
||||||
|
[#2545]: https://github.com/actix/actix-web/pull/2545
|
||||||
|
[#2565]: https://github.com/actix/actix-web/pull/2565
|
||||||
|
[#2585]: https://github.com/actix/actix-web/pull/2585
|
||||||
|
[#2587]: https://github.com/actix/actix-web/pull/2587
|
||||||
|
[#2611]: https://github.com/actix/actix-web/pull/2611
|
||||||
|
[#2618]: https://github.com/actix/actix-web/pull/2618
|
||||||
|
[#2624]: https://github.com/actix/actix-web/pull/2624
|
||||||
|
[#2625]: https://github.com/actix/actix-web/pull/2625
|
||||||
|
[#2660]: https://github.com/actix/actix-web/pull/2660
|
||||||
|
[00ba8d55]: https://github.com/actix/actix-web/commit/00ba8d55492284581695d824648590715a8bd386
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>3.0.0 Pre-Releases</summary>
|
||||||
|
|
||||||
|
## 3.0.0-rc.4 - 2022-02-22
|
||||||
|
### Fixed
|
||||||
|
- Fix h1 dispatcher panic. [1ce58ecb]
|
||||||
|
|
||||||
|
[1ce58ecb]: https://github.com/actix/actix-web/commit/1ce58ecb305c60e51db06e6c913b7a1344e229ca
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-rc.3 - 2022-02-16
|
## 3.0.0-rc.3 - 2022-02-16
|
||||||
- No significant changes since `3.0.0-rc.2`.
|
- No significant changes since `3.0.0-rc.2`.
|
||||||
|
|
||||||
@ -98,7 +408,7 @@
|
|||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.17 - 2021-12-27
|
## 3.0.0-beta.17 - 2021-12-27
|
||||||
### Changes
|
### Changed
|
||||||
- `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527]
|
- `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527]
|
||||||
- `Payload` inner fields are now named. [#2545]
|
- `Payload` inner fields are now named. [#2545]
|
||||||
- `impl Stream` for `Payload` no longer requires the `Stream` variant be `Unpin`. [#2545]
|
- `impl Stream` for `Payload` no longer requires the `Stream` variant be `Unpin`. [#2545]
|
||||||
@ -321,7 +631,7 @@
|
|||||||
- `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159]
|
- `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159]
|
||||||
- Helper `body::to_bytes` for async collecting message body into Bytes. [#2158]
|
- Helper `body::to_bytes` for async collecting message body into Bytes. [#2158]
|
||||||
|
|
||||||
### Changes
|
### Changed
|
||||||
- The type parameter of `Response` no longer has a default. [#2152]
|
- The type parameter of `Response` no longer has a default. [#2152]
|
||||||
- The `Message` variant of `body::Body` is now `Pin<Box<dyn MessageBody>>`. [#2152]
|
- The `Message` variant of `body::Body` is now `Pin<Box<dyn MessageBody>>`. [#2152]
|
||||||
- `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152]
|
- `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152]
|
||||||
@ -465,6 +775,8 @@
|
|||||||
[#1864]: https://github.com/actix/actix-web/pull/1864
|
[#1864]: https://github.com/actix/actix-web/pull/1864
|
||||||
[#1878]: https://github.com/actix/actix-web/pull/1878
|
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## 2.2.2 - 2022-01-21
|
## 2.2.2 - 2022-01-21
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "3.0.0-rc.3"
|
version = "3.0.3"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
@ -100,7 +100,7 @@ zstd = { version = "0.10", optional = true }
|
|||||||
actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] }
|
actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] }
|
||||||
actix-server = "2"
|
actix-server = "2"
|
||||||
actix-tls = { version = "3", features = ["openssl"] }
|
actix-tls = { version = "3", features = ["openssl"] }
|
||||||
actix-web = "4.0.0-rc.3"
|
actix-web = "4.0.0"
|
||||||
|
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
> HTTP primitives for the Actix ecosystem.
|
> HTTP primitives for the Actix ecosystem.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-http)
|
[](https://crates.io/crates/actix-http)
|
||||||
[](https://docs.rs/actix-http/3.0.0-rc.3)
|
[](https://docs.rs/actix-http/3.0.3)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-http/3.0.0-rc.3)
|
[](https://deps.rs/crate/actix-http/3.0.3)
|
||||||
[](https://crates.io/crates/actix-http)
|
[](https://crates.io/crates/actix-http)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@ -18,7 +18,8 @@ async fn main() -> std::io::Result<()> {
|
|||||||
HttpService::build()
|
HttpService::build()
|
||||||
// pass the app to service builder
|
// pass the app to service builder
|
||||||
// map_config is used to map App's configuration to ServiceBuilder
|
// map_config is used to map App's configuration to ServiceBuilder
|
||||||
.finish(map_config(app, |_| AppConfig::default()))
|
// h1 will configure server to only use HTTP/1.1
|
||||||
|
.h1(map_config(app, |_| AppConfig::default()))
|
||||||
.tcp()
|
.tcp()
|
||||||
})?
|
})?
|
||||||
.run()
|
.run()
|
||||||
|
@ -19,7 +19,7 @@ use zstd::stream::write::Decoder as ZstdDecoder;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
encoding::Writer,
|
encoding::Writer,
|
||||||
error::{BlockingError, PayloadError},
|
error::PayloadError,
|
||||||
header::{ContentEncoding, HeaderMap, CONTENT_ENCODING},
|
header::{ContentEncoding, HeaderMap, CONTENT_ENCODING},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,14 +47,17 @@ where
|
|||||||
ContentEncoding::Brotli => Some(ContentDecoder::Brotli(Box::new(
|
ContentEncoding::Brotli => Some(ContentDecoder::Brotli(Box::new(
|
||||||
brotli::DecompressorWriter::new(Writer::new(), 8_096),
|
brotli::DecompressorWriter::new(Writer::new(), 8_096),
|
||||||
))),
|
))),
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
|
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
|
||||||
ZlibDecoder::new(Writer::new()),
|
ZlibDecoder::new(Writer::new()),
|
||||||
))),
|
))),
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(GzDecoder::new(
|
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(GzDecoder::new(
|
||||||
Writer::new(),
|
Writer::new(),
|
||||||
)))),
|
)))),
|
||||||
|
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
ContentEncoding::Zstd => Some(ContentDecoder::Zstd(Box::new(
|
ContentEncoding::Zstd => Some(ContentDecoder::Zstd(Box::new(
|
||||||
ZstdDecoder::new(Writer::new()).expect(
|
ZstdDecoder::new(Writer::new()).expect(
|
||||||
@ -98,8 +101,12 @@ where
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
if let Some(ref mut fut) = this.fut {
|
if let Some(ref mut fut) = this.fut {
|
||||||
let (chunk, decoder) =
|
let (chunk, decoder) = ready!(Pin::new(fut).poll(cx)).map_err(|_| {
|
||||||
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
|
PayloadError::Io(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"Blocking task was cancelled unexpectedly",
|
||||||
|
))
|
||||||
|
})??;
|
||||||
|
|
||||||
*this.decoder = Some(decoder);
|
*this.decoder = Some(decoder);
|
||||||
this.fut.take();
|
this.fut.take();
|
||||||
@ -159,10 +166,13 @@ where
|
|||||||
enum ContentDecoder {
|
enum ContentDecoder {
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
Deflate(Box<ZlibDecoder<Writer>>),
|
Deflate(Box<ZlibDecoder<Writer>>),
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
Gzip(Box<GzDecoder<Writer>>),
|
Gzip(Box<GzDecoder<Writer>>),
|
||||||
|
|
||||||
#[cfg(feature = "compress-brotli")]
|
#[cfg(feature = "compress-brotli")]
|
||||||
Brotli(Box<brotli::DecompressorWriter<Writer>>),
|
Brotli(Box<brotli::DecompressorWriter<Writer>>),
|
||||||
|
|
||||||
// We need explicit 'static lifetime here because ZstdDecoder need lifetime
|
// We need explicit 'static lifetime here because ZstdDecoder need lifetime
|
||||||
// argument, and we use `spawn_blocking` in `Decoder::poll_next` that require `FnOnce() -> R + Send + 'static`
|
// argument, and we use `spawn_blocking` in `Decoder::poll_next` that require `FnOnce() -> R + Send + 'static`
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
|
@ -23,7 +23,6 @@ use zstd::stream::write::Encoder as ZstdEncoder;
|
|||||||
use super::Writer;
|
use super::Writer;
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{self, BodySize, MessageBody},
|
body::{self, BodySize, MessageBody},
|
||||||
error::BlockingError,
|
|
||||||
header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING},
|
header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING},
|
||||||
ResponseHead, StatusCode,
|
ResponseHead, StatusCode,
|
||||||
};
|
};
|
||||||
@ -173,7 +172,12 @@ where
|
|||||||
|
|
||||||
if let Some(ref mut fut) = this.fut {
|
if let Some(ref mut fut) = this.fut {
|
||||||
let mut encoder = ready!(Pin::new(fut).poll(cx))
|
let mut encoder = ready!(Pin::new(fut).poll(cx))
|
||||||
.map_err(|_| EncoderError::Blocking(BlockingError))?
|
.map_err(|_| {
|
||||||
|
EncoderError::Io(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"Blocking task was cancelled unexpectedly",
|
||||||
|
))
|
||||||
|
})?
|
||||||
.map_err(EncoderError::Io)?;
|
.map_err(EncoderError::Io)?;
|
||||||
|
|
||||||
let chunk = encoder.take();
|
let chunk = encoder.take();
|
||||||
@ -400,12 +404,11 @@ fn new_brotli_compressor() -> Box<brotli::CompressorWriter<Writer>> {
|
|||||||
#[derive(Debug, Display)]
|
#[derive(Debug, Display)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum EncoderError {
|
pub enum EncoderError {
|
||||||
|
/// Wrapped body stream error.
|
||||||
#[display(fmt = "body")]
|
#[display(fmt = "body")]
|
||||||
Body(Box<dyn StdError>),
|
Body(Box<dyn StdError>),
|
||||||
|
|
||||||
#[display(fmt = "blocking")]
|
/// Generic I/O error.
|
||||||
Blocking(BlockingError),
|
|
||||||
|
|
||||||
#[display(fmt = "io")]
|
#[display(fmt = "io")]
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
}
|
}
|
||||||
@ -414,7 +417,6 @@ impl StdError for EncoderError {
|
|||||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
match self {
|
match self {
|
||||||
EncoderError::Body(err) => Some(&**err),
|
EncoderError::Body(err) => Some(&**err),
|
||||||
EncoderError::Blocking(err) => Some(err),
|
|
||||||
EncoderError::Io(err) => Some(err),
|
EncoderError::Io(err) => Some(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ impl Error {
|
|||||||
Self::new(Kind::SendResponse)
|
Self::new(Kind::SendResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)] // reserved for future use (TODO: remove allow when being used)
|
#[allow(unused)] // available for future use
|
||||||
pub(crate) fn new_io() -> Self {
|
pub(crate) fn new_io() -> Self {
|
||||||
Self::new(Kind::Io)
|
Self::new(Kind::Io)
|
||||||
}
|
}
|
||||||
@ -108,8 +108,10 @@ pub(crate) enum Kind {
|
|||||||
|
|
||||||
impl fmt::Debug for Error {
|
impl fmt::Debug for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
// TODO: more detail
|
f.debug_struct("actix_http::Error")
|
||||||
f.write_str("actix_http::Error")
|
.field("kind", &self.inner.kind)
|
||||||
|
.field("cause", &self.inner.cause)
|
||||||
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,12 +252,6 @@ impl From<ParseError> for Response<BoxBody> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A set of errors that can occur running blocking tasks in thread pool.
|
|
||||||
#[derive(Debug, Display, Error)]
|
|
||||||
#[display(fmt = "Blocking thread pool is gone")]
|
|
||||||
// TODO: non-exhaustive
|
|
||||||
pub struct BlockingError;
|
|
||||||
|
|
||||||
/// A set of errors that can occur during payload parsing.
|
/// A set of errors that can occur during payload parsing.
|
||||||
#[derive(Debug, Display)]
|
#[derive(Debug, Display)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
@ -293,13 +289,13 @@ impl std::error::Error for PayloadError {
|
|||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
match self {
|
match self {
|
||||||
PayloadError::Incomplete(None) => None,
|
PayloadError::Incomplete(None) => None,
|
||||||
PayloadError::Incomplete(Some(err)) => Some(err as &dyn std::error::Error),
|
PayloadError::Incomplete(Some(err)) => Some(err),
|
||||||
PayloadError::EncodingCorrupted => None,
|
PayloadError::EncodingCorrupted => None,
|
||||||
PayloadError::Overflow => None,
|
PayloadError::Overflow => None,
|
||||||
PayloadError::UnknownLength => None,
|
PayloadError::UnknownLength => None,
|
||||||
#[cfg(feature = "http2")]
|
#[cfg(feature = "http2")]
|
||||||
PayloadError::Http2Payload(err) => Some(err as &dyn std::error::Error),
|
PayloadError::Http2Payload(err) => Some(err),
|
||||||
PayloadError::Io(err) => Some(err as &dyn std::error::Error),
|
PayloadError::Io(err) => Some(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -323,15 +319,6 @@ impl From<io::Error> for PayloadError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BlockingError> for PayloadError {
|
|
||||||
fn from(_: BlockingError) -> Self {
|
|
||||||
PayloadError::Io(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"Operation is canceled",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<PayloadError> for Error {
|
impl From<PayloadError> for Error {
|
||||||
fn from(err: PayloadError) -> Self {
|
fn from(err: PayloadError) -> Self {
|
||||||
Self::new_payload().with_cause(err)
|
Self::new_payload().with_cause(err)
|
||||||
@ -386,7 +373,6 @@ pub enum DispatchError {
|
|||||||
impl StdError for DispatchError {
|
impl StdError for DispatchError {
|
||||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
match self {
|
match self {
|
||||||
// TODO: error source extraction?
|
|
||||||
DispatchError::Service(_res) => None,
|
DispatchError::Service(_res) => None,
|
||||||
DispatchError::Body(err) => Some(&**err),
|
DispatchError::Body(err) => Some(&**err),
|
||||||
DispatchError::Io(err) => Some(err),
|
DispatchError::Io(err) => Some(err),
|
||||||
|
@ -293,22 +293,35 @@ impl MessageType for ResponseHead {
|
|||||||
let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY;
|
let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY;
|
||||||
|
|
||||||
let (len, ver, status, h_len) = {
|
let (len, ver, status, h_len) = {
|
||||||
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = EMPTY_HEADER_ARRAY;
|
// SAFETY:
|
||||||
|
// Create an uninitialized array of `MaybeUninit`. The `assume_init` is safe because the
|
||||||
|
// type we are claiming to have initialized here is a bunch of `MaybeUninit`s, which
|
||||||
|
// do not require initialization.
|
||||||
|
let mut parsed = unsafe {
|
||||||
|
MaybeUninit::<[MaybeUninit<httparse::Header<'_>>; MAX_HEADERS]>::uninit()
|
||||||
|
.assume_init()
|
||||||
|
};
|
||||||
|
|
||||||
let mut res = httparse::Response::new(&mut parsed);
|
let mut res = httparse::Response::new(&mut []);
|
||||||
match res.parse(src)? {
|
|
||||||
|
let mut config = httparse::ParserConfig::default();
|
||||||
|
config.allow_spaces_after_header_name_in_responses(true);
|
||||||
|
|
||||||
|
match config.parse_response_with_uninit_headers(&mut res, src, &mut parsed)? {
|
||||||
httparse::Status::Complete(len) => {
|
httparse::Status::Complete(len) => {
|
||||||
let version = if res.version.unwrap() == 1 {
|
let version = if res.version.unwrap() == 1 {
|
||||||
Version::HTTP_11
|
Version::HTTP_11
|
||||||
} else {
|
} else {
|
||||||
Version::HTTP_10
|
Version::HTTP_10
|
||||||
};
|
};
|
||||||
|
|
||||||
let status = StatusCode::from_u16(res.code.unwrap())
|
let status = StatusCode::from_u16(res.code.unwrap())
|
||||||
.map_err(|_| ParseError::Status)?;
|
.map_err(|_| ParseError::Status)?;
|
||||||
HeaderIndex::record(src, res.headers, &mut headers);
|
HeaderIndex::record(src, res.headers, &mut headers);
|
||||||
|
|
||||||
(len, version, status, res.headers.len())
|
(len, version, status, res.headers.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
httparse::Status::Partial => {
|
httparse::Status::Partial => {
|
||||||
return if src.len() >= MAX_BUFFER_SIZE {
|
return if src.len() >= MAX_BUFFER_SIZE {
|
||||||
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
||||||
@ -360,9 +373,6 @@ pub(crate) const EMPTY_HEADER_INDEX: HeaderIndex = HeaderIndex {
|
|||||||
pub(crate) const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] =
|
pub(crate) const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] =
|
||||||
[EMPTY_HEADER_INDEX; MAX_HEADERS];
|
[EMPTY_HEADER_INDEX; MAX_HEADERS];
|
||||||
|
|
||||||
pub(crate) const EMPTY_HEADER_ARRAY: [httparse::Header<'static>; MAX_HEADERS] =
|
|
||||||
[httparse::EMPTY_HEADER; MAX_HEADERS];
|
|
||||||
|
|
||||||
impl HeaderIndex {
|
impl HeaderIndex {
|
||||||
pub(crate) fn record(
|
pub(crate) fn record(
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
|
@ -375,8 +375,6 @@ where
|
|||||||
DispatchError::Io(err)
|
DispatchError::Io(err)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
this.flags.set(Flags::KEEP_ALIVE, this.codec.keep_alive());
|
|
||||||
|
|
||||||
Ok(size)
|
Ok(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,7 +457,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// all messages are dealt with
|
// all messages are dealt with
|
||||||
None => return Ok(PollResponse::DoNothing),
|
None => {
|
||||||
|
// start keep-alive if last request allowed it
|
||||||
|
this.flags.set(Flags::KEEP_ALIVE, this.codec.keep_alive());
|
||||||
|
|
||||||
|
return Ok(PollResponse::DoNothing);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
StateProj::ServiceCall { fut } => {
|
StateProj::ServiceCall { fut } => {
|
||||||
@ -757,6 +760,7 @@ where
|
|||||||
|
|
||||||
let mut updated = false;
|
let mut updated = false;
|
||||||
|
|
||||||
|
// decode from read buf as many full requests as possible
|
||||||
loop {
|
loop {
|
||||||
match this.codec.decode(this.read_buf) {
|
match this.codec.decode(this.read_buf) {
|
||||||
Ok(Some(msg)) => {
|
Ok(Some(msg)) => {
|
||||||
@ -931,10 +935,16 @@ where
|
|||||||
"dispatcher should not be in keep-alive phase if state is not none: {:?}",
|
"dispatcher should not be in keep-alive phase if state is not none: {:?}",
|
||||||
this.state,
|
this.state,
|
||||||
);
|
);
|
||||||
debug_assert!(
|
|
||||||
this.write_buf.is_empty(),
|
// Assert removed by @robjtede on account of issue #2655. There are cases where an I/O
|
||||||
"dispatcher should not be in keep-alive phase if write_buf is not empty",
|
// flush can be pending after entering the keep-alive state causing the subsequent flush
|
||||||
);
|
// wake up to panic here. This appears to be a Linux-only problem. Leaving original code
|
||||||
|
// below for posterity because a simple and reliable test could not be found to trigger
|
||||||
|
// the behavior.
|
||||||
|
// debug_assert!(
|
||||||
|
// this.write_buf.is_empty(),
|
||||||
|
// "dispatcher should not be in keep-alive phase if write_buf is not empty",
|
||||||
|
// );
|
||||||
|
|
||||||
// keep-alive timer has timed out
|
// keep-alive timer has timed out
|
||||||
if timer.as_mut().poll(cx).is_ready() {
|
if timer.as_mut().poll(cx).is_ready() {
|
||||||
|
@ -517,6 +517,7 @@ unsafe fn write_camel_case(value: &[u8], buf: *mut u8, len: usize) {
|
|||||||
if let Some(c @ b'a'..=b'z') = iter.next() {
|
if let Some(c @ b'a'..=b'z') = iter.next() {
|
||||||
buffer[index] = c & 0b1101_1111;
|
buffer[index] = c & 0b1101_1111;
|
||||||
}
|
}
|
||||||
|
index += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
index += 1;
|
index += 1;
|
||||||
@ -528,7 +529,7 @@ mod tests {
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http::header::AUTHORIZATION;
|
use http::header::{AUTHORIZATION, UPGRADE_INSECURE_REQUESTS};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -559,6 +560,9 @@ mod tests {
|
|||||||
head.headers
|
head.headers
|
||||||
.insert(CONTENT_TYPE, HeaderValue::from_static("plain/text"));
|
.insert(CONTENT_TYPE, HeaderValue::from_static("plain/text"));
|
||||||
|
|
||||||
|
head.headers
|
||||||
|
.insert(UPGRADE_INSECURE_REQUESTS, HeaderValue::from_static("1"));
|
||||||
|
|
||||||
let mut head = RequestHeadType::Owned(head);
|
let mut head = RequestHeadType::Owned(head);
|
||||||
|
|
||||||
let _ = head.encode_headers(
|
let _ = head.encode_headers(
|
||||||
@ -574,6 +578,7 @@ mod tests {
|
|||||||
assert!(data.contains("Connection: close\r\n"));
|
assert!(data.contains("Connection: close\r\n"));
|
||||||
assert!(data.contains("Content-Type: plain/text\r\n"));
|
assert!(data.contains("Content-Type: plain/text\r\n"));
|
||||||
assert!(data.contains("Date: date\r\n"));
|
assert!(data.contains("Date: date\r\n"));
|
||||||
|
assert!(data.contains("Upgrade-Insecure-Requests: 1\r\n"));
|
||||||
|
|
||||||
let _ = head.encode_headers(
|
let _ = head.encode_headers(
|
||||||
&mut bytes,
|
&mut bytes,
|
||||||
|
@ -25,7 +25,9 @@ use pin_project_lite::pin_project;
|
|||||||
use crate::{
|
use crate::{
|
||||||
body::{BodySize, BoxBody, MessageBody},
|
body::{BodySize, BoxBody, MessageBody},
|
||||||
config::ServiceConfig,
|
config::ServiceConfig,
|
||||||
header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING},
|
header::{
|
||||||
|
HeaderName, HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE,
|
||||||
|
},
|
||||||
service::HttpFlow,
|
service::HttpFlow,
|
||||||
Extensions, OnConnectData, Payload, Request, Response, ResponseHead,
|
Extensions, OnConnectData, Payload, Request, Response, ResponseHead,
|
||||||
};
|
};
|
||||||
@ -306,13 +308,22 @@ fn prepare_response(
|
|||||||
|
|
||||||
// copy headers
|
// copy headers
|
||||||
for (key, value) in head.headers.iter() {
|
for (key, value) in head.headers.iter() {
|
||||||
match *key {
|
match key {
|
||||||
// TODO: consider skipping other headers according to:
|
// omit HTTP/1.x only headers according to:
|
||||||
// https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2
|
// https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2
|
||||||
// omit HTTP/1.x only headers
|
&CONNECTION | &TRANSFER_ENCODING | &UPGRADE => continue,
|
||||||
CONNECTION | TRANSFER_ENCODING => continue,
|
|
||||||
CONTENT_LENGTH if skip_len => continue,
|
&CONTENT_LENGTH if skip_len => continue,
|
||||||
DATE => has_date = true,
|
&DATE => has_date = true,
|
||||||
|
|
||||||
|
// omit HTTP/1.x only headers according to:
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2
|
||||||
|
hdr if hdr == HeaderName::from_static("keep-alive")
|
||||||
|
|| hdr == HeaderName::from_static("proxy-connection") =>
|
||||||
|
{
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ impl ResponseBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set connection type to Upgrade
|
/// Set connection type to `Upgrade`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn upgrade<V>(&mut self, value: V) -> &mut Self
|
pub fn upgrade<V>(&mut self, value: V) -> &mut Self
|
||||||
where
|
where
|
||||||
@ -161,7 +161,7 @@ impl ResponseBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force close connection, even if it is marked as keep-alive
|
/// Force-close connection, even if it is marked as keep-alive.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn force_close(&mut self) -> &mut Self {
|
pub fn force_close(&mut self) -> &mut Self {
|
||||||
if let Some(parts) = self.inner() {
|
if let Some(parts) = self.inner() {
|
||||||
|
@ -47,40 +47,6 @@ pub fn apply_mask_fast32(buf: &mut [u8], mask: [u8; 4]) {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
// legacy test from old apply mask test. kept for now for back compat test.
|
|
||||||
// TODO: remove it and favor the other test.
|
|
||||||
#[test]
|
|
||||||
fn test_apply_mask_legacy() {
|
|
||||||
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
|
||||||
|
|
||||||
let unmasked = vec![
|
|
||||||
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9,
|
|
||||||
0x12, 0x03,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Check masking with proper alignment.
|
|
||||||
{
|
|
||||||
let mut masked = unmasked.clone();
|
|
||||||
apply_mask_fallback(&mut masked, mask);
|
|
||||||
|
|
||||||
let mut masked_fast = unmasked.clone();
|
|
||||||
apply_mask(&mut masked_fast, mask);
|
|
||||||
|
|
||||||
assert_eq!(masked, masked_fast);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check masking without alignment.
|
|
||||||
{
|
|
||||||
let mut masked = unmasked.clone();
|
|
||||||
apply_mask_fallback(&mut masked[1..], mask);
|
|
||||||
|
|
||||||
let mut masked_fast = unmasked;
|
|
||||||
apply_mask(&mut masked_fast[1..], mask);
|
|
||||||
|
|
||||||
assert_eq!(masked, masked_fast);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_mask() {
|
fn test_apply_mask() {
|
||||||
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
||||||
|
@ -850,7 +850,8 @@ async fn not_modified_spec_h1() {
|
|||||||
Some(&header::HeaderValue::from_static("4")),
|
Some(&header::HeaderValue::from_static("4")),
|
||||||
);
|
);
|
||||||
// server does not prevent payload from being sent but clients may choose not to read it
|
// server does not prevent payload from being sent but clients may choose not to read it
|
||||||
// TODO: this is probably a bug, especially since CL header can differ in length from the body
|
// TODO: this is probably a bug in the client, especially since CL header can differ in length
|
||||||
|
// from the body
|
||||||
assert!(!srv.load_body(res).await.unwrap().is_empty());
|
assert!(!srv.load_body(res).await.unwrap().is_empty());
|
||||||
|
|
||||||
// TODO: add stream response tests
|
// TODO: add stream response tests
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.0 - 2022-02-25
|
||||||
|
- No significant changes since `0.4.0-beta.13`.
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0-beta.13 - 2022-01-31
|
## 0.4.0-beta.13 - 2022-01-31
|
||||||
- No significant changes since `0.4.0-beta.12`.
|
- No significant changes since `0.4.0-beta.12`.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-multipart"
|
name = "actix-multipart"
|
||||||
version = "0.4.0-beta.13"
|
version = "0.4.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Multipart form support for Actix Web"
|
description = "Multipart form support for Actix Web"
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
@ -14,8 +14,8 @@ name = "actix_multipart"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3"
|
||||||
actix-web = { version = "4.0.0-rc.3", default-features = false }
|
actix-web = { version = "4.0.0", default-features = false }
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
@ -28,7 +28,7 @@ twoway = "0.2"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-http = "3.0.0-rc.3"
|
actix-http = "3.0.0"
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
tokio = { version = "1.8.4", features = ["sync"] }
|
tokio = { version = "1.8.4", features = ["sync"] }
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
> Multipart form support for Actix Web.
|
> Multipart form support for Actix Web.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-multipart)
|
[](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)
|
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||||

|

|
||||||
<br />
|
<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://crates.io/crates/actix-multipart)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@ -3,6 +3,77 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.0 - 2022-02-22
|
||||||
|
### Added
|
||||||
|
- Add `Path::as_str`. [#2590]
|
||||||
|
- Add `ResourceDef::set_name`. [#373][net#373]
|
||||||
|
- Add `RouterBuilder::push`. [#2612]
|
||||||
|
- Implement `IntoPatterns` for `bytestring::ByteString`. [#372][net#372]
|
||||||
|
- Introduce `ResourceDef::join`. [#380][net#380]
|
||||||
|
- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373][net#373]
|
||||||
|
- `Resource` is now implemented for `&mut Path<_>` and `RefMut<Path<_>>`. [#2568]
|
||||||
|
- Support `build_resource_path` on multi-pattern resources. [#2356]
|
||||||
|
- Support multi-pattern prefixes and joins. [#2356]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Change signature of `ResourceDef::capture_match_info_fn` to remove `user_data` parameter. [#2612]
|
||||||
|
- Deprecate `Path::path`. [#2590]
|
||||||
|
- Disallow prefix routes with tail segments. [#379][net#379]
|
||||||
|
- Enforce path separators on dynamic prefixes. [#378][net#378]
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||||
|
- Prefix segments now always end with with a segment delimiter or end-of-input. [#2355]
|
||||||
|
- Prefix segments with trailing slashes define a trailing empty segment. [#2355]
|
||||||
|
- `Quoter::requote` now returns `Option<Vec<u8>>`. [#2613]
|
||||||
|
- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372][net#372]
|
||||||
|
- Rename `Path::{len => segment_count}` to be more descriptive of its purpose. [#370][net#370]
|
||||||
|
- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373][net#373]
|
||||||
|
- Rename `ResourceDef::{match_path => capture_match_info}`. [#373][net#373]
|
||||||
|
- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373][net#373]
|
||||||
|
- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371][net#371]
|
||||||
|
- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371][net#371]
|
||||||
|
- Rename `Router::{*_checked => *_fn}`. [#373][net#373]
|
||||||
|
- Replace `Option<U>` with `U` in `Router` API. [#2612]
|
||||||
|
- `Resource` trait now uses an associated type, `Path`, instead of a generic parameter. [#2568]
|
||||||
|
- `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356]
|
||||||
|
- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373][net#373]
|
||||||
|
- Return type of `ResourceDef::name` is now `Option<&str>`. [#373][net#373]
|
||||||
|
- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373][net#373]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix `ResourceDef`'s `PartialEq` implementation. [#373][net#373]
|
||||||
|
- Fix segment interpolation leaving `Path` in unintended state after matching. [#368][net#368]
|
||||||
|
- Improve malformed path error message. [#384][net#384]
|
||||||
|
- `PathDeserializer` now decodes all percent encoded characters in dynamic segments. [#2566]
|
||||||
|
- Relax bounds on `Router::recognize*` and `ResourceDef::capture_match_info`. [#2612]
|
||||||
|
- Static patterns in multi-patterns are no longer interpreted as regex. [#366][net#366]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- `ResourceDef::name_mut`. [#373][net#373]
|
||||||
|
- Unused `ResourceInfo`. [#2612]
|
||||||
|
|
||||||
|
[#2355]: https://github.com/actix/actix-web/pull/2355
|
||||||
|
[#2356]: https://github.com/actix/actix-web/pull/2356
|
||||||
|
[#2566]: https://github.com/actix/actix-net/pull/2566
|
||||||
|
[#2568]: https://github.com/actix/actix-web/pull/2568
|
||||||
|
[#2590]: https://github.com/actix/actix-web/pull/2590
|
||||||
|
[#2612]: https://github.com/actix/actix-web/pull/2612
|
||||||
|
[#2613]: https://github.com/actix/actix-web/pull/2613
|
||||||
|
[net#366]: https://github.com/actix/actix-net/pull/366
|
||||||
|
[net#368]: https://github.com/actix/actix-net/pull/368
|
||||||
|
[net#368]: https://github.com/actix/actix-net/pull/368
|
||||||
|
[net#370]: https://github.com/actix/actix-net/pull/370
|
||||||
|
[net#371]: https://github.com/actix/actix-net/pull/371
|
||||||
|
[net#372]: https://github.com/actix/actix-net/pull/372
|
||||||
|
[net#373]: https://github.com/actix/actix-net/pull/373
|
||||||
|
[net#378]: https://github.com/actix/actix-net/pull/378
|
||||||
|
[net#379]: https://github.com/actix/actix-net/pull/379
|
||||||
|
[net#380]: https://github.com/actix/actix-net/pull/380
|
||||||
|
[net#384]: https://github.com/actix/actix-net/pull/384
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>0.5.0 Pre-Releases</summary>
|
||||||
|
|
||||||
## 0.5.0-rc.3 - 2022-01-31
|
## 0.5.0-rc.3 - 2022-01-31
|
||||||
- Remove unused `ResourceInfo`. [#2612]
|
- Remove unused `ResourceInfo`. [#2612]
|
||||||
- Add `RouterBuilder::push`. [#2612]
|
- Add `RouterBuilder::push`. [#2612]
|
||||||
@ -41,10 +112,10 @@
|
|||||||
|
|
||||||
|
|
||||||
## 0.5.0-beta.2 - 2021-09-09
|
## 0.5.0-beta.2 - 2021-09-09
|
||||||
- Introduce `ResourceDef::join`. [#380]
|
- Introduce `ResourceDef::join`. [#380][net#380]
|
||||||
- Disallow prefix routes with tail segments. [#379]
|
- Disallow prefix routes with tail segments. [#379][net#379]
|
||||||
- Enforce path separators on dynamic prefixes. [#378]
|
- Enforce path separators on dynamic prefixes. [#378][net#378]
|
||||||
- Improve malformed path error message. [#384]
|
- Improve malformed path error message. [#384][net#384]
|
||||||
- Prefix segments now always end with with a segment delimiter or end-of-input. [#2355]
|
- Prefix segments now always end with with a segment delimiter or end-of-input. [#2355]
|
||||||
- Prefix segments with trailing slashes define a trailing empty segment. [#2355]
|
- Prefix segments with trailing slashes define a trailing empty segment. [#2355]
|
||||||
- Support multi-pattern prefixes and joins. [#2356]
|
- Support multi-pattern prefixes and joins. [#2356]
|
||||||
@ -52,52 +123,54 @@
|
|||||||
- Support `build_resource_path` on multi-pattern resources. [#2356]
|
- Support `build_resource_path` on multi-pattern resources. [#2356]
|
||||||
- Minimum supported Rust version (MSRV) is now 1.51.
|
- Minimum supported Rust version (MSRV) is now 1.51.
|
||||||
|
|
||||||
[#378]: https://github.com/actix/actix-net/pull/378
|
[net#378]: https://github.com/actix/actix-net/pull/378
|
||||||
[#379]: https://github.com/actix/actix-net/pull/379
|
[net#379]: https://github.com/actix/actix-net/pull/379
|
||||||
[#380]: https://github.com/actix/actix-net/pull/380
|
[net#380]: https://github.com/actix/actix-net/pull/380
|
||||||
[#384]: https://github.com/actix/actix-net/pull/384
|
[net#384]: https://github.com/actix/actix-net/pull/384
|
||||||
[#2355]: https://github.com/actix/actix-web/pull/2355
|
[#2355]: https://github.com/actix/actix-web/pull/2355
|
||||||
[#2356]: https://github.com/actix/actix-web/pull/2356
|
[#2356]: https://github.com/actix/actix-web/pull/2356
|
||||||
|
|
||||||
|
|
||||||
## 0.5.0-beta.1 - 2021-07-20
|
## 0.5.0-beta.1 - 2021-07-20
|
||||||
- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366]
|
- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366][net#366]
|
||||||
- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373]
|
- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373][net#373]
|
||||||
- Fix segment interpolation leaving `Path` in unintended state after matching. [#368]
|
- Fix segment interpolation leaving `Path` in unintended state after matching. [#368][net#368]
|
||||||
- Fix `ResourceDef` `PartialEq` implementation. [#373]
|
- Fix `ResourceDef` `PartialEq` implementation. [#373][net#373]
|
||||||
- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372]
|
- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372][net#372]
|
||||||
- Implement `IntoPatterns` for `bytestring::ByteString`. [#372]
|
- Implement `IntoPatterns` for `bytestring::ByteString`. [#372][net#372]
|
||||||
- Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370]
|
- Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370][net#370]
|
||||||
- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371]
|
- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371][net#371]
|
||||||
- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373]
|
- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373][net#373]
|
||||||
- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371]
|
- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371][net#371]
|
||||||
- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373]
|
- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373][net#373]
|
||||||
- Rename `ResourceDef::{match_path => capture_match_info}`. [#373]
|
- Rename `ResourceDef::{match_path => capture_match_info}`. [#373][net#373]
|
||||||
- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373]
|
- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373][net#373]
|
||||||
- Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373]
|
- Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373][net#373]
|
||||||
- Rename `Router::{*_checked => *_fn}`. [#373]
|
- Rename `Router::{*_checked => *_fn}`. [#373][net#373]
|
||||||
- Return type of `ResourceDef::name` is now `Option<&str>`. [#373]
|
- Return type of `ResourceDef::name` is now `Option<&str>`. [#373][net#373]
|
||||||
- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373]
|
- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373][net#373]
|
||||||
|
|
||||||
[#368]: https://github.com/actix/actix-net/pull/368
|
[net#368]: https://github.com/actix/actix-net/pull/368
|
||||||
[#366]: https://github.com/actix/actix-net/pull/366
|
[net#366]: https://github.com/actix/actix-net/pull/366
|
||||||
[#368]: https://github.com/actix/actix-net/pull/368
|
[net#368]: https://github.com/actix/actix-net/pull/368
|
||||||
[#370]: https://github.com/actix/actix-net/pull/370
|
[net#370]: https://github.com/actix/actix-net/pull/370
|
||||||
[#371]: https://github.com/actix/actix-net/pull/371
|
[net#371]: https://github.com/actix/actix-net/pull/371
|
||||||
[#372]: https://github.com/actix/actix-net/pull/372
|
[net#372]: https://github.com/actix/actix-net/pull/372
|
||||||
[#373]: https://github.com/actix/actix-net/pull/373
|
[net#373]: https://github.com/actix/actix-net/pull/373
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0 - 2021-06-06
|
## 0.4.0 - 2021-06-06
|
||||||
- When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357]
|
- When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357][net#357]
|
||||||
- Path tail patterns now match new lines (`\n`) in request URL. [#360]
|
- Path tail patterns now match new lines (`\n`) in request URL. [#360][net#360]
|
||||||
- Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359]
|
- Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359][net#359]
|
||||||
- Methods `Path::{add, add_static}` now take `impl Into<Cow<'static, str>>`. [#345]
|
- Methods `Path::{add, add_static}` now take `impl Into<Cow<'static, str>>`. [#345][net#345]
|
||||||
|
|
||||||
[#345]: https://github.com/actix/actix-net/pull/345
|
[net#345]: https://github.com/actix/actix-net/pull/345
|
||||||
[#357]: https://github.com/actix/actix-net/pull/357
|
[net#357]: https://github.com/actix/actix-net/pull/357
|
||||||
[#359]: https://github.com/actix/actix-net/pull/359
|
[net#359]: https://github.com/actix/actix-net/pull/359
|
||||||
[#360]: https://github.com/actix/actix-net/pull/360
|
[net#360]: https://github.com/actix/actix-net/pull/360
|
||||||
|
|
||||||
|
|
||||||
## 0.3.0 - 2019-12-31
|
## 0.3.0 - 2019-12-31
|
||||||
@ -105,15 +178,15 @@
|
|||||||
|
|
||||||
|
|
||||||
## 0.2.7 - 2021-02-06
|
## 0.2.7 - 2021-02-06
|
||||||
- Add `Router::recognize_checked` [#247]
|
- Add `Router::recognize_checked` [#247][net#247]
|
||||||
|
|
||||||
[#247]: https://github.com/actix/actix-net/pull/247
|
[net#247]: https://github.com/actix/actix-net/pull/247
|
||||||
|
|
||||||
|
|
||||||
## 0.2.6 - 2021-01-09
|
## 0.2.6 - 2021-01-09
|
||||||
- Use `bytestring` version range compatible with Bytes v1.0. [#246]
|
- Use `bytestring` version range compatible with Bytes v1.0. [#246][net#246]
|
||||||
|
|
||||||
[#246]: https://github.com/actix/actix-net/pull/246
|
[net#246]: https://github.com/actix/actix-net/pull/246
|
||||||
|
|
||||||
|
|
||||||
## 0.2.5 - 2020-09-20
|
## 0.2.5 - 2020-09-20
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-router"
|
name = "actix-router"
|
||||||
version = "0.5.0-rc.3"
|
version = "0.5.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
||||||
|
@ -29,13 +29,13 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.5"
|
actix-codec = "0.5"
|
||||||
actix-http = "3.0.0-rc.3"
|
actix-http = "3"
|
||||||
actix-http-test = "3.0.0-beta.13"
|
actix-http-test = "3.0.0-beta.13"
|
||||||
actix-rt = "2.1"
|
actix-rt = "2.1"
|
||||||
actix-service = "2.0.0"
|
actix-service = "2"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3"
|
||||||
actix-web = { version = "4.0.0-rc.3", default-features = false, features = ["cookies"] }
|
actix-web = { version = "4", default-features = false, features = ["cookies"] }
|
||||||
awc = { version = "3.0.0-beta.21", default-features = false, features = ["cookies"] }
|
awc = { version = "3", default-features = false, features = ["cookies"] }
|
||||||
|
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
|
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = [] }
|
futures-util = { version = "0.3.7", default-features = false, features = [] }
|
||||||
|
@ -43,7 +43,7 @@ pub use actix_http_test::unused_addr;
|
|||||||
use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
|
use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
|
||||||
pub use actix_web::test::{
|
pub use actix_web::test::{
|
||||||
call_and_read_body, call_and_read_body_json, call_service, init_service, ok_service,
|
call_and_read_body, call_and_read_body_json, call_service, init_service, ok_service,
|
||||||
read_body, read_body_json, simple_service, TestRequest,
|
read_body, read_body_json, status_service, TestRequest,
|
||||||
};
|
};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
body::MessageBody,
|
body::MessageBody,
|
||||||
|
@ -3,6 +3,16 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 4.1.0 - 2022-03-02
|
||||||
|
- Add support for `actix` version `0.13`. [#2675]
|
||||||
|
|
||||||
|
[#2675]: https://github.com/actix/actix-web/pull/2675
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0 - 2022-02-25
|
||||||
|
- No significant changes since `4.0.0-beta.12`.
|
||||||
|
|
||||||
|
|
||||||
## 4.0.0-beta.12 - 2022-02-16
|
## 4.0.0-beta.12 - 2022-02-16
|
||||||
- No significant changes since `4.0.0-beta.11`.
|
- No significant changes since `4.0.0-beta.11`.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web-actors"
|
name = "actix-web-actors"
|
||||||
version = "4.0.0-beta.12"
|
version = "4.1.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix actors support for Actix Web"
|
description = "Actix actors support for Actix Web"
|
||||||
keywords = ["actix", "http", "web", "framework", "async"]
|
keywords = ["actix", "http", "web", "framework", "async"]
|
||||||
@ -14,21 +14,21 @@ name = "actix_web_actors"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = { version = "0.12.0", default-features = false }
|
actix = { version = ">=0.12, <0.14", default-features = false }
|
||||||
actix-codec = "0.5"
|
actix-codec = "0.5"
|
||||||
actix-http = "3.0.0-rc.3"
|
actix-http = "3"
|
||||||
actix-web = { version = "4.0.0-rc.3", default-features = false }
|
actix-web = { version = "4", default-features = false }
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytestring = "1"
|
bytestring = "1"
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false }
|
||||||
pin-project-lite = "0.2"
|
pin-project-lite = "0.2"
|
||||||
tokio = { version = "1.8.4", features = ["sync"] }
|
tokio = { version = "1.13.1", features = ["sync"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-test = "0.1.0-beta.13"
|
actix-test = "0.1.0-beta.13"
|
||||||
awc = { version = "3.0.0-beta.21", default-features = false }
|
awc = { version = "3", default-features = false }
|
||||||
|
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
> Actix actors support for Actix Web.
|
> Actix actors support for Actix Web.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web-actors)
|
[](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)
|
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||||

|

|
||||||
<br />
|
<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://crates.io/crates/actix-web-actors)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@ -3,6 +3,15 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0 - 2022-02-24
|
||||||
|
- Version aligned with `actix-web` and will remain in sync going forward.
|
||||||
|
- No significant changes since `0.5.0`.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.0 - 2022-02-24
|
||||||
|
- No significant changes since `0.5.0-rc.2`.
|
||||||
|
|
||||||
|
|
||||||
## 0.5.0-rc.2 - 2022-02-01
|
## 0.5.0-rc.2 - 2022-02-01
|
||||||
- No significant changes since `0.5.0-rc.1`.
|
- No significant changes since `0.5.0-rc.1`.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web-codegen"
|
name = "actix-web-codegen"
|
||||||
version = "0.5.0-rc.2"
|
version = "4.0.0"
|
||||||
description = "Routing and runtime macros for Actix Web"
|
description = "Routing and runtime macros for Actix Web"
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
repository = "https://github.com/actix/actix-web.git"
|
repository = "https://github.com/actix/actix-web.git"
|
||||||
@ -15,7 +15,7 @@ edition = "2018"
|
|||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-router = "0.5.0-rc.3"
|
actix-router = "0.5.0"
|
||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
syn = { version = "1", features = ["full", "parsing"] }
|
syn = { version = "1", features = ["full", "parsing"] }
|
||||||
@ -25,7 +25,7 @@ actix-macros = "0.2.3"
|
|||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-test = "0.1.0-beta.13"
|
actix-test = "0.1.0-beta.13"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-web = "4.0.0-rc.3"
|
actix-web = "4.0.0"
|
||||||
|
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
trybuild = "1"
|
trybuild = "1"
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
> Routing and runtime macros for Actix Web.
|
> Routing and runtime macros for Actix Web.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web-codegen)
|
[](https://crates.io/crates/actix-web-codegen)
|
||||||
[](https://docs.rs/actix-web-codegen/0.5.0-rc.2)
|
[](https://docs.rs/actix-web-codegen/4.0.0)
|
||||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-web-codegen/0.5.0-rc.2)
|
[](https://deps.rs/crate/actix-web-codegen/4.0.0)
|
||||||
[](https://crates.io/crates/actix-web-codegen)
|
[](https://crates.io/crates/actix-web-codegen)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@ -1,8 +1,285 @@
|
|||||||
# Changes
|
# Changelog
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.1 - 2022-02-25
|
||||||
|
### Fixed
|
||||||
|
- Use stable version in readme example.
|
||||||
|
|
||||||
|
|
||||||
|
## 4.0.0 - 2022-02-25
|
||||||
|
### Dependencies
|
||||||
|
- Updated `actix-*` to Tokio v1-based versions. [#1813]
|
||||||
|
- Updated `actix-web-codegen` to `4.0.0`.
|
||||||
|
- Updated `cookie` to `0.16`. [#2555]
|
||||||
|
- Updated `language-tags` to `0.3`.
|
||||||
|
- Updated `rand` to `0.8`.
|
||||||
|
- Updated `rustls` to `0.20`. [#2414]
|
||||||
|
- Updated `tokio` to `1`.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Crate Features:
|
||||||
|
- `cookies`; enabled by default. [#2619]
|
||||||
|
- `compress-brotli`; enabled by default. [#2618]
|
||||||
|
- `compress-gzip`; enabled by default. [#2618]
|
||||||
|
- `compress-zstd`; enabled by default. [#2618]
|
||||||
|
- `macros`; enables routing and runtime macros, enabled by default. [#2619]
|
||||||
|
- Types:
|
||||||
|
- `CustomizeResponder` for customizing response. [#2510]
|
||||||
|
- `dev::ServerHandle` re-export from `actix-server`. [#2442]
|
||||||
|
- `dev::ServiceFactory` re-export from `actix-service`. [#2325]
|
||||||
|
- `guard::GuardContext` for use with the `Guard` trait. [#2552]
|
||||||
|
- `http::header::AcceptEncoding` typed header. [#2482]
|
||||||
|
- `http::header::Range` typed header. [#2485]
|
||||||
|
- `http::KeepAlive` re-export from `actix-http`. [#2625]
|
||||||
|
- `middleware::Compat` that boxes middleware types like `Logger` and `Compress` to be used with constrained type bounds. [#1865]
|
||||||
|
- `web::Header` extractor for extracting typed HTTP headers in handlers. [#2094]
|
||||||
|
- Methods:
|
||||||
|
- `dev::ServiceRequest::guard_ctx()` for obtaining a guard context. [#2552]
|
||||||
|
- `dev::ServiceRequest::parts_mut()`. [#2177]
|
||||||
|
- `dev::ServiceResponse::map_into_{left,right}_body()` and `HttpResponse::map_into_boxed_body()`. [#2468]
|
||||||
|
- `Either<web::Json<T>, web::Form<T>>::into_inner()` which returns the inner type for whichever variant was created. Also works for `Either<web::Form<T>, web::Json<T>>`. [#1894]
|
||||||
|
- `http::header::AcceptLanguage::{ranked, preference}()`. [#2480]
|
||||||
|
- `HttpResponse::add_removal_cookie()`. [#2586]
|
||||||
|
- `HttpResponse::map_into_{left,right}_body()` and `HttpResponse::map_into_boxed_body()`. [#2468]
|
||||||
|
- `HttpServer::worker_max_blocking_threads` for setting block thread pool. [#2200]
|
||||||
|
- `middleware::Logger::log_target()` to allow customize. [#2594]
|
||||||
|
- `Responder::customize()` trait method that wraps responder in `CustomizeResponder`. [#2510]
|
||||||
|
- `Route::service()` for using hand-written services as handlers. [#2262]
|
||||||
|
- `ServiceResponse::into_parts()`. [#2499]
|
||||||
|
- `TestServer::client_headers()` method. [#2097]
|
||||||
|
- `web::ServiceConfig::configure()` to allow easy nesting of configuration functions. [#1988]
|
||||||
|
- Trait Implementations:
|
||||||
|
- Implement `Debug` for `DefaultHeaders`. [#2510]
|
||||||
|
- Implement `FromRequest` for `ConnectionInfo` and `PeerAddr`. [#2263]
|
||||||
|
- Implement `FromRequest` for `Method`. [#2263]
|
||||||
|
- Implement `FromRequest` for `Uri`. [#2263]
|
||||||
|
- Implement `Hash` for `http::header::Encoding`. [#2501]
|
||||||
|
- Implement `Responder` for `Vec<u8>`. [#2625]
|
||||||
|
- Misc:
|
||||||
|
- `#[actix_web::test]` macro for setting up tests with a runtime. [#2409]
|
||||||
|
- Enable registering a vec of services of the same type to `App` [#1933]
|
||||||
|
- Add `services!` macro for helping register multiple services to `App`. [#1933]
|
||||||
|
- Option to allow `Json` extractor to work without a `Content-Type` header present. [#2362]
|
||||||
|
- Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Functions:
|
||||||
|
- `guard::fn_guard` functions now receives a `&GuardContext`. [#2552]
|
||||||
|
- `guard::Not` is now generic over the type of guard it wraps. [#2552]
|
||||||
|
- `test::{call_service, read_response, read_response_json, send_request}()` now receive a `&Service`. [#1905]
|
||||||
|
- Some guard functions now return `impl Guard` and their concrete types are made private: `guard::Header` and all the method guards. [#2552]
|
||||||
|
- Rename `test::{default_service => status_service}()`. Old name is deprecated. [#2518]
|
||||||
|
- Rename `test::{read_response_json => call_and_read_body_json}()`. Old name is deprecated. [#2518]
|
||||||
|
- Rename `test::{read_response => call_and_read_body}()`. Old name is deprecated. [#2518]
|
||||||
|
- Traits:
|
||||||
|
- `guard::Guard::check` now receives a `&GuardContext`. [#2552]
|
||||||
|
- `FromRequest::Config` associated type was removed. [#2233]
|
||||||
|
- `Responder` trait has been reworked and now `Response`/`HttpResponse` synchronously, making it simpler and more performant. [#1891]
|
||||||
|
- Rename `Factory` trait to `Handler`. [#1852]
|
||||||
|
- Types:
|
||||||
|
- `App`'s `B` (body) type parameter been removed. As a result, `App`s can be returned from functions now. [#2493]
|
||||||
|
- `Compress` middleware's response type is now `EitherBody<Encoder<B>>`. [#2448]
|
||||||
|
- `error::BlockingError` is now a unit struct. It's now only triggered when blocking thread pool has shutdown. [#1957]
|
||||||
|
- `ErrorHandlerResponse`'s response variants now use `ServiceResponse<EitherBody<B>>`. [#2515]
|
||||||
|
- `ErrorHandlers` middleware's response types now use `ServiceResponse<EitherBody<B>>`. [#2515]
|
||||||
|
- `http::header::Encoding` now only represents `Content-Encoding` types. [#2501]
|
||||||
|
- `middleware::Condition` gained a broader middleware compatibility. [#2635]
|
||||||
|
- `Resource` no longer require service body type to be boxed. [#2526]
|
||||||
|
- `Scope` no longer require service body type to be boxed. [#2523]
|
||||||
|
- `web::Path`s inner field is now private. [#1894]
|
||||||
|
- `web::Payload`'s inner field is now private. [#2384]
|
||||||
|
- Error enums are now marked `#[non_exhaustive]`. [#2148]
|
||||||
|
- Enum Variants:
|
||||||
|
- `Either` now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894]
|
||||||
|
- Include size and limits in `JsonPayloadError::Overflow`. [#2162]
|
||||||
|
- Methods:
|
||||||
|
- `App::data()` is deprecated; `App::app_data()` should be preferred. [#2271]
|
||||||
|
- `dev::JsonBody::new()` returns a default limit of 32kB to be consistent with `JsonConfig` and the default behaviour of the `web::Json<T>` extractor. [#2010]
|
||||||
|
- `dev::ServiceRequest::{into_parts, from_parts}()` can no longer fail. [#1893]
|
||||||
|
- `dev::ServiceRequest::from_request` can no longer fail. [#1893]
|
||||||
|
- `dev::ServiceResponse::error_response()` now uses body type of `BoxBody`. [#2201]
|
||||||
|
- `dev::ServiceResponse::map_body()` closure receives and returns `B` instead of `ResponseBody<B>`. [#2201]
|
||||||
|
- `http::header::ContentType::html()` now produces `text/html; charset=utf-8` instead of `text/html`. [#2423]
|
||||||
|
- `HttpRequest::url_for`'s constructed URLs no longer contain query or fragment. [#2430]
|
||||||
|
- `HttpResponseBuilder::json()` can now receive data by value and reference. [#1903]
|
||||||
|
- `HttpServer::{listen_rustls, bind_rustls}()` now honor the ALPN protocols in the configuration parameter. [#2226]
|
||||||
|
- `middleware::NormalizePath()` now will not try to normalize URIs with no valid path [#2246]
|
||||||
|
- `test::TestRequest::param()` now accepts more than just static strings. [#2172]
|
||||||
|
- `web::Data::into_inner()` and `Data::get_ref()` no longer require `T: Sized`. [#2403]
|
||||||
|
- Rename `HttpServer::{client_timeout => client_request_timeout}()`. [#2611]
|
||||||
|
- Rename `HttpServer::{client_shutdown => client_disconnect_timeout}()`. [#2611]
|
||||||
|
- Rename `http::header::Accept::{mime_precedence => ranked}()`. [#2480]
|
||||||
|
- Rename `http::header::Accept::{mime_preference => preference}()`. [#2480]
|
||||||
|
- Rename `middleware::DefaultHeaders::{content_type => add_content_type}()`. [#1875]
|
||||||
|
- Rename `dev::ConnectionInfo::{remote_addr => peer_addr}`, deprecating the old name. [#2554]
|
||||||
|
- Trait Implementations:
|
||||||
|
- `HttpResponse` can now be used as a `Responder` with any body type. [#2567]
|
||||||
|
- Misc:
|
||||||
|
- Maximum number of handler extractors has increased to 12. [#2582]
|
||||||
|
- The default `TrailingSlash` behavior is now `Trim`, in line with existing documentation. See migration guide for implications. [#1875]
|
||||||
|
- `Result` extractor wrapper can now convert error types. [#2581]
|
||||||
|
- Compress middleware will return `406 Not Acceptable` when no content encoding is acceptable to the client. [#2344]
|
||||||
|
- Adjusted default JSON payload limit to 2MB (from 32kb). [#2162]
|
||||||
|
- All error trait bounds in server service builders have changed from `Into<Error>` to `Into<Response<BoxBody>>`. [#2253]
|
||||||
|
- All error trait bounds in message body and stream impls changed from `Into<Error>` to `Into<Box<dyn std::error::Error>>`. [#2253]
|
||||||
|
- Improve spec compliance of `dev::ConnectionInfo` extractor. [#2282]
|
||||||
|
- Associated types in `FromRequest` implementation for `Option` and `Result` have changed. [#2581]
|
||||||
|
- Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2201]
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Auto-negotiation of content encoding is more fault-tolerant when using the `Compress` middleware. [#2501]
|
||||||
|
- Scope and Resource middleware can access data items set on their own layer. [#2288]
|
||||||
|
- Multiple calls to `App::data()` with the same type now keeps the latest call's data. [#1906]
|
||||||
|
- Typed headers containing lists that require one or more items now enforce this minimum. [#2482]
|
||||||
|
- `dev::ConnectionInfo::peer_addr` will no longer return the port number. [#2554]
|
||||||
|
- `dev::ConnectionInfo::realip_remote_addr` will no longer return the port number if sourcing the IP from the peer's socket address. [#2554]
|
||||||
|
- Accept wildcard `*` items in `AcceptLanguage`. [#2480]
|
||||||
|
- Relax `Unpin` bound on `S` (stream) parameter of `HttpResponseBuilder::streaming`. [#2448]
|
||||||
|
- Fix quality parse error in `http::header::AcceptEncoding` typed header. [#2344]
|
||||||
|
- Double ampersand in `middleware::Logger` format is escaped correctly. [#2067]
|
||||||
|
- Added the underlying parse error to `test::read_body_json`'s panic message. [#1812]
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- `cookie` upgrade addresses [`RUSTSEC-2020-0071`].
|
||||||
|
|
||||||
|
[`rustsec-2020-0071`]: https://rustsec.org/advisories/RUSTSEC-2020-0071.html
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Crate Features:
|
||||||
|
- `compress` feature. [#2065]
|
||||||
|
- Functions:
|
||||||
|
- `test::load_stream` and `test::load_body`; replace usage with `body::to_bytes`. [#2518]
|
||||||
|
- `test::start_with`; moved to new `actix-test` crate. [#2112]
|
||||||
|
- `test::start`; moved to new `actix-test` crate. [#2112]
|
||||||
|
- `test::unused_addr`; moved to new `actix-test` crate. [#2112]
|
||||||
|
- Traits:
|
||||||
|
- `BodyEncoding`; signalling content encoding is now only done via the `Content-Encoding` header. [#2565]
|
||||||
|
- Types:
|
||||||
|
- `dev::{BodySize, MessageBody, SizedStream}` re-exports; they are exposed through the `body` module. [#2468]
|
||||||
|
- `EitherExtractError` direct export. [#2510]
|
||||||
|
- `rt::{Arbiter, ArbiterHandle}` re-exports. [#2619]
|
||||||
|
- `test::TestServer`; moved to new `actix-test` crate. [#2112]
|
||||||
|
- `test::TestServerConfig`; moved to new `actix-test` crate. [#2112]
|
||||||
|
- `web::HttpRequest` re-export. [#2663]
|
||||||
|
- `web::HttpResponse` re-export. [#2663]
|
||||||
|
- Methods:
|
||||||
|
- `AppService::set_service_data`; for custom HTTP service factories adding application data, use the layered data model by calling `ServiceRequest::add_data_container` when handling requests instead. [#1906]
|
||||||
|
- `dev::ConnectionInfo::get`. [#2487]
|
||||||
|
- `dev::ServiceResponse::checked_expr`. [#2401]
|
||||||
|
- `HttpRequestBuilder::del_cookie`. [#2591]
|
||||||
|
- `HttpResponse::take_body` and old `HttpResponse::into_body` method that casted body type. [#2201]
|
||||||
|
- `HttpResponseBuilder::json2()`. [#1903]
|
||||||
|
- `middleware::Compress::new`; restricting compression algorithm is done through feature flags. [#2501]
|
||||||
|
- `test::TestRequest::with_header()`; use `test::TestRequest::default().insert_header()`. [#1869]
|
||||||
|
- Trait Implementations:
|
||||||
|
- Implementation of `From<either::Either>` for `Either` crate. [#2516]
|
||||||
|
- Implementation of `Future` for `HttpResponse`. [#2601]
|
||||||
|
- Misc:
|
||||||
|
- The `client` module was removed; use the `awc` crate directly. [871ca5e4]
|
||||||
|
- `middleware::{normalize, err_handlers}` modules; all necessary middleware types are now exposed in the `middleware` module.
|
||||||
|
|
||||||
|
[#1812]: https://github.com/actix/actix-web/pull/1812
|
||||||
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
|
[#1852]: https://github.com/actix/actix-web/pull/1852
|
||||||
|
[#1865]: https://github.com/actix/actix-web/pull/1865
|
||||||
|
[#1869]: https://github.com/actix/actix-web/pull/1869
|
||||||
|
[#1875]: https://github.com/actix/actix-web/pull/1875
|
||||||
|
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||||
|
[#1891]: https://github.com/actix/actix-web/pull/1891
|
||||||
|
[#1893]: https://github.com/actix/actix-web/pull/1893
|
||||||
|
[#1894]: https://github.com/actix/actix-web/pull/1894
|
||||||
|
[#1903]: https://github.com/actix/actix-web/pull/1903
|
||||||
|
[#1905]: https://github.com/actix/actix-web/pull/1905
|
||||||
|
[#1906]: https://github.com/actix/actix-web/pull/1906
|
||||||
|
[#1933]: https://github.com/actix/actix-web/pull/1933
|
||||||
|
[#1957]: https://github.com/actix/actix-web/pull/1957
|
||||||
|
[#1957]: https://github.com/actix/actix-web/pull/1957
|
||||||
|
[#1981]: https://github.com/actix/actix-web/pull/1981
|
||||||
|
[#1988]: https://github.com/actix/actix-web/pull/1988
|
||||||
|
[#2010]: https://github.com/actix/actix-web/pull/2010
|
||||||
|
[#2065]: https://github.com/actix/actix-web/pull/2065
|
||||||
|
[#2067]: https://github.com/actix/actix-web/pull/2067
|
||||||
|
[#2093]: https://github.com/actix/actix-web/pull/2093
|
||||||
|
[#2094]: https://github.com/actix/actix-web/pull/2094
|
||||||
|
[#2097]: https://github.com/actix/actix-web/pull/2097
|
||||||
|
[#2112]: https://github.com/actix/actix-web/pull/2112
|
||||||
|
[#2148]: https://github.com/actix/actix-web/pull/2148
|
||||||
|
[#2162]: https://github.com/actix/actix-web/pull/2162
|
||||||
|
[#2172]: https://github.com/actix/actix-web/pull/2172
|
||||||
|
[#2177]: https://github.com/actix/actix-web/pull/2177
|
||||||
|
[#2200]: https://github.com/actix/actix-web/pull/2200
|
||||||
|
[#2201]: https://github.com/actix/actix-web/pull/2201
|
||||||
|
[#2201]: https://github.com/actix/actix-web/pull/2201
|
||||||
|
[#2233]: https://github.com/actix/actix-web/pull/2233
|
||||||
|
[#2246]: https://github.com/actix/actix-web/pull/2246
|
||||||
|
[#2250]: https://github.com/actix/actix-web/pull/2250
|
||||||
|
[#2253]: https://github.com/actix/actix-web/pull/2253
|
||||||
|
[#2262]: https://github.com/actix/actix-web/pull/2262
|
||||||
|
[#2263]: https://github.com/actix/actix-web/pull/2263
|
||||||
|
[#2271]: https://github.com/actix/actix-web/pull/2271
|
||||||
|
[#2282]: https://github.com/actix/actix-web/pull/2282
|
||||||
|
[#2288]: https://github.com/actix/actix-web/pull/2288
|
||||||
|
[#2325]: https://github.com/actix/actix-web/pull/2325
|
||||||
|
[#2344]: https://github.com/actix/actix-web/pull/2344
|
||||||
|
[#2362]: https://github.com/actix/actix-web/pull/2362
|
||||||
|
[#2379]: https://github.com/actix/actix-web/pull/2379
|
||||||
|
[#2384]: https://github.com/actix/actix-web/pull/2384
|
||||||
|
[#2401]: https://github.com/actix/actix-web/pull/2401
|
||||||
|
[#2403]: https://github.com/actix/actix-web/pull/2403
|
||||||
|
[#2409]: https://github.com/actix/actix-web/pull/2409
|
||||||
|
[#2414]: https://github.com/actix/actix-web/pull/2414
|
||||||
|
[#2423]: https://github.com/actix/actix-web/pull/2423
|
||||||
|
[#2430]: https://github.com/actix/actix-web/pull/2430
|
||||||
|
[#2442]: https://github.com/actix/actix-web/pull/2442
|
||||||
|
[#2446]: https://github.com/actix/actix-web/pull/2446
|
||||||
|
[#2448]: https://github.com/actix/actix-web/pull/2448
|
||||||
|
[#2468]: https://github.com/actix/actix-web/pull/2468
|
||||||
|
[#2474]: https://github.com/actix/actix-web/pull/2474
|
||||||
|
[#2480]: https://github.com/actix/actix-web/pull/2480
|
||||||
|
[#2482]: https://github.com/actix/actix-web/pull/2482
|
||||||
|
[#2484]: https://github.com/actix/actix-web/pull/2484
|
||||||
|
[#2485]: https://github.com/actix/actix-web/pull/2485
|
||||||
|
[#2487]: https://github.com/actix/actix-web/pull/2487
|
||||||
|
[#2491]: https://github.com/actix/actix-web/pull/2491
|
||||||
|
[#2492]: https://github.com/actix/actix-web/pull/2492
|
||||||
|
[#2493]: https://github.com/actix/actix-web/pull/2493
|
||||||
|
[#2499]: https://github.com/actix/actix-web/pull/2499
|
||||||
|
[#2501]: https://github.com/actix/actix-web/pull/2501
|
||||||
|
[#2510]: https://github.com/actix/actix-web/pull/2510
|
||||||
|
[#2515]: https://github.com/actix/actix-web/pull/2515
|
||||||
|
[#2516]: https://github.com/actix/actix-web/pull/2516
|
||||||
|
[#2518]: https://github.com/actix/actix-web/pull/2518
|
||||||
|
[#2523]: https://github.com/actix/actix-web/pull/2523
|
||||||
|
[#2526]: https://github.com/actix/actix-web/pull/2526
|
||||||
|
[#2552]: https://github.com/actix/actix-web/pull/2552
|
||||||
|
[#2554]: https://github.com/actix/actix-web/pull/2554
|
||||||
|
[#2555]: https://github.com/actix/actix-web/pull/2555
|
||||||
|
[#2565]: https://github.com/actix/actix-web/pull/2565
|
||||||
|
[#2567]: https://github.com/actix/actix-web/pull/2567
|
||||||
|
[#2569]: https://github.com/actix/actix-web/pull/2569
|
||||||
|
[#2581]: https://github.com/actix/actix-web/pull/2581
|
||||||
|
[#2582]: https://github.com/actix/actix-web/pull/2582
|
||||||
|
[#2584]: https://github.com/actix/actix-web/pull/2584
|
||||||
|
[#2585]: https://github.com/actix/actix-web/pull/2585
|
||||||
|
[#2586]: https://github.com/actix/actix-web/pull/2586
|
||||||
|
[#2591]: https://github.com/actix/actix-web/pull/2591
|
||||||
|
[#2594]: https://github.com/actix/actix-web/pull/2594
|
||||||
|
[#2601]: https://github.com/actix/actix-web/pull/2601
|
||||||
|
[#2611]: https://github.com/actix/actix-web/pull/2611
|
||||||
|
[#2619]: https://github.com/actix/actix-web/pull/2619
|
||||||
|
[#2625]: https://github.com/actix/actix-web/pull/2625
|
||||||
|
[#2635]: https://github.com/actix/actix-web/pull/2635
|
||||||
|
[#2659]: https://github.com/actix/actix-web/pull/2659
|
||||||
|
[#2663]: https://github.com/actix/actix-web/pull/2663
|
||||||
|
[871ca5e4]: https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>4.0.0 Pre-Releases</summary>
|
||||||
|
|
||||||
## 4.0.0-rc.3 - 2022-02-08
|
## 4.0.0-rc.3 - 2022-02-08
|
||||||
### Changed
|
### Changed
|
||||||
- `middleware::Condition` gained a broader compatibility; `Compat` is needed in fewer cases. [#2635]
|
- `middleware::Condition` gained a broader compatibility; `Compat` is needed in fewer cases. [#2635]
|
||||||
@ -449,6 +726,7 @@
|
|||||||
[#1875]: https://github.com/actix/actix-web/pull/1875
|
[#1875]: https://github.com/actix/actix-web/pull/1875
|
||||||
[#1878]: https://github.com/actix/actix-web/pull/1878
|
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## 3.3.3 - 2021-12-18
|
## 3.3.3 - 2021-12-18
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "4.0.0-rc.3"
|
version = "4.0.1"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
@ -71,12 +71,13 @@ actix-service = "2"
|
|||||||
actix-utils = "3"
|
actix-utils = "3"
|
||||||
actix-tls = { version = "3", default-features = false, optional = true }
|
actix-tls = { version = "3", default-features = false, optional = true }
|
||||||
|
|
||||||
actix-http = { version = "3.0.0-rc.3", features = ["http2", "ws"] }
|
actix-http = { version = "3", features = ["http2", "ws"] }
|
||||||
actix-router = "0.5.0-rc.3"
|
actix-router = "0.5"
|
||||||
actix-web-codegen = { version = "0.5.0-rc.2", optional = true }
|
actix-web-codegen = { version = "4", optional = true }
|
||||||
|
|
||||||
ahash = "0.7"
|
ahash = "0.7"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
|
bytestring = "1"
|
||||||
cfg-if = "1"
|
cfg-if = "1"
|
||||||
cookie = { version = "0.16", features = ["percent-encode"], optional = true }
|
cookie = { version = "0.16", features = ["percent-encode"], optional = true }
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
@ -99,9 +100,9 @@ time = { version = "0.3", default-features = false, features = ["formatting"] }
|
|||||||
url = "2.1"
|
url = "2.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-files = "0.6.0-beta.16"
|
actix-files = "0.6"
|
||||||
actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] }
|
actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] }
|
||||||
awc = { version = "3.0.0-beta.21", features = ["openssl"] }
|
awc = { version = "3", features = ["openssl"] }
|
||||||
|
|
||||||
brotli = "3.3.3"
|
brotli = "3.3.3"
|
||||||
const-str = "0.3"
|
const-str = "0.3"
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
# Migrating to 4.0.0
|
# Migrating to 4.0.0
|
||||||
|
|
||||||
It is assumed that migration is happening _from_ v3.x. If migration from older version of Actix Web, see the other historical migration notes in this folder.
|
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 is not an exhaustive list of changes. Smaller or less impactful code changes are outlined, with links to the PRs that introduced them, in [CHANGES.md](./CHANGES.md). If you think any of the changes not mentioned here deserve to be, submit an issue or 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** that will probably not surface as compile-time errors though automated tests _might_ detect their effects on your app.
|
Headings marked with :warning: are **breaking behavioral changes**. They will probably not surface as compile-time errors though automated tests _might_ detect their effects on your app.
|
||||||
|
|
||||||
## Table of Contents:
|
## Table of Contents:
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ Headings marked with :warning: are **breaking behavioral changes** that will pro
|
|||||||
- [Server Must Be Polled :warning:](#server-must-be-polled-warning)
|
- [Server Must Be Polled :warning:](#server-must-be-polled-warning)
|
||||||
- [Guards API](#guards-api)
|
- [Guards API](#guards-api)
|
||||||
- [Returning `HttpResponse` synchronously](#returning-httpresponse-synchronously)
|
- [Returning `HttpResponse` synchronously](#returning-httpresponse-synchronously)
|
||||||
- [`#[actix_web::main]` and `#[tokio::main]`](#actixwebmain-and-tokiomain)
|
- [`#[actix_web::main]` and `#[tokio::main]`](#actix_webmain-and-tokiomain)
|
||||||
- [`web::block`](#webblock)
|
- [`web::block`](#webblock)
|
||||||
|
|
||||||
## MSRV
|
## MSRV
|
||||||
@ -37,22 +38,30 @@ The MSRV of Actix Web has been raised from 1.42 to 1.54.
|
|||||||
|
|
||||||
## Tokio v1 Ecosystem
|
## Tokio v1 Ecosystem
|
||||||
|
|
||||||
Actix Web v4 is now underpinned by the the Tokio v1 ecosystem of crates. If you have dependencies that might utilize Tokio directly, it is worth checking to see if an update is available. The following command will assist in finding such dependencies:
|
Actix Web v4 is now underpinned by `tokio`'s v1 ecosystem.
|
||||||
|
|
||||||
|
`cargo` supports having multiple versions of the same crate within the same dependency tree, but `tokio` v1 does not interoperate transparently with its previous versions (v0.2, v0.1). Some of your dependencies might rely on `tokio`, either directly or indirectly—if they are using an older version of `tokio`, check if an update is available.
|
||||||
|
The following command can help you to identify these dependencies:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
# Find all crates in your dependency tree that depend on `tokio`
|
||||||
|
# It also reports the different versions of `tokio` in your dependency tree.
|
||||||
cargo tree -i tokio
|
cargo tree -i tokio
|
||||||
|
|
||||||
# if multiple tokio versions are depended on, show the older ones with:
|
# if you depend on multiple versions of tokio, use this command to
|
||||||
|
# list the dependencies relying on a specific version of tokio:
|
||||||
cargo tree -i tokio:0.2.25
|
cargo tree -i tokio:0.2.25
|
||||||
```
|
```
|
||||||
|
|
||||||
## Module Structure
|
## Module Structure
|
||||||
|
|
||||||
Lots of modules has been organized in this release. If a compile error refers to "item XYZ not found in module..." or "module XYZ not found", refer to the [documentation on docs.rs](https://docs.rs/actix-web) to to search for items' new locations.
|
Lots of modules have been re-organized in this release. If a compile error refers to "item XYZ not found in module..." or "module XYZ not found", check the [documentation on docs.rs](https://docs.rs/actix-web) to search for items' new locations.
|
||||||
|
|
||||||
## `NormalizePath` Middleware :warning:
|
## `NormalizePath` Middleware :warning:
|
||||||
|
|
||||||
The default `NormalizePath` behavior now strips trailing slashes by default. This was previously documented to be the case in v3 but the behavior now matches. The effect is that routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning. It is advised that the `new` or `trim` methods be used instead.
|
The default `NormalizePath` behavior now strips trailing slashes by default. This was the _documented_ behaviour in Actix Web v3, but the _actual_ behaviour differed. The discrepancy has now been resolved.
|
||||||
|
|
||||||
|
As a consequence of this change, routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. Calling `NormalizePath::default()` will log a warning. We suggest to use `new` or `trim`.
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
- #[get("/test/")]
|
- #[get("/test/")]
|
||||||
@ -86,7 +95,7 @@ Consequently, the `FromRequest::configure` method was also removed. Config for e
|
|||||||
|
|
||||||
## Compression Feature Flags
|
## Compression Feature Flags
|
||||||
|
|
||||||
Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). By default, all compression algorithms are enabled. If you want to select specific compression codecs, the new flags are:
|
The `compress` feature flag has been split into more granular feature flags, one for each supported algorithm (brotli, gzip, zstd). By default, all compression algorithms are enabled. If you want to select specific compression codecs, the new flags are:
|
||||||
|
|
||||||
- `compress-brotli`
|
- `compress-brotli`
|
||||||
- `compress-gzip`
|
- `compress-gzip`
|
||||||
@ -94,7 +103,7 @@ Feature flag `compress` has been split into its supported algorithm (brotli, gzi
|
|||||||
|
|
||||||
## `web::Path`
|
## `web::Path`
|
||||||
|
|
||||||
The inner field for `web::Path` was made private because It was causing too many issues when used with inner tuple types due to its `Deref` impl.
|
The inner field for `web::Path` is now private. It was causing ambiguity when trying to use tuple indexing due to its `Deref` implementation.
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
- async fn handler(web::Path((foo, bar)): web::Path<(String, String)>) {
|
- async fn handler(web::Path((foo, bar)): web::Path<(String, String)>) {
|
||||||
@ -102,13 +111,15 @@ The inner field for `web::Path` was made private because It was causing too many
|
|||||||
+ let (foo, bar) = params.into_inner();
|
+ let (foo, bar) = params.into_inner();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
An alternative [path param type with public field but no `Deref` impl is available in `actix-web-lab`](https://docs.rs/actix-web-lab/0.12.0/actix_web_lab/extract/struct.Path.html).
|
||||||
|
|
||||||
## Rustls Crate Upgrade
|
## Rustls Crate Upgrade
|
||||||
|
|
||||||
Required version of `rustls` dependency was bumped to the latest version 0.20. As a result, the new server config builder has changed. [See the updated example project →.](https://github.com/actix/examples/tree/HEAD/security/rustls/)
|
Actix Web now depends on version 0.20 of `rustls`. As a result, the server config builder has changed. [See the updated example project.](https://github.com/actix/examples/tree/master/https-tls/rustls/)
|
||||||
|
|
||||||
## Removed `awc` Client Re-export
|
## Removed `awc` Client Re-export
|
||||||
|
|
||||||
Actix Web's sister crate `awc` is no longer re-exported through the `client` module. This allows `awc` its own release cadence and prevents its own breaking changes from being blocked due to a re-export.
|
Actix Web's sister crate `awc` is no longer re-exported through the `client` module. This allows `awc` to have its own release cadence—its breaking changes are no longer blocked by Actix Web's (more conservative) release schedule.
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
- use actix_web::client::Client;
|
- use actix_web::client::Client;
|
||||||
@ -117,18 +128,20 @@ Actix Web's sister crate `awc` is no longer re-exported through the `client` mod
|
|||||||
|
|
||||||
## Integration Testing Utils Moved To `actix-test`
|
## Integration Testing Utils Moved To `actix-test`
|
||||||
|
|
||||||
Actix Web's `test` module used to contain `TestServer`. Since this required the `awc` client and it was removed as a re-export (see above), it was moved to its own crate [`actix-test`](https://docs.rs/actix-test).
|
`TestServer` has been moved to its own crate, [`actix-test`](https://docs.rs/actix-test).
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
- use use actix_web::test::start;
|
- use use actix_web::test::start;
|
||||||
+ use use actix_test::start;
|
+ use use actix_test::start;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`TestServer` previously lived in `actix_web::test`, but it depends on `awc` which is no longer part of Actix Web's public API (see above).
|
||||||
|
|
||||||
## Header APIs
|
## Header APIs
|
||||||
|
|
||||||
Header related APIs have been standardized across all `actix-*` crates. The terminology now better matches the underlying `HeaderMap` naming conventions. Most of the the old methods have only been deprecated with notes that will guide how to update.
|
Header related APIs have been standardized across all `actix-*` crates. The terminology now better matches the underlying `HeaderMap` naming conventions.
|
||||||
|
|
||||||
In short, "insert" always indicates that existing any existing headers with the same name are overridden and "append" indicates adding with no removal.
|
In short, "insert" always indicates that any existing headers with the same name are overridden, while "append" is used for adding with no removal (e.g. multi-valued headers).
|
||||||
|
|
||||||
For request and response builder APIs, the new methods provide a unified interface for adding key-value pairs _and_ typed headers, which can often be more expressive.
|
For request and response builder APIs, the new methods provide a unified interface for adding key-value pairs _and_ typed headers, which can often be more expressive.
|
||||||
|
|
||||||
@ -143,11 +156,13 @@ For request and response builder APIs, the new methods provide a unified interfa
|
|||||||
+ .insert_header(ContentType::json())
|
+ .insert_header(ContentType::json())
|
||||||
```
|
```
|
||||||
|
|
||||||
|
We chose to deprecate most of the old methods instead of removing them immediately—the warning notes will guide you on how to update.
|
||||||
|
|
||||||
## Response Body Types
|
## Response Body Types
|
||||||
|
|
||||||
There have been a lot of changes to response body types. The general theme is that they are now more expressive and their purposes are more obvious.
|
There have been a lot of changes to response body types. They are now more expressive and their purpose should be more intuitive.
|
||||||
|
|
||||||
All items in the [`body` module](https://docs.rs/actix-web/4/actix_web/body) have much better documentation now.
|
We have boosted the quality and completeness of the documentation for all items in the [`body` module](https://docs.rs/actix-web/4/actix_web/body).
|
||||||
|
|
||||||
### `ResponseBody`
|
### `ResponseBody`
|
||||||
|
|
||||||
@ -164,23 +179,159 @@ All items in the [`body` module](https://docs.rs/actix-web/4/actix_web/body) hav
|
|||||||
|
|
||||||
### `BoxBody`
|
### `BoxBody`
|
||||||
|
|
||||||
`BoxBody` is a new type erased body type. It's used for all error response bodies use this. Creating a boxed body is best done by calling [`.boxed()`](https://docs.rs/actix-web/4/actix_web/body/trait.MessageBody.html#method.boxed) on a `MessageBody` type.
|
`BoxBody` is a new type-erased body type.
|
||||||
|
|
||||||
|
It can be useful when writing handlers, responders, and middleware when you want to trade a (very) small amount of performance for a simpler type.
|
||||||
|
|
||||||
|
Creating a boxed body is done most efficiently by calling [`.boxed()`](https://docs.rs/actix-web/4/actix_web/body/trait.MessageBody.html#method.boxed) on a `MessageBody` type.
|
||||||
|
|
||||||
### `EitherBody`
|
### `EitherBody`
|
||||||
|
|
||||||
`EitherBody` is a new "either" type that is particularly useful in middleware that can bail early, returning their own response plus body type.
|
`EitherBody` is a new "either" type that implements `MessageBody`
|
||||||
|
|
||||||
|
It is particularly useful in middleware that can bail early, returning their own response plus body type. By default the "right" variant is `BoxBody` (i.e., `EitherBody<B>` === `EitherBody<B, BoxBody>`) but it can be anything that implements `MessageBody`.
|
||||||
|
|
||||||
|
For example, it will be common among middleware which value performance of the hot path to use:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
type Response = Result<ServiceResponse<EitherBody<B>>, Error>
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be read (ignoring the `Result`) as "resolves with a `ServiceResponse` that is either the inner service's `B` body type or a boxed body type from elsewhere, likely constructed within the middleware itself". Of course, if your middleware contains only simple string other/error responses, it's possible to use them without boxes at the cost of a less simple implementation:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
type Response = Result<ServiceResponse<EitherBody<B, String>>, Error>
|
||||||
|
```
|
||||||
|
|
||||||
### Error Handlers
|
### Error Handlers
|
||||||
|
|
||||||
TODO In particular, folks seem to be struggling with the `ErrorHandlers` middleware because of this change and the obscured nature of `EitherBody` within its types.
|
`ErrorHandlers` is a commonly used middleware that has changed in design slightly due to the other body type changes.
|
||||||
|
|
||||||
|
In particular, an implicit `EitherBody` is used in the `ErrorHandlerResponse<B>` type. An `ErrorHandlerResponse<B>` now expects a `ServiceResponse<EitherBody<B>>` to be returned within response variants. The following is a migration for an error handler that **only modifies** the response argument (left body).
|
||||||
|
|
||||||
|
```diff
|
||||||
|
fn add_error_header<B>(mut res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>, Error> {
|
||||||
|
res.response_mut().headers_mut().insert(
|
||||||
|
header::CONTENT_TYPE,
|
||||||
|
header::HeaderValue::from_static("Error"),
|
||||||
|
);
|
||||||
|
- Ok(ErrorHandlerResponse::Response(res))
|
||||||
|
+ Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The following is a migration for an error handler that creates a new response instead (right body).
|
||||||
|
|
||||||
|
```diff
|
||||||
|
fn error_handler<B>(res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>, Error> {
|
||||||
|
- let req = res.request().clone();
|
||||||
|
+ let (req, _res) = res.into_parts();
|
||||||
|
|
||||||
|
let res = actix_files::NamedFile::open("./templates/404.html")?
|
||||||
|
.set_status_code(StatusCode::NOT_FOUND)
|
||||||
|
- .into_response(&req)?
|
||||||
|
- .into_body();
|
||||||
|
+ .into_response(&req);
|
||||||
|
|
||||||
|
- let res = ServiceResponse::new(req, res);
|
||||||
|
+ let res = ServiceResponse::new(req, res).map_into_right_body();
|
||||||
|
Ok(ErrorHandlerResponse::Response(res))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Middleware Trait APIs
|
## Middleware Trait APIs
|
||||||
|
|
||||||
This section builds upon guidance from the [response body types](#response-body-types) section.
|
The underlying traits that are used for creating middleware, `Service`, `ServiceFactory`, and `Transform`, have changed in design.
|
||||||
|
|
||||||
TODO
|
- The associated `Request` type has moved to the type parameter position in order to allow multiple request implementations in other areas of the service stack.
|
||||||
|
- The `self` arguments in `Service` have changed from exclusive (mutable) borrows to shared (immutable) borrows. Since most service layers, such as middleware, do not host mutable state, it reduces the runtime overhead in places where a `RefCell` used to be required for wrapping an inner service.
|
||||||
|
- We've also introduced some macros that reduce boilerplate when implementing `poll_ready`.
|
||||||
|
- Further to the guidance on [response body types](#response-body-types), any use of the old methods on `ServiceResponse` designed to match up body types (e.g., the old `into_body` method), should be replaced with an explicit response body type utilizing `EitherBody<B>`.
|
||||||
|
|
||||||
TODO: Also write the Middleware author's guide.
|
A typical migration would look like this:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
use std::{
|
||||||
|
- cell::RefCell,
|
||||||
|
future::Future,
|
||||||
|
pin::Pin,
|
||||||
|
rc::Rc,
|
||||||
|
- task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
use actix_web::{
|
||||||
|
dev::{Service, ServiceRequest, ServiceResponse, Transform},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
use futures_util::future::{ok, LocalBoxFuture, Ready};
|
||||||
|
|
||||||
|
pub struct SayHi;
|
||||||
|
|
||||||
|
- impl<S, B> Transform<S> for SayHi
|
||||||
|
+ impl<S, B> Transform<S, ServiceRequest> for SayHi
|
||||||
|
where
|
||||||
|
- S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
+ S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: 'static,
|
||||||
|
{
|
||||||
|
- type Request = ServiceRequest;
|
||||||
|
type Response = ServiceResponse<B>;
|
||||||
|
type Error = Error;
|
||||||
|
type InitError = ();
|
||||||
|
type Transform = SayHiMiddleware<S>;
|
||||||
|
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||||
|
|
||||||
|
fn new_transform(&self, service: S) -> Self::Future {
|
||||||
|
ok(SayHiMiddleware {
|
||||||
|
- service: Rc::new(RefCell::new(service)),
|
||||||
|
+ service: Rc::new(service),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SayHiMiddleware<S> {
|
||||||
|
- service: Rc<RefCell<S>>,
|
||||||
|
+ service: Rc<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
- impl<S, B> Service for SayHiMiddleware<S>
|
||||||
|
+ impl<S, B> Service<ServiceRequest> for SayHiMiddleware<S>
|
||||||
|
where
|
||||||
|
- S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
+ S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: 'static,
|
||||||
|
{
|
||||||
|
- type Request = ServiceRequest;
|
||||||
|
type Response = ServiceResponse<B>;
|
||||||
|
type Error = Error;
|
||||||
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
- fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
- self.service.poll_ready(cx)
|
||||||
|
- }
|
||||||
|
+ actix_web::dev::forward_ready!(service);
|
||||||
|
|
||||||
|
- fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||||
|
+ fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||||
|
println!("Hi from start. You requested: {}", req.path());
|
||||||
|
|
||||||
|
let fut = self.service.call(req);
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
let res = fut.await?;
|
||||||
|
|
||||||
|
println!("Hi from response");
|
||||||
|
Ok(res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This new design is forward-looking and should ease transition to traits that support the upcoming Generic Associated Type (GAT) feature in Rust while also trimming down the boilerplate required to implement middleware.
|
||||||
|
|
||||||
|
We understand that creating middleware is still a pain point for Actix Web and we hope to provide [an even more ergonomic solution](https://docs.rs/actix-web-lab/0.11.0/actix_web_lab/middleware/fn.from_fn.html) in a v4.x release.
|
||||||
|
|
||||||
## `Responder` Trait
|
## `Responder` Trait
|
||||||
|
|
||||||
@ -208,7 +359,7 @@ Now that more emphasis is placed on expressive body types, as explained in the [
|
|||||||
|
|
||||||
## `App::data` Deprecation :warning:
|
## `App::data` Deprecation :warning:
|
||||||
|
|
||||||
The `App::data` method is deprecated. Replace instances of this with `App::app_data`. Exposing both methods was a footgun and lead to lots of confusion when trying to extract the data in handlers. Now, when using the `Data` wrapper, the type you put in to `app_data` is the same type you extract in handler arguments.
|
The `App::data` method is deprecated. Replace instances of this with `App::app_data`. Exposing both methods led to lots of confusion when trying to extract the data in handlers. Now, when using the `Data` wrapper, the type you put in to `app_data` is the same type you extract in handler arguments.
|
||||||
|
|
||||||
You may need to review the [guidance on shared mutable state](https://docs.rs/actix-web/4/actix_web/struct.App.html#shared-mutable-state) in order to migrate this correctly.
|
You may need to review the [guidance on shared mutable state](https://docs.rs/actix-web/4/actix_web/struct.App.html#shared-mutable-state) in order to migrate this correctly.
|
||||||
|
|
||||||
@ -233,7 +384,12 @@ You may need to review the [guidance on shared mutable state](https://docs.rs/ac
|
|||||||
|
|
||||||
## Direct Dependency On `actix-rt` And `actix-service`
|
## Direct Dependency On `actix-rt` And `actix-service`
|
||||||
|
|
||||||
Improvements to module management and re-exports have resulted in not needing direct dependencies on these underlying crates for the vast majority of cases. In particular, all traits necessary for creating middleware are re-exported through the `dev` modules and `#[actix_web::test]` now exists for async test definitions. Relying on the these re-exports will ease transition to future versions of Actix Web.
|
Improvements to module management and re-exports have resulted in not needing direct dependencies on these underlying crates for the vast majority of cases. In particular:
|
||||||
|
|
||||||
|
- all traits necessary for creating middlewares are now re-exported through the `dev` modules;
|
||||||
|
- `#[actix_web::test]` now exists for async test definitions.
|
||||||
|
|
||||||
|
Relying on these re-exports will ease the transition to future versions of Actix Web.
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
- use actix_service::{Service, Transform};
|
- use actix_service::{Service, Transform};
|
||||||
@ -266,7 +422,7 @@ async fn main() {
|
|||||||
|
|
||||||
## Guards API
|
## Guards API
|
||||||
|
|
||||||
Implementors of routing guards will need to use the modified interface of the `Guard` trait. The API provided is more flexible than before. See [guard module docs](https://docs.rs/actix-web/4/actix_web/guard/struct.GuardContext.html) for more details.
|
Implementors of routing guards will need to use the modified interface of the `Guard` trait. The API is more flexible than before. See [guard module docs](https://docs.rs/actix-web/4/actix_web/guard/struct.GuardContext.html) for more details.
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
struct MethodGuard(HttpMethod);
|
struct MethodGuard(HttpMethod);
|
||||||
@ -289,7 +445,14 @@ web::to(|| HttpResponse::Ok().finish())
|
|||||||
^^^^^^^ the trait `Handler<_>` is not implemented for `[closure@...]`
|
^^^^^^^ the trait `Handler<_>` is not implemented for `[closure@...]`
|
||||||
```
|
```
|
||||||
|
|
||||||
This form should be replaced with the a more explicit async fn:
|
This form should be replaced with explicit async functions and closures:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- fn handler() -> HttpResponse {
|
||||||
|
+ async fn handler() -> HttpResponse {
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
- web::to(|| HttpResponse::Ok().finish())
|
- web::to(|| HttpResponse::Ok().finish())
|
||||||
@ -305,7 +468,7 @@ Or, for these extremely simple cases, utilise an `HttpResponseBuilder`:
|
|||||||
|
|
||||||
## `#[actix_web::main]` and `#[tokio::main]`
|
## `#[actix_web::main]` and `#[tokio::main]`
|
||||||
|
|
||||||
Actix Web now works seamlessly with the primary way of starting a multi-threaded Tokio runtime, `#[tokio::main]`. Therefore, it is no longer necessary to spawn a thread when you need to run something alongside Actix Web that uses of Tokio's multi-threaded mode; you can simply await the server within this context or, if preferred, use `tokio::spawn` just like any other async task.
|
Actix Web now works seamlessly with the primary way of starting a multi-threaded Tokio runtime, `#[tokio::main]`. Therefore, it is no longer necessary to spawn a thread when you need to run something alongside Actix Web that uses Tokio's multi-threaded mode; you can simply await the server within this context or, if preferred, use `tokio::spawn` just like any other async task.
|
||||||
|
|
||||||
For now, `actix` actor support (and therefore WebSocket support via `actix-web-actors`) still requires `#[actix_web::main]` so that a `System` context is created. Designs are being created for an alternative WebSocket interface that does not require actors that should land sometime in the v4.x cycle.
|
For now, `actix` actor support (and therefore WebSocket support via `actix-web-actors`) still requires `#[actix_web::main]` so that a `System` context is created. Designs are being created for an alternative WebSocket interface that does not require actors that should land sometime in the v4.x cycle.
|
||||||
|
|
||||||
|
@ -6,10 +6,10 @@
|
|||||||
<p>
|
<p>
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web)
|
[](https://crates.io/crates/actix-web)
|
||||||
[](https://docs.rs/actix-web/4.0.0-rc.3)
|
[](https://docs.rs/actix-web/4.0.1)
|
||||||

|

|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-web/4.0.0-rc.3)
|
[](https://deps.rs/crate/actix-web/4.0.1)
|
||||||
<br />
|
<br />
|
||||||
[](https://github.com/actix/actix-web/actions/workflows/ci.yml)
|
[](https://github.com/actix/actix-web/actions/workflows/ci.yml)
|
||||||
[](https://codecov.io/gh/actix/actix-web)
|
[](https://codecov.io/gh/actix/actix-web)
|
||||||
@ -48,7 +48,7 @@ Dependencies:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "4.0.0-rc.1"
|
actix-web = "4"
|
||||||
```
|
```
|
||||||
|
|
||||||
Code:
|
Code:
|
||||||
@ -56,37 +56,40 @@ Code:
|
|||||||
```rust
|
```rust
|
||||||
use actix_web::{get, web, App, HttpServer, Responder};
|
use actix_web::{get, web, App, HttpServer, Responder};
|
||||||
|
|
||||||
#[get("/{id}/{name}/index.html")]
|
#[get("/hello/{name}")]
|
||||||
async fn index(params: web::Path<(u32, String)>) -> impl Responder {
|
async fn greet(name: web::Path<String>) -> impl Responder {
|
||||||
let (id, name) = params.into_inner();
|
format!("Hello {name}!")
|
||||||
format!("Hello {}! id:{}", name, id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::main] // or #[tokio::main]
|
#[actix_web::main] // or #[tokio::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
HttpServer::new(|| App::new().service(index))
|
HttpServer::new(|| {
|
||||||
.bind(("127.0.0.1", 8080))?
|
App::new().service(greet)
|
||||||
.run()
|
})
|
||||||
.await
|
.bind(("127.0.0.1", 8080))?
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### More examples
|
### More Examples
|
||||||
|
|
||||||
- [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics/)
|
- [Hello World](https://github.com/actix/examples/tree/master/basics/hello-world)
|
||||||
- [Application State](https://github.com/actix/examples/tree/master/basics/state/)
|
- [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics)
|
||||||
- [JSON Handling](https://github.com/actix/examples/tree/master/json/json/)
|
- [Application State](https://github.com/actix/examples/tree/master/basics/state)
|
||||||
- [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart/)
|
- [JSON Handling](https://github.com/actix/examples/tree/master/json/json)
|
||||||
- [Diesel Integration](https://github.com/actix/examples/tree/master/database_interactions/diesel/)
|
- [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart)
|
||||||
- [r2d2 Integration](https://github.com/actix/examples/tree/master/database_interactions/r2d2/)
|
- [Diesel Integration](https://github.com/actix/examples/tree/master/databases/diesel)
|
||||||
- [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/websocket/)
|
- [SQLite Integration](https://github.com/actix/examples/tree/master/databases/sqlite)
|
||||||
- [Tera Templates](https://github.com/actix/examples/tree/master/template_engines/tera/)
|
- [Postgres Integration](https://github.com/actix/examples/tree/master/databases/postgres)
|
||||||
- [Askama Templates](https://github.com/actix/examples/tree/master/template_engines/askama/)
|
- [Tera Templates](https://github.com/actix/examples/tree/master/templating/tera)
|
||||||
- [HTTPS using Rustls](https://github.com/actix/examples/tree/master/security/rustls/)
|
- [Askama Templates](https://github.com/actix/examples/tree/master/templating/askama)
|
||||||
- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/)
|
- [HTTPS using Rustls](https://github.com/actix/examples/tree/master/https-tls/rustls)
|
||||||
- [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/)
|
- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/https-tls/openssl)
|
||||||
|
- [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets)
|
||||||
|
- [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat)
|
||||||
|
|
||||||
You may consider checking out [this directory](https://github.com/actix/examples/tree/master/) for more examples.
|
You may consider checking out [this directory](https://github.com/actix/examples/tree/master) for more examples.
|
||||||
|
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|
||||||
@ -101,5 +104,4 @@ This project is licensed under either of the following licenses, at your option:
|
|||||||
|
|
||||||
## Code of Conduct
|
## Code of Conduct
|
||||||
|
|
||||||
Contribution to the actix-web repo is organized under the terms of the Contributor Covenant.
|
Contribution to the actix-web repo is organized under the terms of the Contributor Covenant. The Actix team promises to intervene to uphold that code of conduct.
|
||||||
The Actix team promises to intervene to uphold that code of conduct.
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
//! properties and pass them to a handler through request-local data.
|
//! properties and pass them to a handler through request-local data.
|
||||||
//!
|
//!
|
||||||
//! For an example of extracting a client TLS certificate, see:
|
//! For an example of extracting a client TLS certificate, see:
|
||||||
//! <https://github.com/actix/examples/tree/HEAD/security/rustls-client-cert>
|
//! <https://github.com/actix/examples/tree/master/https-tls/rustls-client-cert>
|
||||||
|
|
||||||
use std::{any::Any, io, net::SocketAddr};
|
use std::{any::Any, io, net::SocketAddr};
|
||||||
|
|
||||||
|
@ -290,12 +290,10 @@ where
|
|||||||
/// Ok(HttpResponse::Ok().into())
|
/// Ok(HttpResponse::Ok().into())
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// let app = App::new()
|
||||||
/// let app = App::new()
|
/// .service(web::resource("/index.html").route(
|
||||||
/// .service(web::resource("/index.html").route(
|
/// web::get().to(index)))
|
||||||
/// web::get().to(index)))
|
/// .external_resource("youtube", "https://youtube.com/watch/{video_id}");
|
||||||
/// .external_resource("youtube", "https://youtube.com/watch/{video_id}");
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn external_resource<N, U>(mut self, name: N, url: U) -> Self
|
pub fn external_resource<N, U>(mut self, name: N, url: U) -> Self
|
||||||
where
|
where
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
//!
|
//!
|
||||||
//! Most users will not have to interact with the types in this module, but it is useful for those
|
//! Most users will not have to interact with the types in this module, but it is useful for those
|
||||||
//! writing extractors, middleware, libraries, or interacting with the service API directly.
|
//! writing extractors, middleware, libraries, or interacting with the service API directly.
|
||||||
|
//!
|
||||||
|
//! # Request Extractors
|
||||||
|
//! - [`ConnectionInfo`]: Connection information
|
||||||
|
//! - [`PeerAddr`]: Connection information
|
||||||
|
|
||||||
pub use actix_http::{Extensions, Payload, RequestHead, Response, ResponseHead};
|
pub use actix_http::{Extensions, Payload, RequestHead, Response, ResponseHead};
|
||||||
pub use actix_router::{Path, ResourceDef, ResourcePath, Url};
|
pub use actix_router::{Path, ResourceDef, ResourcePath, Url};
|
||||||
|
@ -47,7 +47,6 @@ impl fmt::Debug for Error {
|
|||||||
|
|
||||||
impl StdError for Error {
|
impl StdError for Error {
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
// TODO: populate if replacement for Box<dyn Error> is found
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
//
|
//
|
||||||
// See <https://github.com/rust-lang/rust/issues/83375>
|
// See <https://github.com/rust-lang/rust/issues/83375>
|
||||||
pub use actix_http::error::{
|
pub use actix_http::error::{
|
||||||
BlockingError, ContentTypeError, DispatchError, HttpError, ParseError, PayloadError,
|
ContentTypeError, DispatchError, HttpError, ParseError, PayloadError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use derive_more::{Display, Error, From};
|
use derive_more::{Display, Error, From};
|
||||||
@ -33,6 +33,14 @@ pub(crate) use macros::{downcast_dyn, downcast_get_type_id};
|
|||||||
/// This type alias is generally used to avoid writing out `actix_http::Error` directly.
|
/// This type alias is generally used to avoid writing out `actix_http::Error` directly.
|
||||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
/// An error representing a problem running a blocking task on a thread pool.
|
||||||
|
#[derive(Debug, Display, Error)]
|
||||||
|
#[display(fmt = "Blocking thread pool is shut down unexpectedly")]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct BlockingError;
|
||||||
|
|
||||||
|
impl ResponseError for crate::error::BlockingError {}
|
||||||
|
|
||||||
/// Errors which can occur when attempting to generate resource uri.
|
/// Errors which can occur when attempting to generate resource uri.
|
||||||
#[derive(Debug, PartialEq, Display, Error, From)]
|
#[derive(Debug, PartialEq, Display, Error, From)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
@ -6,20 +6,22 @@ use std::{
|
|||||||
io::{self, Write as _},
|
io::{self, Write as _},
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::Response;
|
||||||
body::BoxBody,
|
|
||||||
header::{self, TryIntoHeaderValue},
|
|
||||||
Response, StatusCode,
|
|
||||||
};
|
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
body::BoxBody,
|
||||||
error::{downcast_dyn, downcast_get_type_id},
|
error::{downcast_dyn, downcast_get_type_id},
|
||||||
helpers, HttpResponse,
|
helpers,
|
||||||
|
http::{
|
||||||
|
header::{self, TryIntoHeaderValue},
|
||||||
|
StatusCode,
|
||||||
|
},
|
||||||
|
HttpResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Errors that can generate responses.
|
/// Errors that can generate responses.
|
||||||
// TODO: add std::error::Error bound when replacement for Box<dyn Error> is found
|
// TODO: flesh out documentation
|
||||||
pub trait ResponseError: fmt::Debug + fmt::Display {
|
pub trait ResponseError: fmt::Debug + fmt::Display {
|
||||||
/// Returns appropriate status code for error.
|
/// Returns appropriate status code for error.
|
||||||
///
|
///
|
||||||
@ -73,7 +75,6 @@ impl ResponseError for std::str::Utf8Error {
|
|||||||
|
|
||||||
impl ResponseError for std::io::Error {
|
impl ResponseError for std::io::Error {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
// TODO: decide if these errors should consider not found or permission errors
|
|
||||||
match self.kind() {
|
match self.kind() {
|
||||||
io::ErrorKind::NotFound => StatusCode::NOT_FOUND,
|
io::ErrorKind::NotFound => StatusCode::NOT_FOUND,
|
||||||
io::ErrorKind::PermissionDenied => StatusCode::FORBIDDEN,
|
io::ErrorKind::PermissionDenied => StatusCode::FORBIDDEN,
|
||||||
@ -86,7 +87,6 @@ impl ResponseError for actix_http::error::HttpError {}
|
|||||||
|
|
||||||
impl ResponseError for actix_http::Error {
|
impl ResponseError for actix_http::Error {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
// TODO: map error kinds to status code better
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,8 +107,6 @@ impl ResponseError for actix_http::error::ParseError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseError for actix_http::error::BlockingError {}
|
|
||||||
|
|
||||||
impl ResponseError for actix_http::error::PayloadError {
|
impl ResponseError for actix_http::error::PayloadError {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
match *self {
|
match *self {
|
||||||
|
@ -118,12 +118,10 @@ pub trait FromRequest: Sized {
|
|||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// let app = App::new().service(
|
||||||
/// let app = App::new().service(
|
/// web::resource("/users/:first").route(
|
||||||
/// web::resource("/users/:first").route(
|
/// web::post().to(index))
|
||||||
/// web::post().to(index))
|
/// );
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
impl<T> FromRequest for Option<T>
|
impl<T> FromRequest for Option<T>
|
||||||
where
|
where
|
||||||
@ -205,11 +203,9 @@ where
|
|||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// let app = App::new().service(
|
||||||
/// let app = App::new().service(
|
/// web::resource("/users/:first").route(web::post().to(index))
|
||||||
/// web::resource("/users/:first").route(web::post().to(index))
|
/// );
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
impl<T, E> FromRequest for Result<T, E>
|
impl<T, E> FromRequest for Result<T, E>
|
||||||
where
|
where
|
||||||
|
@ -2,5 +2,4 @@
|
|||||||
|
|
||||||
pub mod header;
|
pub mod header;
|
||||||
|
|
||||||
// TODO: figure out how best to expose http::Error vs actix_http::Error
|
|
||||||
pub use actix_http::{uri, ConnectionType, Error, KeepAlive, Method, StatusCode, Uri, Version};
|
pub use actix_http::{uri, ConnectionType, Error, KeepAlive, Method, StatusCode, Uri, Version};
|
||||||
|
@ -159,7 +159,7 @@ impl ConnectionInfo {
|
|||||||
pub fn realip_remote_addr(&self) -> Option<&str> {
|
pub fn realip_remote_addr(&self) -> Option<&str> {
|
||||||
self.realip_remote_addr
|
self.realip_remote_addr
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.or_else(|| self.peer_addr.as_deref())
|
.or(self.peer_addr.as_deref())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns serialized IP address of the peer connection.
|
/// Returns serialized IP address of the peer connection.
|
||||||
|
@ -4,18 +4,19 @@
|
|||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use actix_web::{get, web, App, HttpServer, Responder};
|
//! use actix_web::{get, web, App, HttpServer, Responder};
|
||||||
//!
|
//!
|
||||||
//! #[get("/{id}/{name}/index.html")]
|
//! #[get("/hello/{name}")]
|
||||||
//! async fn index(path: web::Path<(u32, String)>) -> impl Responder {
|
//! async fn greet(name: web::Path<String>) -> impl Responder {
|
||||||
//! let (id, name) = path.into_inner();
|
//! format!("Hello {}!", name)
|
||||||
//! format!("Hello {}! id:{}", name, id)
|
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! #[actix_web::main]
|
//! #[actix_web::main] // or #[tokio::main]
|
||||||
//! async fn main() -> std::io::Result<()> {
|
//! async fn main() -> std::io::Result<()> {
|
||||||
//! HttpServer::new(|| App::new().service(index))
|
//! HttpServer::new(|| {
|
||||||
//! .bind("127.0.0.1:8080")?
|
//! App::new().service(greet)
|
||||||
//! .run()
|
//! })
|
||||||
//! .await
|
//! .bind(("127.0.0.1", 8080))?
|
||||||
|
//! .run()
|
||||||
|
//! .await
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
@ -185,6 +185,7 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
body,
|
||||||
http::{
|
http::{
|
||||||
header::{HeaderValue, CONTENT_TYPE},
|
header::{HeaderValue, CONTENT_TYPE},
|
||||||
StatusCode,
|
StatusCode,
|
||||||
@ -203,7 +204,7 @@ mod tests {
|
|||||||
Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
|
Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
|
||||||
}
|
}
|
||||||
|
|
||||||
let srv = test::simple_service(StatusCode::INTERNAL_SERVER_ERROR);
|
let srv = test::status_service(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
|
||||||
let mw = ErrorHandlers::new()
|
let mw = ErrorHandlers::new()
|
||||||
.handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler)
|
.handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler)
|
||||||
@ -230,7 +231,7 @@ mod tests {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
let srv = test::simple_service(StatusCode::INTERNAL_SERVER_ERROR);
|
let srv = test::status_service(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
|
||||||
let mw = ErrorHandlers::new()
|
let mw = ErrorHandlers::new()
|
||||||
.handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler)
|
.handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler)
|
||||||
@ -245,9 +246,7 @@ mod tests {
|
|||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn changes_body_type() {
|
async fn changes_body_type() {
|
||||||
#[allow(clippy::unnecessary_wraps)]
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
fn error_handler<B: 'static>(
|
fn error_handler<B>(res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
||||||
res: ServiceResponse<B>,
|
|
||||||
) -> Result<ErrorHandlerResponse<B>> {
|
|
||||||
let (req, res) = res.into_parts();
|
let (req, res) = res.into_parts();
|
||||||
let res = res.set_body(Bytes::from("sorry, that's no bueno"));
|
let res = res.set_body(Bytes::from("sorry, that's no bueno"));
|
||||||
|
|
||||||
@ -258,7 +257,7 @@ mod tests {
|
|||||||
Ok(ErrorHandlerResponse::Response(res))
|
Ok(ErrorHandlerResponse::Response(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
let srv = test::simple_service(StatusCode::INTERNAL_SERVER_ERROR);
|
let srv = test::status_service(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
|
||||||
let mw = ErrorHandlers::new()
|
let mw = ErrorHandlers::new()
|
||||||
.handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler)
|
.handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler)
|
||||||
@ -270,5 +269,33 @@ mod tests {
|
|||||||
assert_eq!(test::read_body(res).await, "sorry, that's no bueno");
|
assert_eq!(test::read_body(res).await, "sorry, that's no bueno");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test where error is thrown
|
#[actix_rt::test]
|
||||||
|
async fn error_thrown() {
|
||||||
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
|
fn error_handler<B>(_res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
||||||
|
Err(crate::error::ErrorInternalServerError(
|
||||||
|
"error in error handler",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
let srv = test::status_service(StatusCode::BAD_REQUEST);
|
||||||
|
|
||||||
|
let mw = ErrorHandlers::new()
|
||||||
|
.handler(StatusCode::BAD_REQUEST, error_handler)
|
||||||
|
.new_transform(srv.into_service())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let err = mw
|
||||||
|
.call(TestRequest::default().to_srv_request())
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
let res = err.error_response();
|
||||||
|
|
||||||
|
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
assert_eq!(
|
||||||
|
body::to_bytes(res.into_body()).await.unwrap(),
|
||||||
|
"error in error handler"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -407,12 +407,10 @@ impl Drop for HttpRequest {
|
|||||||
/// format!("Got thing: {:?}", req)
|
/// format!("Got thing: {:?}", req)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// let app = App::new().service(
|
||||||
/// let app = App::new().service(
|
/// web::resource("/users/{first}").route(
|
||||||
/// web::resource("/users/{first}").route(
|
/// web::get().to(index))
|
||||||
/// web::get().to(index))
|
/// );
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
impl FromRequest for HttpRequest {
|
impl FromRequest for HttpRequest {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
@ -93,19 +93,17 @@ where
|
|||||||
/// "Welcome!"
|
/// "Welcome!"
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// let app = App::new()
|
||||||
/// let app = App::new()
|
/// .service(
|
||||||
/// .service(
|
/// web::resource("/app")
|
||||||
/// web::resource("/app")
|
/// .guard(guard::Header("content-type", "text/plain"))
|
||||||
/// .guard(guard::Header("content-type", "text/plain"))
|
/// .route(web::get().to(index))
|
||||||
/// .route(web::get().to(index))
|
/// )
|
||||||
/// )
|
/// .service(
|
||||||
/// .service(
|
/// web::resource("/app")
|
||||||
/// web::resource("/app")
|
/// .guard(guard::Header("content-type", "text/json"))
|
||||||
/// .guard(guard::Header("content-type", "text/json"))
|
/// .route(web::get().to(|| HttpResponse::MethodNotAllowed()))
|
||||||
/// .route(web::get().to(|| HttpResponse::MethodNotAllowed()))
|
/// );
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
|
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
|
||||||
self.guards.push(Box::new(guard));
|
self.guards.push(Box::new(guard));
|
||||||
@ -137,14 +135,13 @@ where
|
|||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::{web, guard, App};
|
/// use actix_web::{web, guard, App};
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// let app = App::new().service(
|
||||||
/// let app = App::new().service(
|
/// web::resource("/container/")
|
||||||
/// web::resource("/container/")
|
/// .route(web::get().to(get_handler))
|
||||||
/// .route(web::get().to(get_handler))
|
/// .route(web::post().to(post_handler))
|
||||||
/// .route(web::post().to(post_handler))
|
/// .route(web::delete().to(delete_handler))
|
||||||
/// .route(web::delete().to(delete_handler))
|
/// );
|
||||||
/// );
|
///
|
||||||
/// }
|
|
||||||
/// # async fn get_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() }
|
/// # async fn get_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() }
|
||||||
/// # async fn post_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() }
|
/// # async fn post_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() }
|
||||||
/// # async fn delete_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() }
|
/// # async fn delete_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() }
|
||||||
|
@ -7,14 +7,35 @@ use actix_http::{
|
|||||||
};
|
};
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
|
|
||||||
use crate::{Error, HttpRequest, HttpResponse};
|
|
||||||
|
|
||||||
use super::CustomizeResponder;
|
use super::CustomizeResponder;
|
||||||
|
use crate::{Error, HttpRequest, HttpResponse};
|
||||||
|
|
||||||
/// Trait implemented by types that can be converted to an HTTP response.
|
/// Trait implemented by types that can be converted to an HTTP response.
|
||||||
///
|
///
|
||||||
/// Any types that implement this trait can be used in the return type of a handler.
|
/// Any types that implement this trait can be used in the return type of a handler. Since handlers
|
||||||
// # TODO: more about implementation notes and foreign impls
|
/// will only have one return type, it is idiomatic to use opaque return types `-> impl Responder`.
|
||||||
|
///
|
||||||
|
/// # Implementations
|
||||||
|
/// It is often not required to implement `Responder` for your own types due to a broad base of
|
||||||
|
/// built-in implementations:
|
||||||
|
/// - `HttpResponse` and `HttpResponseBuilder`
|
||||||
|
/// - `Option<R>` where `R: Responder`
|
||||||
|
/// - `Result<R, E>` where `R: Responder` and [`E: ResponseError`](crate::ResponseError)
|
||||||
|
/// - `(R, StatusCode) where `R: Responder`
|
||||||
|
/// - `&'static str`, `String`, `&'_ String`, `Cow<'_, str>`, [`ByteString`](bytestring::ByteString)
|
||||||
|
/// - `&'static [u8]`, `Vec<u8>`, `Bytes`, `BytesMut`
|
||||||
|
/// - [`Json<T>`](crate::web::Json) and [`Form<T>`](crate::web::Form) where `T: Serialize`
|
||||||
|
/// - [`Either<L, R>`](crate::web::Either) where `L: Serialize` and `R: Serialize`
|
||||||
|
/// - [`CustomizeResponder<R>`]
|
||||||
|
/// - [`actix_files::NamedFile`](https://docs.rs/actix-files/latest/actix_files/struct.NamedFile.html)
|
||||||
|
/// - [Experimental responders from `actix-web-lab`](https://docs.rs/actix-web-lab/latest/actix_web_lab/respond/index.html)
|
||||||
|
/// - Third party integrations may also have implemented `Responder` where appropriate. For example,
|
||||||
|
/// HTML templating engines.
|
||||||
|
///
|
||||||
|
/// # Customizing Responder Output
|
||||||
|
/// Calling [`.customize()`](Responder::customize) on any responder type will wrap it in a
|
||||||
|
/// [`CustomizeResponder`] capable of overriding various parts of the response such as the status
|
||||||
|
/// code and header map.
|
||||||
pub trait Responder {
|
pub trait Responder {
|
||||||
type Body: MessageBody + 'static;
|
type Body: MessageBody + 'static;
|
||||||
|
|
||||||
@ -23,7 +44,7 @@ pub trait Responder {
|
|||||||
|
|
||||||
/// Wraps responder to allow alteration of its response.
|
/// Wraps responder to allow alteration of its response.
|
||||||
///
|
///
|
||||||
/// See [`CustomizeResponder`] docs for its capabilities.
|
/// See [`CustomizeResponder`] docs for more details on its capabilities.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
@ -84,11 +105,8 @@ impl Responder for actix_http::ResponseBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Responder for Option<T>
|
impl<R: Responder> Responder for Option<R> {
|
||||||
where
|
type Body = EitherBody<R::Body>;
|
||||||
T: Responder,
|
|
||||||
{
|
|
||||||
type Body = EitherBody<T::Body>;
|
|
||||||
|
|
||||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
match self {
|
match self {
|
||||||
@ -98,12 +116,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, E> Responder for Result<T, E>
|
impl<R, E> Responder for Result<R, E>
|
||||||
where
|
where
|
||||||
T: Responder,
|
R: Responder,
|
||||||
E: Into<Error>,
|
E: Into<Error>,
|
||||||
{
|
{
|
||||||
type Body = EitherBody<T::Body>;
|
type Body = EitherBody<R::Body>;
|
||||||
|
|
||||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
match self {
|
match self {
|
||||||
@ -113,8 +131,8 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Responder> Responder for (T, StatusCode) {
|
impl<R: Responder> Responder for (R, StatusCode) {
|
||||||
type Body = T::Body;
|
type Body = R::Body;
|
||||||
|
|
||||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
let mut res = self.0.respond_to(req);
|
let mut res = self.0.respond_to(req);
|
||||||
@ -147,6 +165,7 @@ impl_responder_by_forward_into_base_response!(BytesMut);
|
|||||||
|
|
||||||
impl_responder_by_forward_into_base_response!(&'static str);
|
impl_responder_by_forward_into_base_response!(&'static str);
|
||||||
impl_responder_by_forward_into_base_response!(String);
|
impl_responder_by_forward_into_base_response!(String);
|
||||||
|
impl_responder_by_forward_into_base_response!(bytestring::ByteString);
|
||||||
|
|
||||||
macro_rules! impl_into_string_responder {
|
macro_rules! impl_into_string_responder {
|
||||||
($res:ty) => {
|
($res:ty) => {
|
||||||
|
@ -323,12 +323,6 @@ impl From<Error> for HttpResponse {
|
|||||||
impl<B> From<HttpResponse<B>> for Response<B> {
|
impl<B> From<HttpResponse<B>> for Response<B> {
|
||||||
fn from(res: HttpResponse<B>) -> Self {
|
fn from(res: HttpResponse<B>) -> Self {
|
||||||
// this impl will always be called as part of dispatcher
|
// this impl will always be called as part of dispatcher
|
||||||
|
|
||||||
// TODO: expose cause somewhere?
|
|
||||||
// if let Some(err) = res.error {
|
|
||||||
// return Response::from_error(err);
|
|
||||||
// }
|
|
||||||
|
|
||||||
res.res
|
res.res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,7 +151,7 @@ impl ResourceMap {
|
|||||||
.char_indices()
|
.char_indices()
|
||||||
.filter_map(|(i, c)| (c == '/').then(|| i))
|
.filter_map(|(i, c)| (c == '/').then(|| i))
|
||||||
.nth(2)
|
.nth(2)
|
||||||
.unwrap_or_else(|| path.len());
|
.unwrap_or(path.len());
|
||||||
|
|
||||||
(
|
(
|
||||||
Cow::Borrowed(&path[..third_slash_index]),
|
Cow::Borrowed(&path[..third_slash_index]),
|
||||||
|
@ -78,18 +78,18 @@ pub struct ServiceRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceRequest {
|
impl ServiceRequest {
|
||||||
/// Construct service request
|
/// Construct `ServiceRequest` from parts.
|
||||||
pub(crate) fn new(req: HttpRequest, payload: Payload) -> Self {
|
pub(crate) fn new(req: HttpRequest, payload: Payload) -> Self {
|
||||||
Self { req, payload }
|
Self { req, payload }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deconstruct request into parts
|
/// Deconstruct `ServiceRequest` into inner parts.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn into_parts(self) -> (HttpRequest, Payload) {
|
pub fn into_parts(self) -> (HttpRequest, Payload) {
|
||||||
(self.req, self.payload)
|
(self.req, self.payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get mutable access to inner `HttpRequest` and `Payload`
|
/// Returns mutable accessors to inner parts.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn parts_mut(&mut self) -> (&mut HttpRequest, &mut Payload) {
|
pub fn parts_mut(&mut self) -> (&mut HttpRequest, &mut Payload) {
|
||||||
(&mut self.req, &mut self.payload)
|
(&mut self.req, &mut self.payload)
|
||||||
@ -105,9 +105,7 @@ impl ServiceRequest {
|
|||||||
Self { req, payload }
|
Self { req, payload }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct request from request.
|
/// Construct `ServiceRequest` with no payload from given `HttpRequest`.
|
||||||
///
|
|
||||||
/// The returned `ServiceRequest` would have no payload.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_request(req: HttpRequest) -> Self {
|
pub fn from_request(req: HttpRequest) -> Self {
|
||||||
ServiceRequest {
|
ServiceRequest {
|
||||||
@ -116,63 +114,63 @@ impl ServiceRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create service response
|
/// Create `ServiceResponse` from this request and given response.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn into_response<B, R: Into<Response<B>>>(self, res: R) -> ServiceResponse<B> {
|
pub fn into_response<B, R: Into<Response<B>>>(self, res: R) -> ServiceResponse<B> {
|
||||||
let res = HttpResponse::from(res.into());
|
let res = HttpResponse::from(res.into());
|
||||||
ServiceResponse::new(self.req, res)
|
ServiceResponse::new(self.req, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create service response for error
|
/// Create `ServiceResponse` from this request and given error.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn error_response<E: Into<Error>>(self, err: E) -> ServiceResponse {
|
pub fn error_response<E: Into<Error>>(self, err: E) -> ServiceResponse {
|
||||||
let res = HttpResponse::from_error(err.into());
|
let res = HttpResponse::from_error(err.into());
|
||||||
ServiceResponse::new(self.req, res)
|
ServiceResponse::new(self.req, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method returns reference to the request head
|
/// Returns a reference to the request head.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn head(&self) -> &RequestHead {
|
pub fn head(&self) -> &RequestHead {
|
||||||
self.req.head()
|
self.req.head()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method returns reference to the request head
|
/// Returns a mutable reference to the request head.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn head_mut(&mut self) -> &mut RequestHead {
|
pub fn head_mut(&mut self) -> &mut RequestHead {
|
||||||
self.req.head_mut()
|
self.req.head_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request's uri.
|
/// Returns the request URI.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn uri(&self) -> &Uri {
|
pub fn uri(&self) -> &Uri {
|
||||||
&self.head().uri
|
&self.head().uri
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the Request method.
|
/// Returns the request method.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn method(&self) -> &Method {
|
pub fn method(&self) -> &Method {
|
||||||
&self.head().method
|
&self.head().method
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the Request Version.
|
/// Returns the request version.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn version(&self) -> Version {
|
pub fn version(&self) -> Version {
|
||||||
self.head().version
|
self.head().version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to request headers.
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Returns request's headers.
|
|
||||||
pub fn headers(&self) -> &HeaderMap {
|
pub fn headers(&self) -> &HeaderMap {
|
||||||
&self.head().headers
|
&self.head().headers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to request headers.
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Returns mutable request's headers.
|
|
||||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||||
&mut self.head_mut().headers
|
&mut self.head_mut().headers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The target path of this Request.
|
/// Returns request path.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn path(&self) -> &str {
|
pub fn path(&self) -> &str {
|
||||||
self.head().uri.path()
|
self.head().uri.path()
|
||||||
@ -184,7 +182,7 @@ impl ServiceRequest {
|
|||||||
self.req.query_string()
|
self.req.query_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peer socket address.
|
/// Returns peer's socket address.
|
||||||
///
|
///
|
||||||
/// Peer address is the directly connected peer's socket address. If a proxy is used in front of
|
/// Peer address is the directly connected peer's socket address. If a proxy is used in front of
|
||||||
/// the Actix Web server, then it would be address of this proxy.
|
/// the Actix Web server, then it would be address of this proxy.
|
||||||
@ -197,24 +195,23 @@ impl ServiceRequest {
|
|||||||
self.head().peer_addr
|
self.head().peer_addr
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get *ConnectionInfo* for the current request.
|
/// Returns a reference to connection info.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> {
|
pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> {
|
||||||
self.req.connection_info()
|
self.req.connection_info()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the Path parameters.
|
/// Returns reference to the Path parameters.
|
||||||
///
|
///
|
||||||
/// Params is a container for URL parameters.
|
/// Params is a container for URL parameters. A variable segment is specified in the form
|
||||||
/// A variable segment is specified in the form `{identifier}`,
|
/// `{identifier}`, where the identifier can be used later in a request handler to access the
|
||||||
/// where the identifier can be used later in a request handler to
|
/// matched value for that segment.
|
||||||
/// access the matched value for that segment.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn match_info(&self) -> &Path<Url> {
|
pub fn match_info(&self) -> &Path<Url> {
|
||||||
self.req.match_info()
|
self.req.match_info()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable reference to the Path parameters.
|
/// Returns a mutable reference to the path match information.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn match_info_mut(&mut self) -> &mut Path<Url> {
|
pub fn match_info_mut(&mut self) -> &mut Path<Url> {
|
||||||
self.req.match_info_mut()
|
self.req.match_info_mut()
|
||||||
@ -232,13 +229,13 @@ impl ServiceRequest {
|
|||||||
self.req.match_pattern()
|
self.req.match_pattern()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to a `ResourceMap` of current application.
|
/// Returns a reference to the application's resource map.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn resource_map(&self) -> &ResourceMap {
|
pub fn resource_map(&self) -> &ResourceMap {
|
||||||
self.req.resource_map()
|
self.req.resource_map()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Service configuration
|
/// Returns a reference to the application's configuration.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn app_config(&self) -> &AppConfig {
|
pub fn app_config(&self) -> &AppConfig {
|
||||||
self.req.app_config()
|
self.req.app_config()
|
||||||
@ -262,6 +259,7 @@ impl ServiceRequest {
|
|||||||
self.req.conn_data()
|
self.req.conn_data()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return request cookies.
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
|
pub fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
//!
|
//!
|
||||||
//! # Off-The-Shelf Test Services
|
//! # Off-The-Shelf Test Services
|
||||||
//! - [`ok_service`]
|
//! - [`ok_service`]
|
||||||
//! - [`simple_service`]
|
//! - [`status_service`]
|
||||||
//!
|
//!
|
||||||
//! # Calling Test Service
|
//! # Calling Test Service
|
||||||
//! - [`TestRequest`]
|
//! - [`TestRequest`]
|
||||||
@ -27,7 +27,7 @@ mod test_utils;
|
|||||||
|
|
||||||
pub use self::test_request::TestRequest;
|
pub use self::test_request::TestRequest;
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
pub use self::test_services::{default_service, ok_service, simple_service};
|
pub use self::test_services::{default_service, ok_service, simple_service, status_service};
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
pub use self::test_utils::{
|
pub use self::test_utils::{
|
||||||
call_and_read_body, call_and_read_body_json, call_service, init_service, read_body,
|
call_and_read_body, call_and_read_body_json, call_service, init_service, read_body,
|
||||||
|
@ -24,10 +24,10 @@ use crate::cookie::{Cookie, CookieJar};
|
|||||||
///
|
///
|
||||||
/// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern.
|
/// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern.
|
||||||
/// You can generate various types of request via TestRequest's methods:
|
/// You can generate various types of request via TestRequest's methods:
|
||||||
/// * `TestRequest::to_request` creates `actix_http::Request` instance.
|
/// - [`TestRequest::to_request`] creates an [`actix_http::Request`](Request).
|
||||||
/// * `TestRequest::to_srv_request` creates `ServiceRequest` instance, which is used for testing middlewares and chain adapters.
|
/// - [`TestRequest::to_srv_request`] creates a [`ServiceRequest`], which is used for testing middlewares and chain adapters.
|
||||||
/// * `TestRequest::to_srv_response` creates `ServiceResponse` instance.
|
/// - [`TestRequest::to_srv_response`] creates a [`ServiceResponse`].
|
||||||
/// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers.
|
/// - [`TestRequest::to_http_request`] creates an [`HttpRequest`], which is used for testing handlers.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage};
|
/// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage};
|
||||||
@ -42,15 +42,17 @@ use crate::cookie::{Cookie, CookieJar};
|
|||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// #[actix_web::test]
|
/// #[actix_web::test]
|
||||||
|
/// # // force rustdoc to display the correct thing and also compile check the test
|
||||||
|
/// # async fn _test() {}
|
||||||
/// async fn test_index() {
|
/// async fn test_index() {
|
||||||
/// let req = test::TestRequest::default().insert_header("content-type", "text/plain")
|
/// let req = test::TestRequest::default().insert_header(header::ContentType::plaintext())
|
||||||
/// .to_http_request();
|
/// .to_http_request();
|
||||||
///
|
///
|
||||||
/// let resp = index(req).await.unwrap();
|
/// let resp = index(req).await;
|
||||||
/// assert_eq!(resp.status(), StatusCode::OK);
|
/// assert_eq!(resp.status(), StatusCode::OK);
|
||||||
///
|
///
|
||||||
/// let req = test::TestRequest::default().to_http_request();
|
/// let req = test::TestRequest::default().to_http_request();
|
||||||
/// let resp = index(req).await.unwrap();
|
/// let resp = index(req).await;
|
||||||
/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -10,11 +10,11 @@ use crate::{
|
|||||||
/// Creates service that always responds with `200 OK` and no body.
|
/// Creates service that always responds with `200 OK` and no body.
|
||||||
pub fn ok_service(
|
pub fn ok_service(
|
||||||
) -> impl Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error> {
|
) -> impl Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error> {
|
||||||
simple_service(StatusCode::OK)
|
status_service(StatusCode::OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates service that always responds with given status code and no body.
|
/// Creates service that always responds with given status code and no body.
|
||||||
pub fn simple_service(
|
pub fn status_service(
|
||||||
status_code: StatusCode,
|
status_code: StatusCode,
|
||||||
) -> impl Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error> {
|
) -> impl Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error> {
|
||||||
fn_service(move |req: ServiceRequest| {
|
fn_service(move |req: ServiceRequest| {
|
||||||
@ -23,9 +23,17 @@ pub fn simple_service(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[deprecated(since = "4.0.0", note = "Renamed to `simple_service`.")]
|
#[deprecated(since = "4.0.0", note = "Renamed to `status_service`.")]
|
||||||
|
pub fn simple_service(
|
||||||
|
status_code: StatusCode,
|
||||||
|
) -> impl Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error> {
|
||||||
|
status_service(status_code)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[deprecated(since = "4.0.0", note = "Renamed to `status_service`.")]
|
||||||
pub fn default_service(
|
pub fn default_service(
|
||||||
status_code: StatusCode,
|
status_code: StatusCode,
|
||||||
) -> impl Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error> {
|
) -> impl Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error> {
|
||||||
simple_service(status_code)
|
status_service(status_code)
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ where
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// Panics if service call returns error.
|
/// Panics if service call returns error. To handle errors use `app.call(req)`.
|
||||||
pub async fn call_service<S, R, B, E>(app: &S, req: R) -> S::Response
|
pub async fn call_service<S, R, B, E>(app: &S, req: R) -> S::Response
|
||||||
where
|
where
|
||||||
S: Service<R, Response = ServiceResponse<B>, Error = E>,
|
S: Service<R, Response = ServiceResponse<B>, Error = E>,
|
||||||
|
@ -1,4 +1,18 @@
|
|||||||
//! Essentials helper functions and types for application registration.
|
//! Essentials helper functions and types for application registration.
|
||||||
|
//!
|
||||||
|
//! # Request Extractors
|
||||||
|
//! - [`Data`]: Application data item
|
||||||
|
//! - [`ReqData`]: Request-local data item
|
||||||
|
//! - [`Path`]: URL path parameters / dynamic segments
|
||||||
|
//! - [`Query`]: URL query parameters
|
||||||
|
//! - [`Header`]: Typed header
|
||||||
|
//! - [`Json`]: JSON payload
|
||||||
|
//! - [`Form`]: URL-encoded payload
|
||||||
|
//! - [`Bytes`]: Raw payload
|
||||||
|
//!
|
||||||
|
//! # Responders
|
||||||
|
//! - [`Json`]: JSON request payload
|
||||||
|
//! - [`Bytes`]: Raw request payload
|
||||||
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
@ -12,9 +26,7 @@ use crate::{
|
|||||||
|
|
||||||
pub use crate::config::ServiceConfig;
|
pub use crate::config::ServiceConfig;
|
||||||
pub use crate::data::Data;
|
pub use crate::data::Data;
|
||||||
pub use crate::request::HttpRequest;
|
|
||||||
pub use crate::request_data::ReqData;
|
pub use crate::request_data::ReqData;
|
||||||
pub use crate::response::HttpResponse;
|
|
||||||
pub use crate::types::*;
|
pub use crate::types::*;
|
||||||
|
|
||||||
/// Creates a new resource for a specific path.
|
/// Creates a new resource for a specific path.
|
||||||
|
@ -3,6 +3,103 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0 - 2022-03-07
|
||||||
|
### Dependencies
|
||||||
|
- Updated `actix-*` to Tokio v1-based versions. [#1813]
|
||||||
|
- Updated `bytes` to `1.0`. [#1813]
|
||||||
|
- Updated `cookie` to `0.16`. [#2555]
|
||||||
|
- Updated `rand` to `0.8`.
|
||||||
|
- Updated `rustls` to `0.20`. [#2414]
|
||||||
|
- Updated `tokio` to `1`.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `trust-dns` crate feature to enable `trust-dns-resolver` as client DNS resolver; disabled by default. [#1969]
|
||||||
|
- `cookies` crate feature; enabled by default. [#2619]
|
||||||
|
- `compress-brotli` crate feature; enabled by default. [#2250]
|
||||||
|
- `compress-gzip` crate feature; enabled by default. [#2250]
|
||||||
|
- `compress-zstd` crate feature; enabled by default. [#2250]
|
||||||
|
- `client::Connector::handshake_timeout()` for customizing TLS connection handshake timeout. [#2081]
|
||||||
|
- `client::ConnectorService` as `client::Connector::finish` method's return type [#2081]
|
||||||
|
- `client::ConnectionIo` trait alias [#2081]
|
||||||
|
- `Client::headers()` to get default mut reference of `HeaderMap` of client object. [#2114]
|
||||||
|
- `ClientResponse::timeout()` for set the timeout of collecting response body. [#1931]
|
||||||
|
- `ClientBuilder::local_address()` for binding to a local IP address for this client. [#2024]
|
||||||
|
- `ClientRequest::insert_header()` method which allows using typed and untyped headers. [#1869]
|
||||||
|
- `ClientRequest::append_header()` method which allows using typed and untyped headers. [#1869]
|
||||||
|
- `ClientBuilder::add_default_header()` (and deprecate `ClientBuilder::header()`). [#2510]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `client::Connector` type now only has one generic type for `actix_service::Service`. [#2063]
|
||||||
|
- `client::error::ConnectError` Resolver variant contains `Box<dyn std::error::Error>` type. [#1905]
|
||||||
|
- `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905]
|
||||||
|
- `ConnectorService` type is renamed to `BoxConnectorService`. [#2081]
|
||||||
|
- Fix http/https encoding when enabling `compress` feature. [#2116]
|
||||||
|
- Rename `TestResponse::{header => append_header, set => insert_header}`. These methods now take a `TryIntoHeaderPair`. [#2094]
|
||||||
|
- `ClientBuilder::connector()` method now takes `Connector<T, U>` type. [#2008]
|
||||||
|
- Basic auth now accepts blank passwords as an empty string instead of an `Option`. [#2050]
|
||||||
|
- Relax default timeout for `Connector` to 5 seconds (up from 1 second). [#1905]
|
||||||
|
- `*::send_json()` and `*::send_form()` methods now receive `impl Serialize`. [#2553]
|
||||||
|
- `FrozenClientRequest::extra_header()` now uses receives an `impl TryIntoHeaderPair`. [#2553]
|
||||||
|
- Rename `Connector::{ssl => openssl}()`. [#2503]
|
||||||
|
- `ClientRequest::send_body` now takes an `impl MessageBody`. [#2546]
|
||||||
|
- Rename `MessageBody => ResponseBody` to avoid conflicts with `MessageBody` trait. [#2546]
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Send headers along with redirected requests. [#2310]
|
||||||
|
- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503]
|
||||||
|
- Remove unnecessary `Unpin` bounds on `*::send_stream`. [#2553]
|
||||||
|
- `impl Future` for `ResponseBody` no longer requires the body type be `Unpin`. [#2546]
|
||||||
|
- `impl Future` for `JsonBody` no longer requires the body type be `Unpin`. [#2546]
|
||||||
|
- `impl Stream` for `ClientResponse` no longer requires the body type be `Unpin`. [#2546]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- `compress` crate feature. [#2250]
|
||||||
|
- `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869]
|
||||||
|
- `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869]
|
||||||
|
- `ClientRequest::set_header_if_none`; use `ClientRequest::insert_header_if_none`. [#1869]
|
||||||
|
- `ClientRequest::header`; use `ClientRequest::append_header`. [#1869]
|
||||||
|
- Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148]
|
||||||
|
- `ClientBuilder::default` function [#2008]
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- `cookie` upgrade addresses [`RUSTSEC-2020-0071`].
|
||||||
|
|
||||||
|
[`RUSTSEC-2020-0071`]: https://rustsec.org/advisories/RUSTSEC-2020-0071.html
|
||||||
|
|
||||||
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
|
[#1869]: https://github.com/actix/actix-web/pull/1869
|
||||||
|
[#1905]: https://github.com/actix/actix-web/pull/1905
|
||||||
|
[#1905]: https://github.com/actix/actix-web/pull/1905
|
||||||
|
[#1931]: https://github.com/actix/actix-web/pull/1931
|
||||||
|
[#1969]: https://github.com/actix/actix-web/pull/1969
|
||||||
|
[#1969]: https://github.com/actix/actix-web/pull/1969
|
||||||
|
[#1981]: https://github.com/actix/actix-web/pull/1981
|
||||||
|
[#2008]: https://github.com/actix/actix-web/pull/2008
|
||||||
|
[#2024]: https://github.com/actix/actix-web/pull/2024
|
||||||
|
[#2050]: https://github.com/actix/actix-web/pull/2050
|
||||||
|
[#2063]: https://github.com/actix/actix-web/pull/2063
|
||||||
|
[#2081]: https://github.com/actix/actix-web/pull/2081
|
||||||
|
[#2081]: https://github.com/actix/actix-web/pull/2081
|
||||||
|
[#2094]: https://github.com/actix/actix-web/pull/2094
|
||||||
|
[#2114]: https://github.com/actix/actix-web/pull/2114
|
||||||
|
[#2116]: https://github.com/actix/actix-web/pull/2116
|
||||||
|
[#2148]: https://github.com/actix/actix-web/pull/2148
|
||||||
|
[#2250]: https://github.com/actix/actix-web/pull/2250
|
||||||
|
[#2310]: https://github.com/actix/actix-web/pull/2310
|
||||||
|
[#2414]: https://github.com/actix/actix-web/pull/2414
|
||||||
|
[#2425]: https://github.com/actix/actix-web/pull/2425
|
||||||
|
[#2474]: https://github.com/actix/actix-web/pull/2474
|
||||||
|
[#2503]: https://github.com/actix/actix-web/pull/2503
|
||||||
|
[#2510]: https://github.com/actix/actix-web/pull/2510
|
||||||
|
[#2546]: https://github.com/actix/actix-web/pull/2546
|
||||||
|
[#2553]: https://github.com/actix/actix-web/pull/2553
|
||||||
|
[#2555]: https://github.com/actix/actix-web/pull/2555
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>3.0.0 Pre-Releases</summary>
|
||||||
|
|
||||||
## 3.0.0-beta.21 - 2022-02-16
|
## 3.0.0-beta.21 - 2022-02-16
|
||||||
- No significant changes since `3.0.0-beta.20`.
|
- No significant changes since `3.0.0-beta.20`.
|
||||||
|
|
||||||
@ -170,6 +267,7 @@
|
|||||||
|
|
||||||
[#1813]: https://github.com/actix/actix-web/pull/1813
|
[#1813]: https://github.com/actix/actix-web/pull/1813
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## 2.0.3 - 2020-11-29
|
## 2.0.3 - 2020-11-29
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "awc"
|
name = "awc"
|
||||||
version = "3.0.0-beta.21"
|
version = "3.0.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"fakeshadow <24548779@qq.com>",
|
"fakeshadow <24548779@qq.com>",
|
||||||
]
|
]
|
||||||
description = "Async HTTP and WebSocket client library built on the Actix ecosystem"
|
description = "Async HTTP and WebSocket client library"
|
||||||
keywords = ["actix", "http", "framework", "async", "web"]
|
keywords = ["actix", "http", "framework", "async", "web"]
|
||||||
categories = [
|
categories = [
|
||||||
"network-programming",
|
"network-programming",
|
||||||
@ -59,11 +59,11 @@ dangerous-h2c = []
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.5"
|
actix-codec = "0.5"
|
||||||
actix-service = "2.0.0"
|
actix-service = "2"
|
||||||
actix-http = { version = "3.0.0-rc.3", features = ["http2", "ws"] }
|
actix-http = { version = "3", features = ["http2", "ws"] }
|
||||||
actix-rt = { version = "2.1", default-features = false }
|
actix-rt = { version = "2.1", default-features = false }
|
||||||
actix-tls = { version = "3", features = ["connect", "uri"] }
|
actix-tls = { version = "3", features = ["connect", "uri"] }
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3"
|
||||||
|
|
||||||
ahash = "0.7"
|
ahash = "0.7"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
@ -93,13 +93,13 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features
|
|||||||
trust-dns-resolver = { version = "0.20.0", optional = true }
|
trust-dns-resolver = { version = "0.20.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-http = { version = "3.0.0-rc.3", features = ["openssl"] }
|
actix-http = { version = "3", features = ["openssl"] }
|
||||||
actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] }
|
actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] }
|
||||||
actix-server = "2"
|
actix-server = "2"
|
||||||
actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] }
|
actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] }
|
||||||
actix-tls = { version = "3", features = ["openssl", "rustls"] }
|
actix-tls = { version = "3", features = ["openssl", "rustls"] }
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3"
|
||||||
actix-web = { version = "4.0.0-rc.3", features = ["openssl"] }
|
actix-web = { version = "4", features = ["openssl"] }
|
||||||
|
|
||||||
brotli = "3.3.3"
|
brotli = "3.3.3"
|
||||||
const-str = "0.3"
|
const-str = "0.3"
|
||||||
|
@ -3,15 +3,15 @@
|
|||||||
> Async HTTP and WebSocket client library.
|
> Async HTTP and WebSocket client library.
|
||||||
|
|
||||||
[](https://crates.io/crates/awc)
|
[](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)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
## Documentation & Resources
|
## Documentation & Resources
|
||||||
|
|
||||||
- [API Documentation](https://docs.rs/awc)
|
- [API Documentation](https://docs.rs/awc)
|
||||||
- [Example Project](https://github.com/actix/examples/tree/HEAD/security/awc_https)
|
- [Example Project](https://github.com/actix/examples/tree/master/https-tls/awc-https)
|
||||||
- Minimum Supported Rust Version (MSRV): 1.54
|
- Minimum Supported Rust Version (MSRV): 1.54
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
@ -246,7 +246,12 @@ where
|
|||||||
///
|
///
|
||||||
/// The default limit size is 100.
|
/// The default limit size is 100.
|
||||||
pub fn limit(mut self, limit: usize) -> Self {
|
pub fn limit(mut self, limit: usize) -> Self {
|
||||||
self.config.limit = limit;
|
if limit == 0 {
|
||||||
|
self.config.limit = u32::MAX as usize;
|
||||||
|
} else {
|
||||||
|
self.config.limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,12 +83,12 @@ where
|
|||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
framed.send((head, body.size()).into()).await?;
|
|
||||||
|
|
||||||
let mut pin_framed = Pin::new(&mut framed);
|
let mut pin_framed = Pin::new(&mut framed);
|
||||||
|
|
||||||
// special handle for EXPECT request.
|
// special handle for EXPECT request.
|
||||||
let (do_send, mut res_head) = if is_expect {
|
let (do_send, mut res_head) = if is_expect {
|
||||||
|
pin_framed.send((head, body.size()).into()).await?;
|
||||||
|
|
||||||
let head = poll_fn(|cx| pin_framed.as_mut().poll_next(cx))
|
let head = poll_fn(|cx| pin_framed.as_mut().poll_next(cx))
|
||||||
.await
|
.await
|
||||||
.ok_or(ConnectError::Disconnected)??;
|
.ok_or(ConnectError::Disconnected)??;
|
||||||
@ -97,13 +97,17 @@ where
|
|||||||
// and current head would be used as final response head.
|
// and current head would be used as final response head.
|
||||||
(head.status == StatusCode::CONTINUE, Some(head))
|
(head.status == StatusCode::CONTINUE, Some(head))
|
||||||
} else {
|
} else {
|
||||||
|
pin_framed.feed((head, body.size()).into()).await?;
|
||||||
|
|
||||||
(true, None)
|
(true, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
if do_send {
|
if do_send {
|
||||||
// send request body
|
// send request body
|
||||||
match body.size() {
|
match body.size() {
|
||||||
BodySize::None | BodySize::Sized(0) => {}
|
BodySize::None | BodySize::Sized(0) => {
|
||||||
|
poll_fn(|cx| pin_framed.as_mut().flush(cx)).await?;
|
||||||
|
}
|
||||||
_ => send_body(body, pin_framed.as_mut()).await?,
|
_ => send_body(body, pin_framed.as_mut()).await?,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -232,7 +232,7 @@ where
|
|||||||
None => {
|
None => {
|
||||||
let (io, proto) = connector.call(req).await?;
|
let (io, proto) = connector.call(req).await?;
|
||||||
|
|
||||||
// TODO: remove when http3 is added in support.
|
// NOTE: remove when http3 is added in support.
|
||||||
assert!(proto != Protocol::Http3);
|
assert!(proto != Protocol::Http3);
|
||||||
|
|
||||||
if proto == Protocol::Http1 {
|
if proto == Protocol::Http1 {
|
||||||
|
@ -30,17 +30,35 @@ pub type BoxConnectorService = Rc<
|
|||||||
|
|
||||||
pub type BoxedSocket = Box<dyn ConnectionIo>;
|
pub type BoxedSocket = Box<dyn ConnectionIo>;
|
||||||
|
|
||||||
|
/// Combined HTTP and WebSocket request type received by connection service.
|
||||||
pub enum ConnectRequest {
|
pub enum ConnectRequest {
|
||||||
|
/// Standard HTTP request.
|
||||||
|
///
|
||||||
|
/// Contains the request head, body type, and optional pre-resolved socket address.
|
||||||
Client(RequestHeadType, AnyBody, Option<net::SocketAddr>),
|
Client(RequestHeadType, AnyBody, Option<net::SocketAddr>),
|
||||||
|
|
||||||
|
/// Tunnel used by WebSocket connection requests.
|
||||||
|
///
|
||||||
|
/// Contains the request head and optional pre-resolved socket address.
|
||||||
Tunnel(RequestHead, Option<net::SocketAddr>),
|
Tunnel(RequestHead, Option<net::SocketAddr>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Combined HTTP response & WebSocket tunnel type returned from connection service.
|
||||||
pub enum ConnectResponse {
|
pub enum ConnectResponse {
|
||||||
|
/// Standard HTTP response.
|
||||||
Client(ClientResponse),
|
Client(ClientResponse),
|
||||||
|
|
||||||
|
/// Tunnel used for WebSocket communication.
|
||||||
|
///
|
||||||
|
/// Contains response head and framed HTTP/1.1 codec.
|
||||||
Tunnel(ResponseHead, Framed<BoxedSocket, ClientCodec>),
|
Tunnel(ResponseHead, Framed<BoxedSocket, ClientCodec>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConnectResponse {
|
impl ConnectResponse {
|
||||||
|
/// Unwraps type into HTTP response.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if enum variant is not `Client`.
|
||||||
pub fn into_client_response(self) -> ClientResponse {
|
pub fn into_client_response(self) -> ClientResponse {
|
||||||
match self {
|
match self {
|
||||||
ConnectResponse::Client(res) => res,
|
ConnectResponse::Client(res) => res,
|
||||||
@ -50,6 +68,10 @@ impl ConnectResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Unwraps type into WebSocket tunnel response.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if enum variant is not `Tunnel`.
|
||||||
pub fn into_tunnel_response(self) -> (ResponseHead, Framed<BoxedSocket, ClientCodec>) {
|
pub fn into_tunnel_response(self) -> (ResponseHead, Framed<BoxedSocket, ClientCodec>) {
|
||||||
match self {
|
match self {
|
||||||
ConnectResponse::Tunnel(head, framed) => (head, framed),
|
ConnectResponse::Tunnel(head, framed) => (head, framed),
|
||||||
@ -136,30 +158,37 @@ where
|
|||||||
ConnectRequestProj::Connection { fut, req } => {
|
ConnectRequestProj::Connection { fut, req } => {
|
||||||
let connection = ready!(fut.poll(cx))?;
|
let connection = ready!(fut.poll(cx))?;
|
||||||
let req = req.take().unwrap();
|
let req = req.take().unwrap();
|
||||||
|
|
||||||
match req {
|
match req {
|
||||||
ConnectRequest::Client(head, body, ..) => {
|
ConnectRequest::Client(head, body, ..) => {
|
||||||
// send request
|
// send request
|
||||||
let fut = ConnectRequestFuture::Client {
|
let fut = ConnectRequestFuture::Client {
|
||||||
fut: connection.send_request(head, body),
|
fut: connection.send_request(head, body),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.set(fut);
|
self.set(fut);
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectRequest::Tunnel(head, ..) => {
|
ConnectRequest::Tunnel(head, ..) => {
|
||||||
// send request
|
// send request
|
||||||
let fut = ConnectRequestFuture::Tunnel {
|
let fut = ConnectRequestFuture::Tunnel {
|
||||||
fut: connection.open_tunnel(RequestHeadType::from(head)),
|
fut: connection.open_tunnel(RequestHeadType::from(head)),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.set(fut);
|
self.set(fut);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.poll(cx)
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectRequestProj::Client { fut } => {
|
ConnectRequestProj::Client { fut } => {
|
||||||
let (head, payload) = ready!(fut.as_mut().poll(cx))?;
|
let (head, payload) = ready!(fut.as_mut().poll(cx))?;
|
||||||
Poll::Ready(Ok(ConnectResponse::Client(ClientResponse::new(
|
Poll::Ready(Ok(ConnectResponse::Client(ClientResponse::new(
|
||||||
head, payload,
|
head, payload,
|
||||||
))))
|
))))
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectRequestProj::Tunnel { fut } => {
|
ConnectRequestProj::Tunnel { fut } => {
|
||||||
let (head, framed) = ready!(fut.as_mut().poll(cx))?;
|
let (head, framed) = ready!(fut.as_mut().poll(cx))?;
|
||||||
let framed = framed.into_map_io(|io| Box::new(io) as _);
|
let framed = framed.into_map_io(|io| Box::new(io) as _);
|
||||||
|
@ -1,22 +1,25 @@
|
|||||||
//! `awc` is a HTTP and WebSocket client library built on the Actix ecosystem.
|
//! `awc` is an asynchronous HTTP and WebSocket client library.
|
||||||
//!
|
//!
|
||||||
//! # Making a GET request
|
//! # `GET` Requests
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! # #[actix_rt::main]
|
//! # #[actix_rt::main]
|
||||||
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
||||||
|
//! // create client
|
||||||
//! let mut client = awc::Client::default();
|
//! let mut client = awc::Client::default();
|
||||||
//! let response = client.get("http://www.rust-lang.org") // <- Create request builder
|
|
||||||
//! .insert_header(("User-Agent", "Actix-web"))
|
|
||||||
//! .send() // <- Send http request
|
|
||||||
//! .await?;
|
|
||||||
//!
|
//!
|
||||||
//! println!("Response: {:?}", response);
|
//! // construct request
|
||||||
|
//! let req = client.get("http://www.rust-lang.org")
|
||||||
|
//! .insert_header(("User-Agent", "awc/3.0"));
|
||||||
|
//!
|
||||||
|
//! // send request and await response
|
||||||
|
//! let res = req.send().await?;
|
||||||
|
//! println!("Response: {:?}", res);
|
||||||
//! # Ok(())
|
//! # Ok(())
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! # Making POST requests
|
//! # `POST` Requests
|
||||||
//! ## Raw body contents
|
//! ## Raw Body
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! # #[actix_rt::main]
|
//! # #[actix_rt::main]
|
||||||
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
||||||
@ -28,20 +31,6 @@
|
|||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Forms
|
|
||||||
//! ```no_run
|
|
||||||
//! # #[actix_rt::main]
|
|
||||||
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
|
||||||
//! let params = [("foo", "bar"), ("baz", "quux")];
|
|
||||||
//!
|
|
||||||
//! let mut client = awc::Client::default();
|
|
||||||
//! let response = client.post("http://httpbin.org/post")
|
|
||||||
//! .send_form(¶ms)
|
|
||||||
//! .await?;
|
|
||||||
//! # Ok(())
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! ## JSON
|
//! ## JSON
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! # #[actix_rt::main]
|
//! # #[actix_rt::main]
|
||||||
@ -59,6 +48,20 @@
|
|||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! ## URL Encoded Form
|
||||||
|
//! ```no_run
|
||||||
|
//! # #[actix_rt::main]
|
||||||
|
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
||||||
|
//! let params = [("foo", "bar"), ("baz", "quux")];
|
||||||
|
//!
|
||||||
|
//! let mut client = awc::Client::default();
|
||||||
|
//! let response = client.post("http://httpbin.org/post")
|
||||||
|
//! .send_form(¶ms)
|
||||||
|
//! .await?;
|
||||||
|
//! # Ok(())
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
//! # Response Compression
|
//! # Response Compression
|
||||||
//! All [official][iana-encodings] and common content encoding codecs are supported, optionally.
|
//! All [official][iana-encodings] and common content encoding codecs are supported, optionally.
|
||||||
//!
|
//!
|
||||||
@ -76,11 +79,12 @@
|
|||||||
//!
|
//!
|
||||||
//! [iana-encodings]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding
|
//! [iana-encodings]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding
|
||||||
//!
|
//!
|
||||||
//! # WebSocket support
|
//! # WebSockets
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! # #[actix_rt::main]
|
//! # #[actix_rt::main]
|
||||||
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! use futures_util::{sink::SinkExt, stream::StreamExt};
|
//! use futures_util::{sink::SinkExt as _, stream::StreamExt as _};
|
||||||
|
//!
|
||||||
//! let (_resp, mut connection) = awc::Client::new()
|
//! let (_resp, mut connection) = awc::Client::new()
|
||||||
//! .ws("ws://echo.websocket.org")
|
//! .ws("ws://echo.websocket.org")
|
||||||
//! .connect()
|
//! .connect()
|
||||||
@ -89,8 +93,9 @@
|
|||||||
//! connection
|
//! connection
|
||||||
//! .send(awc::ws::Message::Text("Echo".into()))
|
//! .send(awc::ws::Message::Text("Echo".into()))
|
||||||
//! .await?;
|
//! .await?;
|
||||||
|
//!
|
||||||
//! let response = connection.next().await.unwrap()?;
|
//! let response = connection.next().await.unwrap()?;
|
||||||
//! # assert_eq!(response, awc::ws::Frame::Text("Echo".as_bytes().into()));
|
//! assert_eq!(response, awc::ws::Frame::Text("Echo".into()));
|
||||||
//! # Ok(())
|
//! # Ok(())
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
@ -161,7 +161,8 @@ where
|
|||||||
| StatusCode::SEE_OTHER
|
| StatusCode::SEE_OTHER
|
||||||
| StatusCode::TEMPORARY_REDIRECT
|
| StatusCode::TEMPORARY_REDIRECT
|
||||||
| StatusCode::PERMANENT_REDIRECT
|
| StatusCode::PERMANENT_REDIRECT
|
||||||
if *max_redirect_times > 0 =>
|
if *max_redirect_times > 0
|
||||||
|
&& res.headers().contains_key(header::LOCATION) =>
|
||||||
{
|
{
|
||||||
let reuse_body = res.head().status == StatusCode::TEMPORARY_REDIRECT
|
let reuse_body = res.head().status == StatusCode::TEMPORARY_REDIRECT
|
||||||
|| res.head().status == StatusCode::PERMANENT_REDIRECT;
|
|| res.head().status == StatusCode::PERMANENT_REDIRECT;
|
||||||
@ -245,26 +246,32 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_next_uri(res: &ClientResponse, prev_uri: &Uri) -> Result<Uri, SendRequestError> {
|
fn build_next_uri(res: &ClientResponse, prev_uri: &Uri) -> Result<Uri, SendRequestError> {
|
||||||
let uri = res
|
// responses without this header are not processed
|
||||||
.headers()
|
let location = res.headers().get(header::LOCATION).unwrap();
|
||||||
.get(header::LOCATION)
|
|
||||||
.map(|value| {
|
// try to parse the location and resolve to a full URI but fall back to default if it fails
|
||||||
// try to parse the location to a full uri
|
let uri = Uri::try_from(location.as_bytes()).unwrap_or_else(|_| Uri::default());
|
||||||
let uri = Uri::try_from(value.as_bytes())
|
|
||||||
.map_err(|e| SendRequestError::Url(InvalidUrl::HttpError(e.into())))?;
|
let uri = if uri.scheme().is_none() || uri.authority().is_none() {
|
||||||
if uri.scheme().is_none() || uri.authority().is_none() {
|
let builder = Uri::builder()
|
||||||
let uri = Uri::builder()
|
.scheme(prev_uri.scheme().cloned().unwrap())
|
||||||
.scheme(prev_uri.scheme().cloned().unwrap())
|
.authority(prev_uri.authority().cloned().unwrap());
|
||||||
.authority(prev_uri.authority().cloned().unwrap())
|
|
||||||
.path_and_query(value.as_bytes())
|
// when scheme or authority is missing treat the location value as path and query
|
||||||
.build()?;
|
// recover error where location does not have leading slash
|
||||||
Ok::<_, SendRequestError>(uri)
|
let path = if location.as_bytes().starts_with(b"/") {
|
||||||
} else {
|
location.as_bytes().to_owned()
|
||||||
Ok(uri)
|
} else {
|
||||||
}
|
[b"/", location.as_bytes()].concat()
|
||||||
})
|
};
|
||||||
// TODO: this error type is wrong.
|
|
||||||
.ok_or(SendRequestError::Url(InvalidUrl::MissingScheme))??;
|
builder
|
||||||
|
.path_and_query(path)
|
||||||
|
.build()
|
||||||
|
.map_err(|err| SendRequestError::Url(InvalidUrl::HttpError(err)))?
|
||||||
|
} else {
|
||||||
|
uri
|
||||||
|
};
|
||||||
|
|
||||||
Ok(uri)
|
Ok(uri)
|
||||||
}
|
}
|
||||||
@ -287,10 +294,13 @@ mod tests {
|
|||||||
use actix_web::{web, App, Error, HttpRequest, HttpResponse};
|
use actix_web::{web, App, Error, HttpRequest, HttpResponse};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{http::header::HeaderValue, ClientBuilder};
|
use crate::{
|
||||||
|
http::{header::HeaderValue, StatusCode},
|
||||||
|
ClientBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_basic_redirect() {
|
async fn basic_redirect() {
|
||||||
let client = ClientBuilder::new()
|
let client = ClientBuilder::new()
|
||||||
.disable_redirects()
|
.disable_redirects()
|
||||||
.wrap(Redirect::new().max_redirect_times(10))
|
.wrap(Redirect::new().max_redirect_times(10))
|
||||||
@ -315,6 +325,44 @@ mod tests {
|
|||||||
assert_eq!(res.status().as_u16(), 400);
|
assert_eq!(res.status().as_u16(), 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn redirect_relative_without_leading_slash() {
|
||||||
|
let client = ClientBuilder::new().finish();
|
||||||
|
|
||||||
|
let srv = actix_test::start(|| {
|
||||||
|
App::new()
|
||||||
|
.service(web::resource("/").route(web::to(|| async {
|
||||||
|
HttpResponse::Found()
|
||||||
|
.insert_header(("location", "abc/"))
|
||||||
|
.finish()
|
||||||
|
})))
|
||||||
|
.service(
|
||||||
|
web::resource("/abc/")
|
||||||
|
.route(web::to(|| async { HttpResponse::Accepted().finish() })),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let res = client.get(srv.url("/")).send().await.unwrap();
|
||||||
|
assert_eq!(res.status(), StatusCode::ACCEPTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn redirect_without_location() {
|
||||||
|
let client = ClientBuilder::new()
|
||||||
|
.disable_redirects()
|
||||||
|
.wrap(Redirect::new().max_redirect_times(10))
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let srv = actix_test::start(|| {
|
||||||
|
App::new().service(web::resource("/").route(web::to(|| async {
|
||||||
|
Ok::<_, Error>(HttpResponse::Found().finish())
|
||||||
|
})))
|
||||||
|
});
|
||||||
|
|
||||||
|
let res = client.get(srv.url("/")).send().await.unwrap();
|
||||||
|
assert_eq!(res.status(), StatusCode::FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_redirect_limit() {
|
async fn test_redirect_limit() {
|
||||||
let client = ClientBuilder::new()
|
let client = ClientBuilder::new()
|
||||||
@ -328,14 +376,14 @@ mod tests {
|
|||||||
.service(web::resource("/").route(web::to(|| async {
|
.service(web::resource("/").route(web::to(|| async {
|
||||||
Ok::<_, Error>(
|
Ok::<_, Error>(
|
||||||
HttpResponse::Found()
|
HttpResponse::Found()
|
||||||
.append_header(("location", "/test"))
|
.insert_header(("location", "/test"))
|
||||||
.finish(),
|
.finish(),
|
||||||
)
|
)
|
||||||
})))
|
})))
|
||||||
.service(web::resource("/test").route(web::to(|| async {
|
.service(web::resource("/test").route(web::to(|| async {
|
||||||
Ok::<_, Error>(
|
Ok::<_, Error>(
|
||||||
HttpResponse::Found()
|
HttpResponse::Found()
|
||||||
.append_header(("location", "/test2"))
|
.insert_header(("location", "/test2"))
|
||||||
.finish(),
|
.finish(),
|
||||||
)
|
)
|
||||||
})))
|
})))
|
||||||
@ -345,8 +393,15 @@ mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let res = client.get(srv.url("/")).send().await.unwrap();
|
let res = client.get(srv.url("/")).send().await.unwrap();
|
||||||
|
assert_eq!(res.status(), StatusCode::FOUND);
|
||||||
assert_eq!(res.status().as_u16(), 302);
|
assert_eq!(
|
||||||
|
res.headers()
|
||||||
|
.get(header::LOCATION)
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"/test2"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
@ -505,7 +505,7 @@ impl fmt::Debug for ClientRequest {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"\nClientRequest {:?} {}:{}",
|
"\nClientRequest {:?} {} {}",
|
||||||
self.head.version, self.head.method, self.head.uri
|
self.head.version, self.head.method, self.head.uri
|
||||||
)?;
|
)?;
|
||||||
writeln!(f, " headers:")?;
|
writeln!(f, " headers:")?;
|
||||||
|
@ -160,7 +160,7 @@ where
|
|||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// `Future` implementation returns error if:
|
/// `Future` implementation returns error if:
|
||||||
/// - content length is greater than [limit](JsonBody::limit) (default: 2 MiB)
|
/// - content length is greater than [limit](ResponseBody::limit) (default: 2 MiB)
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
|
Reference in New Issue
Block a user