1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-24 09:47:49 +02:00

Compare commits

...

31 Commits

Author SHA1 Message Date
Rob Ede
76684a786e update server dep to rc2 (#2550) 2021-12-27 18:45:31 +00:00
Rob Ede
2308f8afa4 use const header values where possible 2021-12-27 16:15:33 +00:00
Ali MJ Al-Nasrawy
554ae7a868 rework Handler trait (#2549) 2021-12-27 00:44:30 +00:00
Rob Ede
ac0c4eb684 update actix-tls references to stable 3.0.0 2021-12-26 21:24:03 +00:00
Rob Ede
2e493cf791 remove crate level clippy allows 2021-12-25 04:53:51 +00:00
Rob Ede
5860fe5381 expose Handler trait 2021-12-25 04:43:59 +00:00
Rob Ede
adf9935841 improve scope documentation
closes #2389
2021-12-25 03:44:09 +00:00
Mark Lodato
34e5c7c799 Improve module docs for error handler middleware (#2543) 2021-12-25 02:35:19 +00:00
Rob Ede
01cbfc5724 reduce -http re-exports in awc 2021-12-25 02:34:35 +00:00
Rob Ede
3756dfc2ce move client to own module 2021-12-25 02:34:31 +00:00
Rob Ede
d2590fd46c ClientRequest::send_body takes impl MessageBody (#2546) 2021-12-25 02:33:37 +00:00
Rob Ede
1296e07c48 relax unpin bounds on payload types (#2545) 2021-12-24 17:47:47 +00:00
Ali MJ Al-Nasrawy
7b1512d863 allow any body type in Scope (#2523) 2021-12-22 15:48:59 +00:00
Rob Ede
cd025f5c0b allow any body type in Resource (#2526) 2021-12-22 15:00:32 +00:00
Rob Ede
1769812d0b bump outdated deps 2021-12-22 08:43:38 +00:00
Rob Ede
324eba7e0b tighten tokio version range to prevent RUSTSEC-2021-0124 2021-12-22 08:41:44 +00:00
Rob Ede
b3ac918d70 update itoa to v1 2021-12-22 08:34:48 +00:00
Rob Ede
de20d21703 use dash hyphenation in markdown 2021-12-22 08:21:30 +00:00
Rob Ede
212c6926f9 Revert "use dash hyphenation in changelogs"
This reverts commit 1ea619f2a1.
2021-12-22 08:18:44 +00:00
Rob Ede
1ea619f2a1 use dash hyphenation in changelogs 2021-12-22 08:17:35 +00:00
Rob Ede
40a0162074 add tests to scope and resource for returning from fns 2021-12-22 07:58:37 +00:00
Rob Ede
f8488aff1e upstream changelog for v3.3.3 2021-12-22 07:20:53 +00:00
Rob Ede
64c2e5e1cd remove crate level clippy lint 2021-12-22 07:16:07 +00:00
Rob Ede
17f636a183 split request and response modules (#2530) 2021-12-19 17:05:27 +00:00
Rob Ede
2e00776d5e Update FUNDING.yml 2021-12-19 04:18:57 +00:00
Rob Ede
7d507a41ee Create FUNDING.yml 2021-12-19 03:58:29 +00:00
Rob Ede
fb036264cc only build SslConnectorBuilder once (#2503) 2021-12-18 16:44:30 +00:00
Rob Ede
d2b9724010 update bump script to detect prerelease versions 2021-12-18 03:27:32 +00:00
Rob Ede
5c53db1e4d remove hidden anybody 2021-12-18 01:48:16 +00:00
Thales
84ea9e7e88 http: Replace header::map::GetAll with std::slice::Iter (#2527) 2021-12-18 00:05:12 +00:00
Rob Ede
0bd5ccc432 update changelog 2021-12-17 21:39:15 +00:00
116 changed files with 3505 additions and 3013 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [robjtede]

View File

@@ -33,5 +33,5 @@ Please search on the [Actix Web issue tracker](https://github.com/actix/actix-we
## Your Environment
<!--- Include as many relevant details about the environment you experienced the bug in -->
* Rust Version (I.e, output of `rustc -V`):
* Actix Web Version:
- Rust Version (I.e, output of `rustc -V`):
- Actix Web Version:

66
.github/workflows/ci-master.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: CI (master only)
on:
push:
branches: [master]
jobs:
ci_feature_powerset_check:
name: Verify Feature Combinations
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable-x86_64-unknown-linux-gnu
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with: { command: generate-lockfile }
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0
- name: Install cargo-hack
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-hack
- name: check feature combinations
uses: actions-rs/cargo@v1
with: { command: ci-check-all-feature-powerset }
- name: check feature combinations
uses: actions-rs/cargo@v1
with: { command: ci-check-all-feature-powerset-linux }
coverage:
name: coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable-x86_64-unknown-linux-gnu
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with: { command: generate-lockfile }
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0
- name: Generate coverage file
run: |
cargo install cargo-tarpaulin --vers "^0.13"
cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose
- name: Upload to Codecov
uses: codecov/codecov-action@v1
with: { file: cobertura.xml }

View File

@@ -96,68 +96,6 @@ jobs:
cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean
cargo-cache
ci_feature_powerset_check:
name: Verify Feature Combinations
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable-x86_64-unknown-linux-gnu
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with: { command: generate-lockfile }
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0
- name: Install cargo-hack
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-hack
- name: check feature combinations
uses: actions-rs/cargo@v1
with: { command: ci-check-all-feature-powerset }
- name: check feature combinations
uses: actions-rs/cargo@v1
with: { command: ci-check-all-feature-powerset-linux }
coverage:
name: coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable-x86_64-unknown-linux-gnu
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with: { command: generate-lockfile }
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0
- name: Generate coverage file
if: github.ref == 'refs/heads/master'
run: |
cargo install cargo-tarpaulin --vers "^0.13"
cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose
- name: Upload to Codecov
if: github.ref == 'refs/heads/master'
uses: codecov/codecov-action@v1
with: { file: cobertura.xml }
rustdoc:
name: doc tests
runs-on: ubuntu-latest

File diff suppressed because it is too large Load Diff

View File

@@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "4.0.0-beta.15"
version = "4.0.0-beta.16"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
keywords = ["actix", "http", "web", "framework", "async"]
@@ -72,12 +72,12 @@ experimental-io-uring = ["actix-server/io-uring"]
actix-codec = "0.4.1"
actix-macros = "0.2.3"
actix-rt = "2.3"
actix-server = "2.0.0-rc.1"
actix-server = "2.0.0-rc.2"
actix-service = "2.0.0"
actix-utils = "3.0.0"
actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true }
actix-tls = { version = "3.0.0", default-features = false, optional = true }
actix-http = "3.0.0-beta.16"
actix-http = "3.0.0-beta.17"
actix-router = "0.5.0-beta.3"
actix-web-codegen = "0.5.0-beta.6"
@@ -89,7 +89,7 @@ derive_more = "0.99.5"
encoding_rs = "0.8"
futures-core = { version = "0.3.7", default-features = false }
futures-util = { version = "0.3.7", default-features = false }
itoa = "0.4"
itoa = "1"
language-tags = "0.3"
once_cell = "1.5"
log = "0.4"
@@ -106,8 +106,8 @@ time = { version = "0.3", default-features = false, features = ["formatting"] }
url = "2.1"
[dev-dependencies]
actix-test = { version = "0.1.0-beta.9", features = ["openssl", "rustls"] }
awc = { version = "3.0.0-beta.14", features = ["openssl"] }
actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] }
awc = { version = "3.0.0-beta.15", features = ["openssl"] }
brotli2 = "0.3.2"
criterion = { version = "0.3", features = ["html_reports"] }

View File

@@ -1,6 +1,6 @@
## Unreleased
* The default `NormalizePath` behavior now strips trailing slashes by default. This was
- 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.
@@ -11,9 +11,9 @@
Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`.
* The `type Config` of `FromRequest` was removed.
- The `type Config` of `FromRequest` was removed.
* Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd).
- Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd).
By default all compression algorithms are enabled.
To select algorithm you want to include with `middleware::Compress` use following flags:
- `compress-brotli`
@@ -28,30 +28,30 @@
## 3.0.0
* The return type for `ServiceRequest::app_data::<T>()` was changed from returning a `Data<T>` to
- The return type for `ServiceRequest::app_data::<T>()` was changed from returning a `Data<T>` to
simply a `T`. To access a `Data<T>` use `ServiceRequest::app_data::<Data<T>>()`.
* Cookie handling has been offloaded to the `cookie` crate:
- Cookie handling has been offloaded to the `cookie` crate:
* `USERINFO_ENCODE_SET` is no longer exposed. Percent-encoding is still supported; check docs.
* Some types now require lifetime parameters.
* The time crate was updated to `v0.2`, a major breaking change to the time crate, which affects
- The time crate was updated to `v0.2`, a major breaking change to the time crate, which affects
any `actix-web` method previously expecting a time v0.1 input.
* Setting a cookie's SameSite property, explicitly, to `SameSite::None` will now
- Setting a cookie's SameSite property, explicitly, to `SameSite::None` will now
result in `SameSite=None` being sent with the response Set-Cookie header.
To create a cookie without a SameSite attribute, remove any calls setting same_site.
* actix-http support for Actors messages was moved to actix-http crate and is enabled
- actix-http support for Actors messages was moved to actix-http crate and is enabled
with feature `actors`
* content_length function is removed from actix-http.
- content_length function is removed from actix-http.
You can set Content-Length by normally setting the response body or calling no_chunking function.
* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
- `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
`u64` instead of a `usize`.
* Code that was using `path.<index>` to access a `web::Path<(A, B, C)>`s elements now needs to use
- Code that was using `path.<index>` to access a `web::Path<(A, B, C)>`s elements now needs to use
destructuring or `.into_inner()`. For example:
```rust
@@ -71,35 +71,35 @@
}
```
* `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one.
- `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one.
It will need `middleware::normalize::TrailingSlash` when being constructed with `NormalizePath::new(...)`,
or for an easier migration you can replace `wrap(middleware::NormalizePath)` with `wrap(middleware::NormalizePath::new(TrailingSlash::MergeOnly))`.
* `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`.
- `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`.
* `HttpServer::maxconnrate` is renamed to the more expressive `HttpServer::max_connection_rate`.
- `HttpServer::maxconnrate` is renamed to the more expressive `HttpServer::max_connection_rate`.
## 2.0.0
* `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to
- `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to
`.await` on `run` method result, in that case it awaits server exit.
* `App::register_data()` renamed to `App::app_data()` and accepts any type `T: 'static`.
- `App::register_data()` renamed to `App::app_data()` and accepts any type `T: 'static`.
Stored data is available via `HttpRequest::app_data()` method at runtime.
* Extractor configuration must be registered with `App::app_data()` instead of `App::data()`
- Extractor configuration must be registered with `App::app_data()` instead of `App::data()`
* Sync handlers has been removed. `.to_async()` method has been renamed to `.to()`
- Sync handlers has been removed. `.to_async()` method has been renamed to `.to()`
replace `fn` with `async fn` to convert sync handler to async
* `actix_http_test::TestServer` moved to `actix_web::test` module. To start
- `actix_http_test::TestServer` moved to `actix_web::test` module. To start
test server use `test::start()` or `test_start_with_config()` methods
* `ResponseError` trait has been reafctored. `ResponseError::error_response()` renders
- `ResponseError` trait has been reafctored. `ResponseError::error_response()` renders
http response.
* Feature `rust-tls` renamed to `rustls`
- Feature `rust-tls` renamed to `rustls`
instead of
@@ -113,7 +113,7 @@
actix-web = { version = "2.0.0", features = ["rustls"] }
```
* Feature `ssl` renamed to `openssl`
- Feature `ssl` renamed to `openssl`
instead of
@@ -126,11 +126,11 @@
```rust
actix-web = { version = "2.0.0", features = ["openssl"] }
```
* `Cors` builder now requires that you call `.finish()` to construct the middleware
- `Cors` builder now requires that you call `.finish()` to construct the middleware
## 1.0.1
* Cors middleware has been moved to `actix-cors` crate
- Cors middleware has been moved to `actix-cors` crate
instead of
@@ -144,7 +144,7 @@
use actix_cors::Cors;
```
* Identity middleware has been moved to `actix-identity` crate
- Identity middleware has been moved to `actix-identity` crate
instead of
@@ -161,7 +161,7 @@
## 1.0.0
* Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration
- Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration
instead of
@@ -219,7 +219,7 @@
)
```
* Resource registration. 1.0 version uses generalized resource
- Resource registration. 1.0 version uses generalized resource
registration via `.service()` method.
instead of
@@ -239,7 +239,7 @@
.route(web::post().to(post_handler))
```
* Scope registration.
- Scope registration.
instead of
@@ -263,7 +263,7 @@
);
```
* `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`.
- `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`.
instead of
@@ -277,7 +277,7 @@
App.new().service(web::resource("/welcome").to(welcome))
```
* Passing arguments to handler with extractors, multiple arguments are allowed
- Passing arguments to handler with extractors, multiple arguments are allowed
instead of
@@ -295,7 +295,7 @@
}
```
* `.f()`, `.a()` and `.h()` handler registration methods have been removed.
- `.f()`, `.a()` and `.h()` handler registration methods have been removed.
Use `.to()` for handlers and `.to_async()` for async handlers. Handler function
must use extractors.
@@ -311,7 +311,7 @@
App.new().service(web::resource("/welcome").to(welcome))
```
* `HttpRequest` does not provide access to request's payload stream.
- `HttpRequest` does not provide access to request's payload stream.
instead of
@@ -341,7 +341,7 @@
}
```
* `State` is now `Data`. You register Data during the App initialization process
- `State` is now `Data`. You register Data during the App initialization process
and then access it from handlers either using a Data extractor or using
HttpRequest's api.
@@ -377,7 +377,7 @@
```
* AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type.
- AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type.
instead of
@@ -393,7 +393,7 @@
.. simply omit AsyncResponder and the corresponding responder() finish method
* Middleware
- Middleware
instead of
@@ -410,7 +410,7 @@
.route("/index.html", web::get().to(index));
```
* `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()`
- `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()`
method have been removed. Use `Bytes`, `String`, `Form`, `Json`, `Multipart` extractors instead.
instead of
@@ -432,9 +432,9 @@
}
```
* `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type
- `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type
* StaticFiles and NamedFile have been moved to a separate crate.
- StaticFiles and NamedFile have been moved to a separate crate.
instead of `use actix_web::fs::StaticFile`
@@ -444,20 +444,20 @@
use `use actix_files::NamedFile`
* Multipart has been moved to a separate crate.
- Multipart has been moved to a separate crate.
instead of `use actix_web::multipart::Multipart`
use `use actix_multipart::Multipart`
* Response compression is not enabled by default.
- Response compression is not enabled by default.
To enable, use `Compress` middleware, `App::new().wrap(Compress::default())`.
* Session middleware moved to actix-session crate
- Session middleware moved to actix-session crate
* Actors support have been moved to `actix-web-actors` crate
- Actors support have been moved to `actix-web-actors` crate
* Custom Error
- Custom Error
Instead of error_response method alone, ResponseError now provides two methods: error_response and render_response respectively. Where, error_response creates the error response and render_response returns the error response to the caller.
@@ -471,7 +471,7 @@
## 0.7.15
* The `' '` character is not percent decoded anymore before matching routes. If you need to use it in
- The `' '` character is not percent decoded anymore before matching routes. If you need to use it in
your routes, you should use `%20`.
instead of
@@ -496,18 +496,18 @@
}
```
* If you used `AsyncResult::async` you need to replace it with `AsyncResult::future`
- If you used `AsyncResult::async` you need to replace it with `AsyncResult::future`
## 0.7.4
* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
- `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
even for handler with one parameter.
## 0.7
* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload
- `HttpRequest` does not implement `Stream` anymore. If you need to read request payload
use `HttpMessage::payload()` method.
instead of
@@ -533,10 +533,10 @@
}
```
* [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html)
- [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html)
trait uses `&HttpRequest` instead of `&mut HttpRequest`.
* Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead.
- Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead.
instead of
@@ -550,17 +550,17 @@
fn index((query, json): (Query<..>, Json<MyStruct)) -> impl Responder {}
```
* `Handler::handle()` uses `&self` instead of `&mut self`
- `Handler::handle()` uses `&self` instead of `&mut self`
* `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value
- `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value
* Removed deprecated `HttpServer::threads()`, use
- Removed deprecated `HttpServer::threads()`, use
[HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead.
* Renamed `client::ClientConnectorError::Connector` to
- Renamed `client::ClientConnectorError::Connector` to
`client::ClientConnectorError::Resolver`
* `Route::with()` does not return `ExtractorConfig`, to configure
- `Route::with()` does not return `ExtractorConfig`, to configure
extractor use `Route::with_config()`
instead of
@@ -589,26 +589,26 @@
}
```
* `Route::with_async()` does not return `ExtractorConfig`, to configure
- `Route::with_async()` does not return `ExtractorConfig`, to configure
extractor use `Route::with_async_config()`
## 0.6
* `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
- `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
* `ws::Message::Close` now includes optional close reason.
- `ws::Message::Close` now includes optional close reason.
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
* `HttpServer::threads()` renamed to `HttpServer::workers()`.
- `HttpServer::threads()` renamed to `HttpServer::workers()`.
* `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated.
- `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated.
Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead.
* `HttpRequest::extensions()` returns read only reference to the request's Extension
- `HttpRequest::extensions()` returns read only reference to the request's Extension
`HttpRequest::extensions_mut()` returns mutable reference.
* Instead of
- Instead of
`use actix_web::middleware::{
CookieSessionBackend, CookieSessionError, RequestSession,
@@ -619,15 +619,15 @@
`use actix_web::middleware::session{CookieSessionBackend, CookieSessionError,
RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};`
* `FromRequest::from_request()` accepts mutable reference to a request
- `FromRequest::from_request()` accepts mutable reference to a request
* `FromRequest::Result` has to implement `Into<Reply<Self>>`
- `FromRequest::Result` has to implement `Into<Reply<Self>>`
* [`Responder::respond_to()`](
- [`Responder::respond_to()`](
https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
is generic over `S`
* Use `Query` extractor instead of HttpRequest::query()`.
- Use `Query` extractor instead of HttpRequest::query()`.
```rust
fn index(q: Query<HashMap<String, String>>) -> Result<..> {
@@ -641,37 +641,37 @@
let q = Query::<HashMap<String, String>>::extract(req);
```
* Websocket operations are implemented as `WsWriter` trait.
- Websocket operations are implemented as `WsWriter` trait.
you need to use `use actix_web::ws::WsWriter`
## 0.5
* `HttpResponseBuilder::body()`, `.finish()`, `.json()`
- `HttpResponseBuilder::body()`, `.finish()`, `.json()`
methods return `HttpResponse` instead of `Result<HttpResponse>`
* `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version`
- `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version`
moved to `actix_web::http` module
* `actix_web::header` moved to `actix_web::http::header`
- `actix_web::header` moved to `actix_web::http::header`
* `NormalizePath` moved to `actix_web::http` module
- `NormalizePath` moved to `actix_web::http` module
* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function,
- `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function,
shortcut for `actix_web::server::HttpServer::new()`
* `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself
- `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself
* `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead.
- `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead.
* `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type
- `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type
* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()`
- `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()`
functions should be used instead
* `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>`
- `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>`
instead of `Result<_, http::Error>`
* `Application` renamed to a `App`
- `Application` renamed to a `App`
* `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev`
- `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev`

View File

@@ -6,10 +6,10 @@
<p>
[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.15)](https://docs.rs/actix-web/4.0.0-beta.15)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.16)](https://docs.rs/actix-web/4.0.0-beta.16)
[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg)
[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.15/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.15)
[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.16/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.16)
<br />
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions)
[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web)
@@ -21,25 +21,25 @@
## Features
* Supports *HTTP/1.x* and *HTTP/2*
* Streaming and pipelining
* Keep-alive and slow requests handling
* Client/server [WebSockets](https://actix.rs/docs/websockets/) support
* Transparent content compression/decompression (br, gzip, deflate, zstd)
* Powerful [request routing](https://actix.rs/docs/url-dispatch/)
* Multipart streams
* Static assets
* SSL support using OpenSSL or Rustls
* Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
* Includes an async [HTTP client](https://docs.rs/awc/)
* Runs on stable Rust 1.52+
- Supports *HTTP/1.x* and *HTTP/2*
- Streaming and pipelining
- Keep-alive and slow requests handling
- Client/server [WebSockets](https://actix.rs/docs/websockets/) support
- Transparent content compression/decompression (br, gzip, deflate, zstd)
- Powerful [request routing](https://actix.rs/docs/url-dispatch/)
- Multipart streams
- Static assets
- SSL support using OpenSSL or Rustls
- Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
- Includes an async [HTTP client](https://docs.rs/awc/)
- Runs on stable Rust 1.52+
## Documentation
* [Website & User Guide](https://actix.rs)
* [Examples Repository](https://github.com/actix/examples)
* [API Documentation](https://docs.rs/actix-web)
* [API Documentation (master branch)](https://actix.rs/actix-web/actix_web)
- [Website & User Guide](https://actix.rs)
- [Examples Repository](https://github.com/actix/examples)
- [API Documentation](https://docs.rs/actix-web)
- [API Documentation (master branch)](https://actix.rs/actix-web/actix_web)
## Example
@@ -71,18 +71,18 @@ async fn main() -> std::io::Result<()> {
### More examples
* [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics/)
* [Application State](https://github.com/actix/examples/tree/master/basics/state/)
* [JSON Handling](https://github.com/actix/examples/tree/master/json/json/)
* [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart/)
* [Diesel Integration](https://github.com/actix/examples/tree/master/database_interactions/diesel/)
* [r2d2 Integration](https://github.com/actix/examples/tree/master/database_interactions/r2d2/)
* [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/websocket/)
* [Tera Templates](https://github.com/actix/examples/tree/master/template_engines/tera/)
* [Askama Templates](https://github.com/actix/examples/tree/master/template_engines/askama/)
* [HTTPS using Rustls](https://github.com/actix/examples/tree/master/security/rustls/)
* [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/)
* [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/)
- [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics/)
- [Application State](https://github.com/actix/examples/tree/master/basics/state/)
- [JSON Handling](https://github.com/actix/examples/tree/master/json/json/)
- [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart/)
- [Diesel Integration](https://github.com/actix/examples/tree/master/database_interactions/diesel/)
- [r2d2 Integration](https://github.com/actix/examples/tree/master/database_interactions/r2d2/)
- [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/websocket/)
- [Tera Templates](https://github.com/actix/examples/tree/master/template_engines/tera/)
- [Askama Templates](https://github.com/actix/examples/tree/master/template_engines/askama/)
- [HTTPS using Rustls](https://github.com/actix/examples/tree/master/security/rustls/)
- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/)
- [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.
@@ -96,9 +96,9 @@ One of the fastest web frameworks available according to the
This project is licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
[http://www.apache.org/licenses/LICENSE-2.0])
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
- MIT license ([LICENSE-MIT](LICENSE-MIT) or
[http://opensource.org/licenses/MIT])
at your option.

View File

@@ -3,43 +3,47 @@
## Unreleased - 2021-xx-xx
## 0.6.0-beta.11 - 2021-12-27
* No significant changes since `0.6.0-beta.10`.
## 0.6.0-beta.10 - 2021-12-11
* No significant changes since `0.6.0-beta.9`.
- No significant changes since `0.6.0-beta.9`.
## 0.6.0-beta.9 - 2021-11-22
* Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408]
* Add `NamedFile::open_async`. [#2408]
* Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453]
* The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408]
* The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408]
* Add `impl Clone` for `FilesService`. [#2408]
- Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408]
- Add `NamedFile::open_async`. [#2408]
- Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453]
- The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408]
- The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408]
- Add `impl Clone` for `FilesService`. [#2408]
[#2408]: https://github.com/actix/actix-web/pull/2408
[#2453]: https://github.com/actix/actix-web/pull/2453
## 0.6.0-beta.8 - 2021-10-20
* Minimum supported Rust version (MSRV) is now 1.52.
- Minimum supported Rust version (MSRV) is now 1.52.
## 0.6.0-beta.7 - 2021-09-09
* Minimum supported Rust version (MSRV) is now 1.51.
- Minimum supported Rust version (MSRV) is now 1.51.
## 0.6.0-beta.6 - 2021-06-26
* Added `Files::path_filter()`. [#2274]
* `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228]
- Added `Files::path_filter()`. [#2274]
- `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228]
[#2274]: https://github.com/actix/actix-web/pull/2274
[#2228]: https://github.com/actix/actix-web/pull/2228
## 0.6.0-beta.5 - 2021-06-17
* `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135]
* For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156]
* `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225]
* `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257]
- `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135]
- For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156]
- `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225]
- `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257]
[#2135]: https://github.com/actix/actix-web/pull/2135
[#2156]: https://github.com/actix/actix-web/pull/2156
@@ -48,130 +52,130 @@
## 0.6.0-beta.4 - 2021-04-02
* Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046]
- Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046]
[#2046]: https://github.com/actix/actix-web/pull/2046
## 0.6.0-beta.3 - 2021-03-09
* No notable changes.
- No notable changes.
## 0.6.0-beta.2 - 2021-02-10
* Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887]
* Replace `v_htmlescape` with `askama_escape`. [#1953]
- Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887]
- Replace `v_htmlescape` with `askama_escape`. [#1953]
[#1887]: https://github.com/actix/actix-web/pull/1887
[#1953]: https://github.com/actix/actix-web/pull/1953
## 0.6.0-beta.1 - 2021-01-07
* `HttpRange::parse` now has its own error type.
* Update `bytes` to `1.0`. [#1813]
- `HttpRange::parse` now has its own error type.
- Update `bytes` to `1.0`. [#1813]
[#1813]: https://github.com/actix/actix-web/pull/1813
## 0.5.0 - 2020-12-26
* Optionally support hidden files/directories. [#1811]
- Optionally support hidden files/directories. [#1811]
[#1811]: https://github.com/actix/actix-web/pull/1811
## 0.4.1 - 2020-11-24
* Clarify order of parameters in `Files::new` and improve docs.
- Clarify order of parameters in `Files::new` and improve docs.
## 0.4.0 - 2020-10-06
* Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714]
- Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714]
[#1714]: https://github.com/actix/actix-web/pull/1714
## 0.3.0 - 2020-09-11
* No significant changes from 0.3.0-beta.1.
- No significant changes from 0.3.0-beta.1.
## 0.3.0-beta.1 - 2020-07-15
* Update `v_htmlescape` to 0.10
* Update `actix-web` and `actix-http` dependencies to beta.1
- Update `v_htmlescape` to 0.10
- Update `actix-web` and `actix-http` dependencies to beta.1
## 0.3.0-alpha.1 - 2020-05-23
* Update `actix-web` and `actix-http` dependencies to alpha
* Fix some typos in the docs
* Bump minimum supported Rust version to 1.40
* Support sending Content-Length when Content-Range is specified [#1384]
- Update `actix-web` and `actix-http` dependencies to alpha
- Fix some typos in the docs
- Bump minimum supported Rust version to 1.40
- Support sending Content-Length when Content-Range is specified [#1384]
[#1384]: https://github.com/actix/actix-web/pull/1384
## 0.2.1 - 2019-12-22
* Use the same format for file URLs regardless of platforms
- Use the same format for file URLs regardless of platforms
## 0.2.0 - 2019-12-20
* Fix BodyEncoding trait import #1220
- Fix BodyEncoding trait import #1220
## 0.2.0-alpha.1 - 2019-12-07
* Migrate to `std::future`
- Migrate to `std::future`
## 0.1.7 - 2019-11-06
* Add an additional `filename*` param in the `Content-Disposition` header of
- Add an additional `filename*` param in the `Content-Disposition` header of
`actix_files::NamedFile` to be more compatible. (#1151)
## 0.1.6 - 2019-10-14
* Add option to redirect to a slash-ended path `Files` #1132
- Add option to redirect to a slash-ended path `Files` #1132
## 0.1.5 - 2019-10-08
* Bump up `mime_guess` crate version to 2.0.1
* Bump up `percent-encoding` crate version to 2.1
* Allow user defined request guards for `Files` #1113
- Bump up `mime_guess` crate version to 2.0.1
- Bump up `percent-encoding` crate version to 2.1
- Allow user defined request guards for `Files` #1113
## 0.1.4 - 2019-07-20
* Allow to disable `Content-Disposition` header #686
- Allow to disable `Content-Disposition` header #686
## 0.1.3 - 2019-06-28
* Do not set `Content-Length` header, let actix-http set it #930
- Do not set `Content-Length` header, let actix-http set it #930
## 0.1.2 - 2019-06-13
* Content-Length is 0 for NamedFile HEAD request #914
* Fix ring dependency from actix-web default features for #741
- Content-Length is 0 for NamedFile HEAD request #914
- Fix ring dependency from actix-web default features for #741
## 0.1.1 - 2019-06-01
* Static files are incorrectly served as both chunked and with length #812
- Static files are incorrectly served as both chunked and with length #812
## 0.1.0 - 2019-05-25
* NamedFile last-modified check always fails due to nano-seconds in file modified date #820
- NamedFile last-modified check always fails due to nano-seconds in file modified date #820
## 0.1.0-beta.4 - 2019-05-12
* Update actix-web to beta.4
- Update actix-web to beta.4
## 0.1.0-beta.1 - 2019-04-20
* Update actix-web to beta.1
- Update actix-web to beta.1
## 0.1.0-alpha.6 - 2019-04-14
* Update actix-web to alpha6
- Update actix-web to alpha6
## 0.1.0-alpha.4 - 2019-04-08
* Update actix-web to alpha4
- Update actix-web to alpha4
## 0.1.0-alpha.2 - 2019-04-02
* Add default handler support
- Add default handler support
## 0.1.0-alpha.1 - 2019-03-28
* Initial impl
- Initial impl

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-files"
version = "0.6.0-beta.10"
version = "0.6.0-beta.11"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>",
@@ -22,10 +22,10 @@ path = "src/lib.rs"
experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
[dependencies]
actix-http = "3.0.0-beta.16"
actix-http = "3.0.0-beta.17"
actix-service = "2"
actix-utils = "3"
actix-web = { version = "4.0.0-beta.15", default-features = false }
actix-web = { version = "4.0.0-beta.16", default-features = false }
askama_escape = "0.10"
bitflags = "1"
@@ -43,5 +43,5 @@ tokio-uring = { version = "0.1", optional = true }
[dev-dependencies]
actix-rt = "2.2"
actix-test = "0.1.0-beta.9"
actix-web = "4.0.0-beta.15"
actix-test = "0.1.0-beta.10"
actix-web = "4.0.0-beta.16"

View File

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

View File

@@ -3,126 +3,132 @@
## Unreleased - 2021-xx-xx
## 3.0.0-beta.10 - 2021-12-27
- Update `actix-server` to `2.0.0-rc.2`. [#2550]
[#2550]: https://github.com/actix/actix-web/pull/2550
## 3.0.0-beta.9 - 2021-12-11
* No significant changes since `3.0.0-beta.8`.
- No significant changes since `3.0.0-beta.8`.
## 3.0.0-beta.8 - 2021-11-30
* Update `actix-tls` to `3.0.0-rc.1`. [#2474]
- Update `actix-tls` to `3.0.0-rc.1`. [#2474]
[#2474]: https://github.com/actix/actix-web/pull/2474
## 3.0.0-beta.7 - 2021-11-22
* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408]
- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408]
[#2408]: https://github.com/actix/actix-web/pull/2408
## 3.0.0-beta.6 - 2021-11-15
* `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442]
* Update `actix-server` to `2.0.0-beta.9`. [#2442]
* Minimum supported Rust version (MSRV) is now 1.52.
- `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442]
- Update `actix-server` to `2.0.0-beta.9`. [#2442]
- Minimum supported Rust version (MSRV) is now 1.52.
[#2442]: https://github.com/actix/actix-web/pull/2442
## 3.0.0-beta.5 - 2021-09-09
* Minimum supported Rust version (MSRV) is now 1.51.
- Minimum supported Rust version (MSRV) is now 1.51.
## 3.0.0-beta.4 - 2021-04-02
* Added `TestServer::client_headers` method. [#2097]
- Added `TestServer::client_headers` method. [#2097]
[#2097]: https://github.com/actix/actix-web/pull/2097
## 3.0.0-beta.3 - 2021-03-09
* No notable changes.
- No notable changes.
## 3.0.0-beta.2 - 2021-02-10
* No notable changes.
- No notable changes.
## 3.0.0-beta.1 - 2021-01-07
* Update `bytes` to `1.0`. [#1813]
- Update `bytes` to `1.0`. [#1813]
[#1813]: https://github.com/actix/actix-web/pull/1813
## 2.1.0 - 2020-11-25
* Add ability to set address for `TestServer`. [#1645]
* Upgrade `base64` to `0.13`.
* Upgrade `serde_urlencoded` to `0.7`. [#1773]
- Add ability to set address for `TestServer`. [#1645]
- Upgrade `base64` to `0.13`.
- Upgrade `serde_urlencoded` to `0.7`. [#1773]
[#1773]: https://github.com/actix/actix-web/pull/1773
[#1645]: https://github.com/actix/actix-web/pull/1645
## 2.0.0 - 2020-09-11
* Update actix-codec and actix-utils dependencies.
- Update actix-codec and actix-utils dependencies.
## 2.0.0-alpha.1 - 2020-05-23
* Update the `time` dependency to 0.2.7
* Update `actix-connect` dependency to 2.0.0-alpha.2
* Make `test_server` `async` fn.
* Bump minimum supported Rust version to 1.40
* Replace deprecated `net2` crate with `socket2`
* Update `base64` dependency to 0.12
* Update `env_logger` dependency to 0.7
- Update the `time` dependency to 0.2.7
- Update `actix-connect` dependency to 2.0.0-alpha.2
- Make `test_server` `async` fn.
- Bump minimum supported Rust version to 1.40
- Replace deprecated `net2` crate with `socket2`
- Update `base64` dependency to 0.12
- Update `env_logger` dependency to 0.7
## 1.0.0 - 2019-12-13
* Replaced `TestServer::start()` with `test_server()`
- Replaced `TestServer::start()` with `test_server()`
## 1.0.0-alpha.3 - 2019-12-07
* Migrate to `std::future`
- Migrate to `std::future`
## 0.2.5 - 2019-09-17
* Update serde_urlencoded to "0.6.1"
* Increase TestServerRuntime timeouts from 500ms to 3000ms
* Do not override current `System`
- Update serde_urlencoded to "0.6.1"
- Increase TestServerRuntime timeouts from 500ms to 3000ms
- Do not override current `System`
## 0.2.4 - 2019-07-18
* Update actix-server to 0.6
- Update actix-server to 0.6
## 0.2.3 - 2019-07-16
* Add `delete`, `options`, `patch` methods to `TestServerRunner`
- Add `delete`, `options`, `patch` methods to `TestServerRunner`
## 0.2.2 - 2019-06-16
* Add .put() and .sput() methods
- Add .put() and .sput() methods
## 0.2.1 - 2019-06-05
* Add license files
- Add license files
## 0.2.0 - 2019-05-12
* Update awc and actix-http deps
- Update awc and actix-http deps
## 0.1.1 - 2019-04-24
* Always make new connection for http client
- Always make new connection for http client
## 0.1.0 - 2019-04-16
* No changes
- No changes
## 0.1.0-alpha.3 - 2019-04-02
* Request functions accept path #743
- Request functions accept path #743
## 0.1.0-alpha.2 - 2019-03-29
* Added TestServerRuntime::load_body() method
* Update actix-http and awc libraries
- Added TestServerRuntime::load_body() method
- Update actix-http and awc libraries
## 0.1.0-alpha.1 - 2019-03-28
* Initial impl
- Initial impl

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-http-test"
version = "3.0.0-beta.9"
version = "3.0.0-beta.10"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Various helpers for Actix applications to use during testing"
keywords = ["http", "web", "framework", "async", "futures"]
@@ -31,11 +31,11 @@ openssl = ["tls-openssl", "awc/openssl"]
[dependencies]
actix-service = "2.0.0"
actix-codec = "0.4.1"
actix-tls = "3.0.0-rc.1"
actix-tls = "3.0.0"
actix-utils = "3.0.0"
actix-rt = "2.2"
actix-server = "2.0.0-rc.1"
awc = { version = "3.0.0-beta.14", default-features = false }
actix-server = "2.0.0-rc.2"
awc = { version = "3.0.0-beta.15", default-features = false }
base64 = "0.13"
bytes = "1"
@@ -48,8 +48,8 @@ serde_json = "1.0"
slab = "0.4"
serde_urlencoded = "0.7"
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
tokio = { version = "1.2", features = ["sync"] }
tokio = { version = "1.8", features = ["sync"] }
[dev-dependencies]
actix-web = { version = "4.0.0-beta.15", default-features = false, features = ["cookies"] }
actix-http = "3.0.0-beta.16"
actix-web = { version = "4.0.0-beta.16", default-features = false, features = ["cookies"] }
actix-http = "3.0.0-beta.17"

View File

@@ -3,11 +3,11 @@
> Various helpers for Actix applications to use during testing.
[![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test)
[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.9)](https://docs.rs/actix-http-test/3.0.0-beta.9)
[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.10)](https://docs.rs/actix-http-test/3.0.0-beta.10)
[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
<br>
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.9/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.9)
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.10/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.10)
[![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@@ -12,7 +12,7 @@ use std::{net, thread, time::Duration};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_rt::{net::TcpStream, System};
use actix_server::{Server, ServiceFactory};
use actix_server::{Server, ServerServiceFactory};
use awc::{
error::PayloadError, http::header::HeaderMap, ws, Client, ClientRequest, ClientResponse,
Connector,
@@ -51,13 +51,13 @@ use tokio::sync::mpsc;
/// assert!(response.status().is_success());
/// }
/// ```
pub async fn test_server<F: ServiceFactory<TcpStream>>(factory: F) -> TestServer {
pub async fn test_server<F: ServerServiceFactory<TcpStream>>(factory: F) -> TestServer {
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
test_server_with_addr(tcp, factory).await
}
/// Start [`test server`](test_server()) on an existing address binding.
pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
pub async fn test_server_with_addr<F: ServerServiceFactory<TcpStream>>(
tcp: net::TcpListener,
factory: F,
) -> TestServer {
@@ -107,7 +107,7 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
Connector::new()
.conn_lifetime(Duration::from_secs(0))
.timeout(Duration::from_millis(30000))
.ssl(builder.build())
.openssl(builder.build())
};
#[cfg(not(feature = "openssl"))]

View File

@@ -3,17 +3,33 @@
## Unreleased - 2021-xx-xx
## 3.0.0-beta.16 - 2021-12-17
### Added
* New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522]
### Changed
* Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510]
* Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510]
* Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510]
## 3.0.0-beta.17 - 2021-12-27
### Changes
- `HeaderMap::get_all` now returns a `std::slice::Iter`. [#2527]
- `Payload` inner fields are now named. [#2545]
- `impl Stream` for `Payload` no longer requires the `Stream` variant be `Unpin`. [#2545]
- `impl Future` for `h1::SendResponse` no longer requires the body type be `Unpin`. [#2545]
- `impl Stream` for `encoding::Decoder` no longer requires the stream type be `Unpin`. [#2545]
- Rename `PayloadStream` to `BoxedPayloadStream`. [#2545]
### Removed
* `MessageBody::{is_complete_body,take_complete_body}`. [#2522]
- `h1::Payload::readany`. [#2545]
[#2527]: https://github.com/actix/actix-web/pull/2527
[#2545]: https://github.com/actix/actix-web/pull/2545
## 3.0.0-beta.16 - 2021-12-17
### Added
- New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522]
### Changed
- Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510]
- Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510]
- Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510]
### Removed
- `MessageBody::{is_complete_body,take_complete_body}`. [#2522]
[#2510]: https://github.com/actix/actix-web/pull/2510
[#2522]: https://github.com/actix/actix-web/pull/2522
@@ -21,43 +37,43 @@
## 3.0.0-beta.15 - 2021-12-11
### Added
* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483]
* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483]
* `Response::map_into_boxed_body`. [#2468]
* `body::EitherBody` enum. [#2468]
* `body::None` struct. [#2468]
* Impl `MessageBody` for `bytestring::ByteString`. [#2468]
* `impl Clone for ws::HandshakeError`. [#2468]
* `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920]
* `impl Default ` for `ws::Codec`. [#1920]
* `header::QualityItem::{max, min}`. [#2486]
* `header::Quality::{MAX, MIN}`. [#2486]
* `impl Display` for `header::Quality`. [#2486]
* Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491]
* `Request::take_conn_data()`. [#2491]
* `Request::take_req_data()`. [#2487]
* `impl Clone` for `RequestHead`. [#2487]
* New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497]
* New `boxed` method on `MessageBody` trait for wrapping body type. [#2520]
- Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483]
- HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483]
- `Response::map_into_boxed_body`. [#2468]
- `body::EitherBody` enum. [#2468]
- `body::None` struct. [#2468]
- Impl `MessageBody` for `bytestring::ByteString`. [#2468]
- `impl Clone for ws::HandshakeError`. [#2468]
- `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920]
- `impl Default ` for `ws::Codec`. [#1920]
- `header::QualityItem::{max, min}`. [#2486]
- `header::Quality::{MAX, MIN}`. [#2486]
- `impl Display` for `header::Quality`. [#2486]
- Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491]
- `Request::take_conn_data()`. [#2491]
- `Request::take_req_data()`. [#2487]
- `impl Clone` for `RequestHead`. [#2487]
- New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497]
- New `boxed` method on `MessageBody` trait for wrapping body type. [#2520]
### Changed
* Rename `body::BoxBody::{from_body => new}`. [#2468]
* Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468]
* The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468]
* Error types using in service builders now require `Into<Response<BoxBody>>`. [#2468]
* `From` implementations on error types now return a `Response<BoxBody>`. [#2468]
* `ResponseBuilder::body(B)` now returns `Response<EitherBody<B>>`. [#2468]
* `ResponseBuilder::finish()` now returns `Response<EitherBody<()>>`. [#2468]
- Rename `body::BoxBody::{from_body => new}`. [#2468]
- Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468]
- The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468]
- Error types using in service builders now require `Into<Response<BoxBody>>`. [#2468]
- `From` implementations on error types now return a `Response<BoxBody>`. [#2468]
- `ResponseBuilder::body(B)` now returns `Response<EitherBody<B>>`. [#2468]
- `ResponseBuilder::finish()` now returns `Response<EitherBody<()>>`. [#2468]
### Removed
* `ResponseBuilder::streaming`. [#2468]
* `impl Future` for `ResponseBuilder`. [#2468]
* Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468]
* Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468]
* `impl Copy` for `ws::Codec`. [#1920]
* `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486]
* `impl TryFrom<u16>` for `header::Quality`. [#2486]
* `http` module. Most everything it contained is exported at the crate root. [#2488]
- `ResponseBuilder::streaming`. [#2468]
- `impl Future` for `ResponseBuilder`. [#2468]
- Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468]
- Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468]
- `impl Copy` for `ws::Codec`. [#1920]
- `header::qitem` helper. Replaced with `header::QualityItem::max`. [#2486]
- `impl TryFrom<u16>` for `header::Quality`. [#2486]
- `http` module. Most everything it contained is exported at the crate root. [#2488]
[#2483]: https://github.com/actix/actix-web/pull/2483
[#2468]: https://github.com/actix/actix-web/pull/2468
@@ -72,10 +88,10 @@
## 3.0.0-beta.14 - 2021-11-30
### Changed
* Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467]
* Expose `header::map` module. [#2467]
* Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470]
* Update `actix-tls` to `3.0.0-rc.1`. [#2474]
- Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467]
- Expose `header::map` module. [#2467]
- Implement `ExactSizeIterator` and `FusedIterator` for all `HeaderMap` iterators. [#2470]
- Update `actix-tls` to `3.0.0-rc.1`. [#2474]
[#2467]: https://github.com/actix/actix-web/pull/2467
[#2470]: https://github.com/actix/actix-web/pull/2470
@@ -84,24 +100,24 @@
## 3.0.0-beta.13 - 2021-11-22
### Added
* `body::AnyBody::empty` for quickly creating an empty body. [#2446]
* `body::AnyBody::none` for quickly creating a "none" body. [#2456]
* `impl Clone` for `body::AnyBody<S> where S: Clone`. [#2448]
* `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448]
- `body::AnyBody::empty` for quickly creating an empty body. [#2446]
- `body::AnyBody::none` for quickly creating a "none" body. [#2456]
- `impl Clone` for `body::AnyBody<S> where S: Clone`. [#2448]
- `body::AnyBody::into_boxed` for quickly converting to a type-erased, boxed body type. [#2448]
### Changed
* Rename `body::AnyBody::{Message => Body}`. [#2446]
* Rename `body::AnyBody::{from_message => new_boxed}`. [#2448]
* Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448]
* Rename `body::{BoxAnyBody => BoxBody}`. [#2448]
* Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448]
* `Encoder::response` now returns `AnyBody<Encoder<B>>`. [#2448]
- Rename `body::AnyBody::{Message => Body}`. [#2446]
- Rename `body::AnyBody::{from_message => new_boxed}`. [#2448]
- Rename `body::AnyBody::{from_slice => copy_from_slice}`. [#2448]
- Rename `body::{BoxAnyBody => BoxBody}`. [#2448]
- Change representation of `AnyBody` to include a type parameter in `Body` variant. Defaults to `BoxBody`. [#2448]
- `Encoder::response` now returns `AnyBody<Encoder<B>>`. [#2448]
### Removed
* `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446]
* `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446]
* `EncoderError::Boxed`; it is no longer required. [#2446]
* `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446]
- `body::AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446]
- `body::BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446]
- `EncoderError::Boxed`; it is no longer required. [#2446]
- `body::ResponseBody`; is function is replaced by the new `body::AnyBody` enum. [#2446]
[#2446]: https://github.com/actix/actix-web/pull/2446
[#2448]: https://github.com/actix/actix-web/pull/2448
@@ -110,11 +126,11 @@
## 3.0.0-beta.12 - 2021-11-15
### Changed
* Update `actix-server` to `2.0.0-beta.9`. [#2442]
- Update `actix-server` to `2.0.0-beta.9`. [#2442]
### Removed
* `client` module. [#2425]
* `trust-dns` feature. [#2425]
- `client` module. [#2425]
- `trust-dns` feature. [#2425]
[#2425]: https://github.com/actix/actix-web/pull/2425
[#2442]: https://github.com/actix/actix-web/pull/2442
@@ -122,21 +138,21 @@
## 3.0.0-beta.11 - 2021-10-20
### Changed
* Updated rustls to v0.20. [#2414]
* Minimum supported Rust version (MSRV) is now 1.52.
- Updated rustls to v0.20. [#2414]
- Minimum supported Rust version (MSRV) is now 1.52.
[#2414]: https://github.com/actix/actix-web/pull/2414
## 3.0.0-beta.10 - 2021-09-09
### Changed
* `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377]
* Minimum supported Rust version (MSRV) is now 1.51.
- `ContentEncoding` is now marked `#[non_exhaustive]`. [#2377]
- Minimum supported Rust version (MSRV) is now 1.51.
### Fixed
* Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364]
* Remove `Into<Error>` bound on `Encoder` body types. [#2375]
* Fix quality parse error in Accept-Encoding header. [#2344]
- Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364]
- Remove `Into<Error>` bound on `Encoder` body types. [#2375]
- Fix quality parse error in Accept-Encoding header. [#2344]
[#2364]: https://github.com/actix/actix-web/pull/2364
[#2375]: https://github.com/actix/actix-web/pull/2375
@@ -146,15 +162,15 @@
## 3.0.0-beta.9 - 2021-08-09
### Fixed
* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
## 3.0.0-beta.8 - 2021-06-26
### Changed
* Change compression algorithm features flags. [#2250]
- Change compression algorithm features flags. [#2250]
### Removed
* `downcast` and `downcast_get_type_id` macros. [#2291]
- `downcast` and `downcast_get_type_id` macros. [#2291]
[#2291]: https://github.com/actix/actix-web/pull/2291
[#2250]: https://github.com/actix/actix-web/pull/2250
@@ -162,37 +178,37 @@
## 3.0.0-beta.7 - 2021-06-17
### Added
* Alias `body::Body` as `body::AnyBody`. [#2215]
* `BoxAnyBody`: a boxed message body with boxed errors. [#2183]
* Re-export `http` crate's `Error` type as `error::HttpError`. [#2171]
* Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171]
* Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171]
* `Response::into_body` that consumes response and returns body type. [#2201]
* `impl Default` for `Response`. [#2201]
* Add zstd support for `ContentEncoding`. [#2244]
- Alias `body::Body` as `body::AnyBody`. [#2215]
- `BoxAnyBody`: a boxed message body with boxed errors. [#2183]
- Re-export `http` crate's `Error` type as `error::HttpError`. [#2171]
- Re-export `StatusCode`, `Method`, `Version` and `Uri` at the crate root. [#2171]
- Re-export `ContentEncoding` and `ConnectionType` at the crate root. [#2171]
- `Response::into_body` that consumes response and returns body type. [#2201]
- `impl Default` for `Response`. [#2201]
- Add zstd support for `ContentEncoding`. [#2244]
### Changed
* The `MessageBody` trait now has an associated `Error` type. [#2183]
* All error trait bounds in server service builders have changed from `Into<Error>` to `Into<Response<AnyBody>>`. [#2253]
* All error trait bounds in message body and stream impls changed from `Into<Error>` to `Into<Box<dyn std::error::Error>>`. [#2253]
* Places in `Response` where `ResponseBody<B>` was received or returned now simply use `B`. [#2201]
* `header` mod is now public. [#2171]
* `uri` mod is now public. [#2171]
* Update `language-tags` to `0.3`.
* 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]
* `ResponseBuilder::message_body` now returns a `Result`. [#2201]
* Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253]
* `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226]
- The `MessageBody` trait now has an associated `Error` type. [#2183]
- All error trait bounds in server service builders have changed from `Into<Error>` to `Into<Response<AnyBody>>`. [#2253]
- All error trait bounds in message body and stream impls changed from `Into<Error>` to `Into<Box<dyn std::error::Error>>`. [#2253]
- Places in `Response` where `ResponseBody<B>` was received or returned now simply use `B`. [#2201]
- `header` mod is now public. [#2171]
- `uri` mod is now public. [#2171]
- Update `language-tags` to `0.3`.
- 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]
- `ResponseBuilder::message_body` now returns a `Result`. [#2201]
- Remove `Unpin` bound on `ResponseBuilder::streaming`. [#2253]
- `HttpServer::{listen_rustls(), bind_rustls()}` now honor the ALPN protocols in the configuation parameter. [#2226]
### Removed
* Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171]
* Down-casting for `MessageBody` types. [#2183]
* `error::Result` alias. [#2201]
* Error field from `Response` and `Response::error`. [#2205]
* `impl Future` for `Response`. [#2201]
* `Response::take_body` and old `Response::into_body` method that casted body type. [#2201]
* `InternalError` and all the error types it constructed. [#2215]
* Conversion (`impl Into`) of `Response<Body>` and `ResponseBuilder` to `Error`. [#2215]
- Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171]
- Down-casting for `MessageBody` types. [#2183]
- `error::Result` alias. [#2201]
- Error field from `Response` and `Response::error`. [#2205]
- `impl Future` for `Response`. [#2201]
- `Response::take_body` and old `Response::into_body` method that casted body type. [#2201]
- `InternalError` and all the error types it constructed. [#2215]
- Conversion (`impl Into`) of `Response<Body>` and `ResponseBuilder` to `Error`. [#2215]
[#2171]: https://github.com/actix/actix-web/pull/2171
[#2183]: https://github.com/actix/actix-web/pull/2183
@@ -207,27 +223,27 @@
## 3.0.0-beta.6 - 2021-04-17
### Added
* `impl<T: MessageBody> MessageBody for Pin<Box<T>>`. [#2152]
* `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159]
* Helper `body::to_bytes` for async collecting message body into Bytes. [#2158]
- `impl<T: MessageBody> MessageBody for Pin<Box<T>>`. [#2152]
- `Response::{ok, bad_request, not_found, internal_server_error}`. [#2159]
- Helper `body::to_bytes` for async collecting message body into Bytes. [#2158]
### Changes
* The type parameter of `Response` no longer has a default. [#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]
* Error enum types are marked `#[non_exhaustive]`. [#2161]
- The type parameter of `Response` no longer has a default. [#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]
- Error enum types are marked `#[non_exhaustive]`. [#2161]
### Removed
* `cookies` feature flag. [#2065]
* Top-level `cookies` mod (re-export). [#2065]
* `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065]
* `impl ResponseError for CookieParseError`. [#2065]
* Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148]
* `ResponseBuilder::json`. [#2148]
* `ResponseBuilder::{set_header, header}`. [#2148]
* `impl From<serde_json::Value> for Body`. [#2148]
* `Response::build_from`. [#2159]
* Most of the status code builders on `Response`. [#2159]
- `cookies` feature flag. [#2065]
- Top-level `cookies` mod (re-export). [#2065]
- `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065]
- `impl ResponseError for CookieParseError`. [#2065]
- Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148]
- `ResponseBuilder::json`. [#2148]
- `ResponseBuilder::{set_header, header}`. [#2148]
- `impl From<serde_json::Value> for Body`. [#2148]
- `Response::build_from`. [#2159]
- Most of the status code builders on `Response`. [#2159]
[#2065]: https://github.com/actix/actix-web/pull/2065
[#2148]: https://github.com/actix/actix-web/pull/2148
@@ -239,16 +255,16 @@
## 3.0.0-beta.5 - 2021-04-02
### Added
* `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081]
* `client::ConnectorService` as `client::Connector::finish` method's return type [#2081]
* `client::ConnectionIo` trait alias [#2081]
- `client::Connector::handshake_timeout` method for customizing TLS connection handshake timeout. [#2081]
- `client::ConnectorService` as `client::Connector::finish` method's return type [#2081]
- `client::ConnectionIo` trait alias [#2081]
### Changed
* `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063]
- `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063]
### Removed
* Common typed HTTP headers were moved to actix-web. [2094]
* `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127]
- Common typed HTTP headers were moved to actix-web. [2094]
- `ResponseError` impl for `actix_utils::timeout::TimeoutError`. [#2127]
[#2063]: https://github.com/actix/actix-web/pull/2063
[#2081]: https://github.com/actix/actix-web/pull/2081
@@ -258,13 +274,13 @@
## 3.0.0-beta.4 - 2021-03-08
### Changed
* Feature `cookies` is now optional and disabled by default. [#1981]
* `ws::hash_key` now returns array. [#2035]
* `ResponseBuilder::json` now takes `impl Serialize`. [#2052]
- Feature `cookies` is now optional and disabled by default. [#1981]
- `ws::hash_key` now returns array. [#2035]
- `ResponseBuilder::json` now takes `impl Serialize`. [#2052]
### Removed
* Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994]
* `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994]
- Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994]
- `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994]
[#1981]: https://github.com/actix/actix-web/pull/1981
[#1994]: https://github.com/actix/actix-web/pull/1994
@@ -273,48 +289,48 @@
## 3.0.0-beta.3 - 2021-02-10
* No notable changes.
- No notable changes.
## 3.0.0-beta.2 - 2021-02-10
### Added
* `TryIntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869]
* `ResponseBuilder::insert_header` method which allows using typed headers. [#1869]
* `ResponseBuilder::append_header` method which allows using typed headers. [#1869]
* `TestRequest::insert_header` method which allows using typed headers. [#1869]
* `ContentEncoding` implements all necessary header traits. [#1912]
* `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964]
* `HeaderMap::drain` as an efficient draining iterator. [#1964]
* Implement `IntoIterator` for owned `HeaderMap`. [#1964]
* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
- `TryIntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869]
- `ResponseBuilder::insert_header` method which allows using typed headers. [#1869]
- `ResponseBuilder::append_header` method which allows using typed headers. [#1869]
- `TestRequest::insert_header` method which allows using typed headers. [#1869]
- `ContentEncoding` implements all necessary header traits. [#1912]
- `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964]
- `HeaderMap::drain` as an efficient draining iterator. [#1964]
- Implement `IntoIterator` for owned `HeaderMap`. [#1964]
- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
### Changed
* `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed
- `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed
`mime` types. [#1894]
* Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std
- Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std
`TryInto` trait. [#1894]
* `Extensions::insert` returns Option of replaced item. [#1904]
* Remove `HttpResponseBuilder::json2()`. [#1903]
* Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903]
* `client::error::ConnectError` Resolver variant contains `Box<dyn std::error::Error>` type. [#1905]
* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905]
* Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool
- `Extensions::insert` returns Option of replaced item. [#1904]
- Remove `HttpResponseBuilder::json2()`. [#1903]
- Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903]
- `client::error::ConnectError` Resolver variant contains `Box<dyn std::error::Error>` type. [#1905]
- `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905]
- Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool
is dead. [#1957]
* `HeaderMap::len` now returns number of values instead of number of keys. [#1964]
* `HeaderMap::insert` now returns iterator of removed values. [#1964]
* `HeaderMap::remove` now returns iterator of removed values. [#1964]
- `HeaderMap::len` now returns number of values instead of number of keys. [#1964]
- `HeaderMap::insert` now returns iterator of removed values. [#1964]
- `HeaderMap::remove` now returns iterator of removed values. [#1964]
### Removed
* `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869]
* `ResponseBuilder::set_header`; use `ResponseBuilder::insert_header`. [#1869]
* `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869]
* `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869]
* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869]
* `actors` optional feature. [#1969]
* `ResponseError` impl for `actix::MailboxError`. [#1969]
- `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869]
- `ResponseBuilder::set_header`; use `ResponseBuilder::insert_header`. [#1869]
- `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869]
- `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869]
- `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869]
- `actors` optional feature. [#1969]
- `ResponseError` impl for `actix::MailboxError`. [#1969]
### Documentation
* Vastly improve docs and add examples for `HeaderMap`. [#1964]
- Vastly improve docs and add examples for `HeaderMap`. [#1964]
[#1869]: https://github.com/actix/actix-web/pull/1869
[#1894]: https://github.com/actix/actix-web/pull/1894
@@ -329,24 +345,24 @@
## 3.0.0-beta.1 - 2021-01-07
### Added
* Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`.
- Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`.
### Changed
* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
* Bumped `rand` to `0.8`.
* Update `bytes` to `1.0`. [#1813]
* Update `h2` to `0.3`. [#1813]
* The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864]
- Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
- Bumped `rand` to `0.8`.
- Update `bytes` to `1.0`. [#1813]
- Update `h2` to `0.3`. [#1813]
- The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864]
### Removed
* Deprecated `on_connect` methods have been removed. Prefer the new
- Deprecated `on_connect` methods have been removed. Prefer the new
`on_connect_ext` technique. [#1857]
* Remove `ResponseError` impl for `actix::actors::resolver::ResolverError`
- Remove `ResponseError` impl for `actix::actors::resolver::ResolverError`
due to deprecate of resolver actor. [#1813]
* Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`.
- Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`.
due to the removal of this type from `tokio-openssl` crate. openssl handshake
error would return as `ConnectError::SslError`. [#1813]
* Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`.
- Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`.
Due to this change `actix_threadpool::BlockingError` type is moved into
`actix_http::error` module. [#1878]
@@ -358,20 +374,20 @@
## 2.2.1 - 2021-08-09
### Fixed
* Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
- Potential HTTP request smuggling vulnerabilities. [RUSTSEC-2021-0081](https://github.com/rustsec/advisory-db/pull/977)
## 2.2.0 - 2020-11-25
### Added
* HttpResponse builders for 1xx status codes. [#1768]
* `Accept::mime_precedence` and `Accept::mime_preference`. [#1793]
* `TryFrom<u16>` and `TryFrom<f32>` for `http::header::Quality`. [#1797]
- HttpResponse builders for 1xx status codes. [#1768]
- `Accept::mime_precedence` and `Accept::mime_preference`. [#1793]
- `TryFrom<u16>` and `TryFrom<f32>` for `http::header::Quality`. [#1797]
### Fixed
* Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767]
- Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767]
### Changed
* Upgrade `serde_urlencoded` to `0.7`. [#1773]
- Upgrade `serde_urlencoded` to `0.7`. [#1773]
[#1773]: https://github.com/actix/actix-web/pull/1773
[#1767]: https://github.com/actix/actix-web/pull/1767
@@ -382,12 +398,12 @@
## 2.1.0 - 2020-10-30
### Added
* Added more flexible `on_connect_ext` methods for on-connect handling. [#1754]
- Added more flexible `on_connect_ext` methods for on-connect handling. [#1754]
### Changed
* Upgrade `base64` to `0.13`. [#1744]
* Upgrade `pin-project` to `1.0`. [#1733]
* Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760]
- Upgrade `base64` to `0.13`. [#1744]
- Upgrade `pin-project` to `1.0`. [#1733]
- Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760]
[#1760]: https://github.com/actix/actix-web/pull/1760
[#1754]: https://github.com/actix/actix-web/pull/1754
@@ -396,28 +412,28 @@
## 2.0.0 - 2020-09-11
* No significant changes from `2.0.0-beta.4`.
- No significant changes from `2.0.0-beta.4`.
## 2.0.0-beta.4 - 2020-09-09
### Changed
* Update actix-codec and actix-utils dependencies.
* Update actix-connect and actix-tls dependencies.
- Update actix-codec and actix-utils dependencies.
- Update actix-connect and actix-tls dependencies.
## 2.0.0-beta.3 - 2020-08-14
### Fixed
* Memory leak of `client::pool::ConnectorPoolSupport`. [#1626]
- Memory leak of `client::pool::ConnectorPoolSupport`. [#1626]
[#1626]: https://github.com/actix/actix-web/pull/1626
## 2.0.0-beta.2 - 2020-07-21
### Fixed
* Potential UB in h1 decoder using uninitialized memory. [#1614]
- Potential UB in h1 decoder using uninitialized memory. [#1614]
### Changed
* Fix illegal chunked encoding. [#1615]
- Fix illegal chunked encoding. [#1615]
[#1614]: https://github.com/actix/actix-web/pull/1614
[#1615]: https://github.com/actix/actix-web/pull/1615
@@ -425,10 +441,10 @@
## 2.0.0-beta.1 - 2020-07-11
### Changed
* Migrate cookie handling to `cookie` crate. [#1558]
* Update `sha-1` to 0.9. [#1586]
* Fix leak in client pool. [#1580]
* MSRV is now 1.41.1.
- Migrate cookie handling to `cookie` crate. [#1558]
- Update `sha-1` to 0.9. [#1586]
- Fix leak in client pool. [#1580]
- MSRV is now 1.41.1.
[#1558]: https://github.com/actix/actix-web/pull/1558
[#1586]: https://github.com/actix/actix-web/pull/1586
@@ -437,15 +453,15 @@
## 2.0.0-alpha.4 - 2020-05-21
### Changed
* Bump minimum supported Rust version to 1.40
* content_length function is removed, and you can set Content-Length by calling
- Bump minimum supported Rust version to 1.40
- content_length function is removed, and you can set Content-Length by calling
no_chunking function [#1439]
* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
- `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
`u64` instead of a `usize`.
* Update `base64` dependency to 0.12
- Update `base64` dependency to 0.12
### Fixed
* Support parsing of `SameSite=None` [#1503]
- Support parsing of `SameSite=None` [#1503]
[#1439]: https://github.com/actix/actix-web/pull/1439
[#1503]: https://github.com/actix/actix-web/pull/1503
@@ -453,13 +469,13 @@
## 2.0.0-alpha.3 - 2020-05-08
### Fixed
* Correct spelling of ConnectError::Unresolved [#1487]
* Fix a mistake in the encoding of websocket continuation messages wherein
- Correct spelling of ConnectError::Unresolved [#1487]
- Fix a mistake in the encoding of websocket continuation messages wherein
Item::FirstText and Item::FirstBinary are each encoded as the other.
### Changed
* Implement `std::error::Error` for our custom errors [#1422]
* Remove `failure` support for `ResponseError` since that crate
- Implement `std::error::Error` for our custom errors [#1422]
- Remove `failure` support for `ResponseError` since that crate
will be deprecated in the near future.
[#1422]: https://github.com/actix/actix-web/pull/1422
@@ -468,12 +484,12 @@
## 2.0.0-alpha.2 - 2020-03-07
### Changed
* Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395]
* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB
- Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395]
- Change default initial window size and connection window size for HTTP2 to 2MB and 1MB
respectively to improve download speed for awc when downloading large objects. [#1394]
* client::Connector accepts initial_window_size and initial_connection_window_size
- client::Connector accepts initial_window_size and initial_connection_window_size
HTTP2 configuration. [#1394]
* client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394]
- client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394]
[#1394]: https://github.com/actix/actix-web/pull/1394
[#1395]: https://github.com/actix/actix-web/pull/1395
@@ -481,61 +497,61 @@
## 2.0.0-alpha.1 - 2020-02-27
### Changed
* Update the `time` dependency to 0.2.7.
* Moved actors messages support from actix crate, enabled with feature `actors`.
* Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of
- Update the `time` dependency to 0.2.7.
- Moved actors messages support from actix crate, enabled with feature `actors`.
- Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of
`&mut self` in the poll_next().
* MessageBody is not implemented for &'static [u8] anymore.
- MessageBody is not implemented for &'static [u8] anymore.
### Fixed
* Allow `SameSite=None` cookies to be sent in a response.
- Allow `SameSite=None` cookies to be sent in a response.
## 1.0.1 - 2019-12-20
### Fixed
* Poll upgrade service's readiness from HTTP service handlers
* Replace brotli with brotli2 #1224
- Poll upgrade service's readiness from HTTP service handlers
- Replace brotli with brotli2 #1224
## 1.0.0 - 2019-12-13
### Added
* Add websockets continuation frame support
- Add websockets continuation frame support
### Changed
* Replace `flate2-xxx` features with `compress`
- Replace `flate2-xxx` features with `compress`
## 1.0.0-alpha.5 - 2019-12-09
### Fixed
* Check `Upgrade` service readiness before calling it
* Fix buffer remaining capacity calculation
- Check `Upgrade` service readiness before calling it
- Fix buffer remaining capacity calculation
### Changed
* Websockets: Ping and Pong should have binary data #1049
- Websockets: Ping and Pong should have binary data #1049
## 1.0.0-alpha.4 - 2019-12-08
### Added
* Add impl ResponseBuilder for Error
- Add impl ResponseBuilder for Error
### Changed
* Use rust based brotli compression library
- Use rust based brotli compression library
## 1.0.0-alpha.3 - 2019-12-07
### Changed
* Migrate to tokio 0.2
* Migrate to `std::future`
- Migrate to tokio 0.2
- Migrate to `std::future`
## 0.2.11 - 2019-11-06
### Added
* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body()
* Add an additional `filename*` param in the `Content-Disposition` header of
- Add support for serde_json::Value to be passed as argument to ResponseBuilder.body()
- Add an additional `filename*` param in the `Content-Disposition` header of
`actix_files::NamedFile` to be more compatible. (#1151)
* Allow to use `std::convert::Infallible` as `actix_http::error::Error`
- Allow to use `std::convert::Infallible` as `actix_http::error::Error`
### Fixed
* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain;
- To be compatible with non-English error responses, `ResponseError` rendered with `text/plain;
charset=utf-8` header [#1118]
[#1878]: https://github.com/actix/actix-web/pull/1878
@@ -543,169 +559,169 @@
## 0.2.10 - 2019-09-11
### Added
* Add support for sending HTTP requests with `Rc<RequestHead>` in addition to sending HTTP requests
- Add support for sending HTTP requests with `Rc<RequestHead>` in addition to sending HTTP requests
with `RequestHead`
### Fixed
* h2 will use error response #1080
* on_connect result isn't added to request extensions for http2 requests #1009
- h2 will use error response #1080
- on_connect result isn't added to request extensions for http2 requests #1009
## 0.2.9 - 2019-08-13
### Changed
* Dropped the `byteorder`-dependency in favor of `stdlib`-implementation
* Update percent-encoding to 2.1
* Update serde_urlencoded to 0.6.1
- Dropped the `byteorder`-dependency in favor of `stdlib`-implementation
- Update percent-encoding to 2.1
- Update serde_urlencoded to 0.6.1
### Fixed
* Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031)
- Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031)
## 0.2.8 - 2019-08-01
### Added
* Add `rustls` support
* Add `Clone` impl for `HeaderMap`
- Add `rustls` support
- Add `Clone` impl for `HeaderMap`
### Fixed
* awc client panic #1016
* Invalid response with compression middleware enabled, but compression-related features
- awc client panic #1016
- Invalid response with compression middleware enabled, but compression-related features
disabled #997
## 0.2.7 - 2019-07-18
### Added
* Add support for downcasting response errors #986
- Add support for downcasting response errors #986
## 0.2.6 - 2019-07-17
### Changed
* Replace `ClonableService` with local copy
* Upgrade `rand` dependency version to 0.7
- Replace `ClonableService` with local copy
- Upgrade `rand` dependency version to 0.7
## 0.2.5 - 2019-06-28
### Added
* Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946
- Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946
### Changed
* Use `encoding_rs` crate instead of unmaintained `encoding` crate
* Add `Copy` and `Clone` impls for `ws::Codec`
- Use `encoding_rs` crate instead of unmaintained `encoding` crate
- Add `Copy` and `Clone` impls for `ws::Codec`
## 0.2.4 - 2019-06-16
### Fixed
* Do not compress NoContent (204) responses #918
- Do not compress NoContent (204) responses #918
## 0.2.3 - 2019-06-02
### Added
* Debug impl for ResponseBuilder
* From SizedStream and BodyStream for Body
- Debug impl for ResponseBuilder
- From SizedStream and BodyStream for Body
### Changed
* SizedStream uses u64
- SizedStream uses u64
## 0.2.2 - 2019-05-29
### Fixed
* Parse incoming stream before closing stream on disconnect #868
- Parse incoming stream before closing stream on disconnect #868
## 0.2.1 - 2019-05-25
### Fixed
* Handle socket read disconnect
- Handle socket read disconnect
## 0.2.0 - 2019-05-12
### Changed
* Update actix-service to 0.4
* Expect and upgrade services accept `ServerConfig` config.
- Update actix-service to 0.4
- Expect and upgrade services accept `ServerConfig` config.
### Deleted
* `OneRequest` service
- `OneRequest` service
## 0.1.5 - 2019-05-04
### Fixed
* Clean up response extensions in response pool #817
- Clean up response extensions in response pool #817
## 0.1.4 - 2019-04-24
### Added
* Allow to render h1 request headers in `Camel-Case`
- Allow to render h1 request headers in `Camel-Case`
### Fixed
* Read until eof for http/1.0 responses #771
- Read until eof for http/1.0 responses #771
## 0.1.3 - 2019-04-23
### Fixed
* Fix http client pool management
* Fix http client wait queue management #794
- Fix http client pool management
- Fix http client wait queue management #794
## 0.1.2 - 2019-04-23
### Fixed
* Fix BorrowMutError panic in client connector #793
- Fix BorrowMutError panic in client connector #793
## 0.1.1 - 2019-04-19
### Changed
* Cookie::max_age() accepts value in seconds
* Cookie::max_age_time() accepts value in time::Duration
* Allow to specify server address for client connector
- Cookie::max_age() accepts value in seconds
- Cookie::max_age_time() accepts value in time::Duration
- Allow to specify server address for client connector
## 0.1.0 - 2019-04-16
### Added
* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr`
- Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr`
### Changed
* `actix_http::encoding` always available
* use trust-dns-resolver 0.11.0
- `actix_http::encoding` always available
- use trust-dns-resolver 0.11.0
## 0.1.0-alpha.5 - 2019-04-12
### Added
* Allow to use custom service for upgrade requests
* Added `h1::SendResponse` future.
- Allow to use custom service for upgrade requests
- Added `h1::SendResponse` future.
### Changed
* MessageBody::length() renamed to MessageBody::size() for consistency
* ws handshake verification functions take RequestHead instead of Request
- MessageBody::length() renamed to MessageBody::size() for consistency
- ws handshake verification functions take RequestHead instead of Request
## 0.1.0-alpha.4 - 2019-04-08
### Added
* Allow to use custom `Expect` handler
* Add minimal `std::error::Error` impl for `Error`
- Allow to use custom `Expect` handler
- Add minimal `std::error::Error` impl for `Error`
### Changed
* Export IntoHeaderValue
* Render error and return as response body
* Use thread pool for response body compression
- Export IntoHeaderValue
- Render error and return as response body
- Use thread pool for response body compression
### Deleted
* Removed PayloadBuffer
- Removed PayloadBuffer
## 0.1.0-alpha.3 - 2019-04-02
### Added
* Warn when an unsealed private cookie isn't valid UTF-8
- Warn when an unsealed private cookie isn't valid UTF-8
### Fixed
* Rust 1.31.0 compatibility
* Preallocate read buffer for h1 codec
* Detect socket disconnection during protocol selection
- Rust 1.31.0 compatibility
- Preallocate read buffer for h1 codec
- Detect socket disconnection during protocol selection
## 0.1.0-alpha.2 - 2019-03-29
### Added
* Added ws::Message::Nop, no-op websockets message
- Added ws::Message::Nop, no-op websockets message
### Changed
* Do not use thread pool for decompression if chunk size is smaller than 2048.
- Do not use thread pool for decompression if chunk size is smaller than 2048.
## 0.1.0-alpha.1 - 2019-03-28
* Initial impl
- Initial impl

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-http"
version = "3.0.0-beta.16"
version = "3.0.0-beta.17"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "HTTP primitives for the Actix ecosystem"
keywords = ["actix", "http", "framework", "async", "futures"]
@@ -55,12 +55,11 @@ bytestring = "1"
derive_more = "0.99.5"
encoding_rs = "0.8"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
futures-task = { version = "0.3.7", default-features = false, features = ["alloc"] }
h2 = "0.3.9"
http = "0.2.5"
httparse = "1.5.1"
httpdate = "1.0.1"
itoa = "0.4"
itoa = "1"
language-tags = "0.3"
local-channel = "0.1"
log = "0.4"
@@ -68,11 +67,11 @@ mime = "0.3"
percent-encoding = "2.1"
pin-project-lite = "0.2"
rand = "0.8"
sha-1 = "0.9"
sha-1 = "0.10"
smallvec = "1.6.1"
# tls
actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true }
actix-tls = { version = "3.0.0", default-features = false, optional = true }
# compression
brotli2 = { version="0.3.2", optional = true }
@@ -80,10 +79,10 @@ flate2 = { version = "1.0.13", optional = true }
zstd = { version = "0.9", optional = true }
[dev-dependencies]
actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] }
actix-server = "2.0.0-rc.1"
actix-tls = { version = "3.0.0-rc.1", features = ["openssl"] }
actix-web = "4.0.0-beta.15"
actix-http-test = { version = "3.0.0-beta.10", features = ["openssl"] }
actix-server = "2.0.0-rc.2"
actix-tls = { version = "3.0.0", features = ["openssl"] }
actix-web = "4.0.0-beta.16"
async-stream = "0.3"
criterion = { version = "0.3", features = ["html_reports"] }
@@ -97,7 +96,7 @@ serde_json = "1.0"
static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.9" }
tls-rustls = { package = "rustls", version = "0.20.0" }
tokio = { version = "1.2", features = ["net", "rt", "macros"] }
tokio = { version = "1.8", features = ["net", "rt", "macros"] }
[[example]]
name = "ws"

View File

@@ -3,11 +3,11 @@
> HTTP primitives for the Actix ecosystem.
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.16)](https://docs.rs/actix-http/3.0.0-beta.16)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.17)](https://docs.rs/actix-http/3.0.0-beta.17)
[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.16/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.16)
[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.17/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.17)
[![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
@@ -54,8 +54,8 @@ async fn main() -> io::Result<()> {
This project is licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
- MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
at your option.

View File

@@ -36,6 +36,7 @@ where
<S::Service as Service<Request>>::Future: 'static,
{
/// Create instance of `ServiceConfigBuilder`
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
HttpServiceBuilder {
keep_alive: KeepAlive::Timeout(5),

View File

@@ -28,11 +28,14 @@ use crate::{
const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049;
pub struct Decoder<S> {
decoder: Option<ContentDecoder>,
stream: S,
eof: bool,
fut: Option<JoinHandle<Result<(Option<Bytes>, ContentDecoder), io::Error>>>,
pin_project_lite::pin_project! {
pub struct Decoder<S> {
decoder: Option<ContentDecoder>,
#[pin]
stream: S,
eof: bool,
fut: Option<JoinHandle<Result<(Option<Bytes>, ContentDecoder), io::Error>>>,
}
}
impl<S> Decoder<S>
@@ -89,42 +92,44 @@ where
impl<S> Stream for Decoder<S>
where
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
S: Stream<Item = Result<Bytes, PayloadError>>,
{
type Item = Result<Bytes, PayloadError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut this = self.project();
loop {
if let Some(ref mut fut) = self.fut {
if let Some(ref mut fut) = this.fut {
let (chunk, decoder) =
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
self.decoder = Some(decoder);
self.fut.take();
*this.decoder = Some(decoder);
this.fut.take();
if let Some(chunk) = chunk {
return Poll::Ready(Some(Ok(chunk)));
}
}
if self.eof {
if *this.eof {
return Poll::Ready(None);
}
match ready!(Pin::new(&mut self.stream).poll_next(cx)) {
match ready!(this.stream.as_mut().poll_next(cx)) {
Some(Err(err)) => return Poll::Ready(Some(Err(err))),
Some(Ok(chunk)) => {
if let Some(mut decoder) = self.decoder.take() {
if let Some(mut decoder) = this.decoder.take() {
if chunk.len() < MAX_CHUNK_SIZE_DECODE_IN_PLACE {
let chunk = decoder.feed_data(chunk)?;
self.decoder = Some(decoder);
*this.decoder = Some(decoder);
if let Some(chunk) = chunk {
return Poll::Ready(Some(Ok(chunk)));
}
} else {
self.fut = Some(spawn_blocking(move || {
*this.fut = Some(spawn_blocking(move || {
let chunk = decoder.feed_data(chunk)?;
Ok((chunk, decoder))
}));
@@ -137,9 +142,9 @@ where
}
None => {
self.eof = true;
*this.eof = true;
return if let Some(mut decoder) = self.decoder.take() {
return if let Some(mut decoder) = this.decoder.take() {
match decoder.feed_eof() {
Ok(Some(res)) => Poll::Ready(Some(Ok(res))),
Ok(None) => Poll::Ready(None),

View File

@@ -5,13 +5,15 @@ use bitflags::bitflags;
use bytes::{Bytes, BytesMut};
use http::{Method, Version};
use super::decoder::{PayloadDecoder, PayloadItem, PayloadType};
use super::{decoder, encoder, reserve_readbuf};
use super::{Message, MessageType};
use crate::body::BodySize;
use crate::config::ServiceConfig;
use crate::error::{ParseError, PayloadError};
use crate::message::{ConnectionType, RequestHeadType, ResponseHead};
use super::{
decoder::{self, PayloadDecoder, PayloadItem, PayloadType},
encoder, reserve_readbuf, Message, MessageType,
};
use crate::{
body::BodySize,
error::{ParseError, PayloadError},
ConnectionType, RequestHeadType, ResponseHead, ServiceConfig,
};
bitflags! {
struct Flags: u8 {

View File

@@ -5,15 +5,13 @@ use bitflags::bitflags;
use bytes::BytesMut;
use http::{Method, Version};
use super::decoder::{PayloadDecoder, PayloadItem, PayloadType};
use super::{decoder, encoder};
use super::{Message, MessageType};
use crate::body::BodySize;
use crate::config::ServiceConfig;
use crate::error::ParseError;
use crate::message::ConnectionType;
use crate::request::Request;
use crate::response::Response;
use super::{
decoder::{self, PayloadDecoder, PayloadItem, PayloadType},
encoder, Message, MessageType,
};
use crate::{
body::BodySize, error::ParseError, ConnectionType, Request, Response, ServiceConfig,
};
bitflags! {
struct Flags: u8 {
@@ -199,7 +197,7 @@ mod tests {
use http::Method;
use super::*;
use crate::HttpMessage;
use crate::HttpMessage as _;
#[actix_rt::test]
async fn test_http_request_chunked_payload_and_next_message() {

View File

@@ -2,17 +2,14 @@ use std::{convert::TryFrom, io, marker::PhantomData, mem::MaybeUninit, task::Pol
use actix_codec::Decoder;
use bytes::{Bytes, BytesMut};
use http::header::{HeaderName, HeaderValue};
use http::{header, Method, StatusCode, Uri, Version};
use http::{
header::{self, HeaderName, HeaderValue},
Method, StatusCode, Uri, Version,
};
use log::{debug, error, trace};
use super::chunked::ChunkedState;
use crate::{
error::ParseError,
header::HeaderMap,
message::{ConnectionType, ResponseHead},
request::Request,
};
use crate::{error::ParseError, header::HeaderMap, ConnectionType, Request, ResponseHead};
pub(crate) const MAX_BUFFER_SIZE: usize = 131_072;
const MAX_HEADERS: usize = 96;
@@ -50,7 +47,7 @@ pub(crate) enum PayloadLength {
}
pub(crate) trait MessageType: Sized {
fn set_connection_type(&mut self, ctype: Option<ConnectionType>);
fn set_connection_type(&mut self, conn_type: Option<ConnectionType>);
fn set_expect(&mut self);
@@ -193,8 +190,8 @@ pub(crate) trait MessageType: Sized {
}
impl MessageType for Request {
fn set_connection_type(&mut self, ctype: Option<ConnectionType>) {
if let Some(ctype) = ctype {
fn set_connection_type(&mut self, conn_type: Option<ConnectionType>) {
if let Some(ctype) = conn_type {
self.head_mut().set_connection_type(ctype);
}
}
@@ -278,8 +275,8 @@ impl MessageType for Request {
}
impl MessageType for ResponseHead {
fn set_connection_type(&mut self, ctype: Option<ConnectionType>) {
if let Some(ctype) = ctype {
fn set_connection_type(&mut self, conn_type: Option<ConnectionType>) {
if let Some(ctype) = conn_type {
ResponseHead::set_connection_type(self, ctype);
}
}

View File

@@ -646,10 +646,11 @@ where
Payload is attached to Request and passed to Service::call
where the state can be collected and consumed.
*/
let (ps, pl) = Payload::create(false);
let (req1, _) = req.replace_payload(crate::Payload::H1(pl));
let (sender, payload) = Payload::create(false);
let (req1, _) =
req.replace_payload(crate::Payload::H1 { payload });
req = req1;
*this.payload = Some(ps);
*this.payload = Some(sender);
}
// Request has no payload.

View File

@@ -1,19 +1,19 @@
use std::io::Write;
use std::marker::PhantomData;
use std::ptr::copy_nonoverlapping;
use std::slice::from_raw_parts_mut;
use std::{cmp, io};
use std::{
cmp,
io::{self, Write as _},
marker::PhantomData,
ptr::copy_nonoverlapping,
slice::from_raw_parts_mut,
};
use bytes::{BufMut, BytesMut};
use crate::{
body::BodySize,
config::ServiceConfig,
header::{map::Value, HeaderMap, HeaderName},
header::{CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING},
helpers,
message::{ConnectionType, RequestHeadType},
Response, StatusCode, Version,
header::{
map::Value, HeaderMap, HeaderName, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
},
helpers, ConnectionType, RequestHeadType, Response, ServiceConfig, StatusCode, Version,
};
const AVERAGE_HEADER_SIZE: usize = 30;

View File

@@ -1,8 +1,7 @@
use actix_service::{Service, ServiceFactory};
use actix_utils::future::{ready, Ready};
use crate::error::Error;
use crate::request::Request;
use crate::{Error, Request};
pub struct ExpectHandler;

View File

@@ -59,7 +59,7 @@ pub(crate) fn reserve_readbuf(src: &mut BytesMut) {
#[cfg(test)]
mod tests {
use super::*;
use crate::request::Request;
use crate::Request;
impl Message<Request> {
pub fn message(self) -> Request {

View File

@@ -1,9 +1,12 @@
//! Payload stream
use std::cell::RefCell;
use std::collections::VecDeque;
use std::pin::Pin;
use std::rc::{Rc, Weak};
use std::task::{Context, Poll, Waker};
use std::{
cell::RefCell,
collections::VecDeque,
pin::Pin,
rc::{Rc, Weak},
task::{Context, Poll, Waker},
};
use bytes::Bytes;
use futures_core::Stream;
@@ -22,39 +25,32 @@ pub enum PayloadStatus {
/// Buffered stream of bytes chunks
///
/// Payload stores chunks in a vector. First chunk can be received with
/// `.readany()` method. Payload stream is not thread safe. Payload does not
/// notify current task when new data is available.
/// Payload stores chunks in a vector. First chunk can be received with `poll_next`. Payload does
/// not notify current task when new data is available.
///
/// Payload stream can be used as `Response` body stream.
/// Payload can be used as `Response` body stream.
#[derive(Debug)]
pub struct Payload {
inner: Rc<RefCell<Inner>>,
}
impl Payload {
/// Create payload stream.
/// Creates a payload stream.
///
/// This method construct two objects responsible for bytes stream
/// generation.
///
/// * `PayloadSender` - *Sender* side of the stream
///
/// * `Payload` - *Receiver* side of the stream
/// This method construct two objects responsible for bytes stream generation:
/// - `PayloadSender` - *Sender* side of the stream
/// - `Payload` - *Receiver* side of the stream
pub fn create(eof: bool) -> (PayloadSender, Payload) {
let shared = Rc::new(RefCell::new(Inner::new(eof)));
(
PayloadSender {
inner: Rc::downgrade(&shared),
},
PayloadSender::new(Rc::downgrade(&shared)),
Payload { inner: shared },
)
}
/// Create empty payload
#[doc(hidden)]
pub fn empty() -> Payload {
/// Creates an empty payload.
pub(crate) fn empty() -> Payload {
Payload {
inner: Rc::new(RefCell::new(Inner::new(true))),
}
@@ -77,14 +73,6 @@ impl Payload {
pub fn unread_data(&mut self, data: Bytes) {
self.inner.borrow_mut().unread_data(data);
}
#[inline]
pub fn readany(
&mut self,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, PayloadError>>> {
self.inner.borrow_mut().readany(cx)
}
}
impl Stream for Payload {
@@ -94,7 +82,7 @@ impl Stream for Payload {
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, PayloadError>>> {
self.inner.borrow_mut().readany(cx)
Pin::new(&mut *self.inner.borrow_mut()).poll_next(cx)
}
}
@@ -104,6 +92,10 @@ pub struct PayloadSender {
}
impl PayloadSender {
fn new(inner: Weak<RefCell<Inner>>) -> Self {
Self { inner }
}
#[inline]
pub fn set_error(&mut self, err: PayloadError) {
if let Some(shared) = self.inner.upgrade() {
@@ -227,7 +219,10 @@ impl Inner {
self.len
}
fn readany(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, PayloadError>>> {
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, PayloadError>>> {
if let Some(data) = self.items.pop_front() {
self.len -= data.len();
self.need_read = self.len < MAX_BUFFER_SIZE;
@@ -257,8 +252,18 @@ impl Inner {
#[cfg(test)]
mod tests {
use super::*;
use std::panic::{RefUnwindSafe, UnwindSafe};
use actix_utils::future::poll_fn;
use static_assertions::{assert_impl_all, assert_not_impl_any};
use super::*;
assert_impl_all!(Payload: Unpin);
assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe);
assert_impl_all!(Inner: Unpin, Send, Sync);
assert_not_impl_any!(Inner: UnwindSafe, RefUnwindSafe);
#[actix_rt::test]
async fn test_unread_data() {
@@ -270,7 +275,10 @@ mod tests {
assert_eq!(
Bytes::from("data"),
poll_fn(|cx| payload.readany(cx)).await.unwrap().unwrap()
poll_fn(|cx| Pin::new(&mut payload).poll_next(cx))
.await
.unwrap()
.unwrap()
);
}
}

View File

@@ -2,9 +2,7 @@ use actix_codec::Framed;
use actix_service::{Service, ServiceFactory};
use futures_core::future::LocalBoxFuture;
use crate::error::Error;
use crate::h1::Codec;
use crate::request::Request;
use crate::{h1::Codec, Error, Request};
pub struct UpgradeHandler;

View File

@@ -9,9 +9,8 @@ use pin_project_lite::pin_project;
use crate::{
body::{BodySize, MessageBody},
error::Error,
h1::{Codec, Message},
response::Response,
Error, Response,
};
pin_project! {
@@ -46,7 +45,7 @@ where
impl<T, B> Future for SendResponse<T, B>
where
T: AsyncRead + AsyncWrite + Unpin,
B: MessageBody + Unpin,
B: MessageBody,
B::Error: Into<Error>,
{
type Output = Result<Framed<T, Codec>, Error>;
@@ -82,7 +81,7 @@ where
// body is done when item is None
body_done = item.is_none();
if body_done {
let _ = this.body.take();
this.body.set(None);
}
let framed = this.framed.as_mut().as_pin_mut().unwrap();
framed

View File

@@ -108,8 +108,8 @@ where
match Pin::new(&mut this.connection).poll_accept(cx)? {
Poll::Ready(Some((req, tx))) => {
let (parts, body) = req.into_parts();
let pl = crate::h2::Payload::new(body);
let pl = Payload::H2(pl);
let payload = crate::h2::Payload::new(body);
let pl = Payload::H2 { payload };
let mut req = Request::with_payload(pl);
let head = req.head_mut();
@@ -288,9 +288,11 @@ fn prepare_response(
let _ = match size {
BodySize::None | BodySize::Stream => None,
BodySize::Sized(0) => res
.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0")),
BodySize::Sized(0) => {
#[allow(clippy::declare_interior_mutable_const)]
const HV_ZERO: HeaderValue = HeaderValue::from_static("0");
res.headers_mut().insert(CONTENT_LENGTH, HV_ZERO)
}
BodySize::Sized(len) => {
let mut buf = itoa::Buffer::new();

View File

@@ -98,3 +98,14 @@ where
}
}
}
#[cfg(test)]
mod tests {
use std::panic::{RefUnwindSafe, UnwindSafe};
use static_assertions::assert_impl_all;
use super::*;
assert_impl_all!(Payload: Unpin, Send, Sync, UnwindSafe, RefUnwindSafe);
}

View File

@@ -306,8 +306,11 @@ impl HeaderMap {
/// assert_eq!(set_cookies_iter.next().unwrap(), "two=2");
/// assert!(set_cookies_iter.next().is_none());
/// ```
pub fn get_all(&self, key: impl AsHeaderName) -> GetAll<'_> {
GetAll::new(self.get_value(key))
pub fn get_all(&self, key: impl AsHeaderName) -> std::slice::Iter<'_, HeaderValue> {
match self.get_value(key) {
Some(value) => value.iter(),
None => (&[]).iter(),
}
}
// TODO: get_all_mut ?
@@ -602,52 +605,6 @@ impl<'a> IntoIterator for &'a HeaderMap {
}
}
/// Iterator over borrowed values with the same associated name.
///
/// See [`HeaderMap::get_all`].
#[derive(Debug)]
pub struct GetAll<'a> {
idx: usize,
value: Option<&'a Value>,
}
impl<'a> GetAll<'a> {
fn new(value: Option<&'a Value>) -> Self {
Self { idx: 0, value }
}
}
impl<'a> Iterator for GetAll<'a> {
type Item = &'a HeaderValue;
fn next(&mut self) -> Option<Self::Item> {
let val = self.value?;
match val.get(self.idx) {
Some(val) => {
self.idx += 1;
Some(val)
}
None => {
// current index is none; remove value to fast-path future next calls
self.value = None;
None
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self.value {
Some(val) => (val.len(), Some(val.len())),
None => (0, Some(0)),
}
}
}
impl ExactSizeIterator for GetAll<'_> {}
impl iter::FusedIterator for GetAll<'_> {}
/// Iterator over removed, owned values with the same associated name.
///
/// Returned from methods that remove or replace items. See [`HeaderMap::insert`]
@@ -895,7 +852,7 @@ mod tests {
assert_impl_all!(HeaderMap: IntoIterator);
assert_impl_all!(Keys<'_>: Iterator, ExactSizeIterator, FusedIterator);
assert_impl_all!(GetAll<'_>: Iterator, ExactSizeIterator, FusedIterator);
assert_impl_all!(std::slice::Iter<'_, HeaderValue>: Iterator, ExactSizeIterator, FusedIterator);
assert_impl_all!(Removed: Iterator, ExactSizeIterator, FusedIterator);
assert_impl_all!(Iter<'_>: Iterator, ExactSizeIterator, FusedIterator);
assert_impl_all!(IntoIter: Iterator, ExactSizeIterator, FusedIterator);

View File

@@ -45,13 +45,13 @@ pub enum ContentEncoding {
impl ContentEncoding {
/// Is the content compressed?
#[inline]
pub fn is_compression(self) -> bool {
pub const fn is_compression(self) -> bool {
matches!(self, ContentEncoding::Identity | ContentEncoding::Auto)
}
/// Convert content encoding to string.
#[inline]
pub fn as_str(self) -> &'static str {
pub const fn as_str(self) -> &'static str {
match self {
ContentEncoding::Br => "br",
ContentEncoding::Gzip => "gzip",

View File

@@ -87,7 +87,7 @@ impl fmt::Display for Quality {
// 0 is already handled so it's not possible to have a trailing 0 in this range
// we can just write the integer
itoa::fmt(f, x)
itoa_fmt(f, x)
} else if x < 100 {
// x in is range 1099
@@ -95,21 +95,21 @@ impl fmt::Display for Quality {
if x % 10 == 0 {
// trailing 0, divide by 10 and write
itoa::fmt(f, x / 10)
itoa_fmt(f, x / 10)
} else {
itoa::fmt(f, x)
itoa_fmt(f, x)
}
} else {
// x is in range 100999
if x % 100 == 0 {
// two trailing 0s, divide by 100 and write
itoa::fmt(f, x / 100)
itoa_fmt(f, x / 100)
} else if x % 10 == 0 {
// one trailing 0, divide by 10 and write
itoa::fmt(f, x / 10)
itoa_fmt(f, x / 10)
} else {
itoa::fmt(f, x)
itoa_fmt(f, x)
}
}
}
@@ -117,6 +117,12 @@ impl fmt::Display for Quality {
}
}
/// Write integer to a `fmt::Write`.
pub fn itoa_fmt<W: fmt::Write, V: itoa::Integer>(mut wr: W, value: V) -> fmt::Result {
let mut buf = itoa::Buffer::new();
wr.write_str(buf.format(value))
}
#[derive(Debug, Clone, Display, Error)]
#[display(fmt = "quality out of bounds")]
#[non_exhaustive]

View File

@@ -19,7 +19,6 @@
#![allow(
clippy::type_complexity,
clippy::too_many_arguments,
clippy::new_without_default,
clippy::borrow_interior_mutable_const
)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
@@ -28,26 +27,26 @@
#[macro_use]
extern crate log;
pub use ::http::{uri, uri::Uri};
pub use ::http::{Method, StatusCode, Version};
pub mod body;
mod builder;
mod config;
#[cfg(feature = "__compress")]
pub mod encoding;
pub mod error;
mod extensions;
pub mod h1;
pub mod h2;
pub mod header;
mod helpers;
mod http_message;
mod message;
mod payload;
mod request;
mod response;
mod response_builder;
mod requests;
mod responses;
mod service;
pub mod error;
pub mod h1;
pub mod h2;
pub mod test;
pub mod ws;
@@ -58,16 +57,13 @@ pub use self::extensions::Extensions;
pub use self::header::ContentEncoding;
pub use self::http_message::HttpMessage;
pub use self::message::ConnectionType;
pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead};
pub use self::payload::{Payload, PayloadStream};
pub use self::request::Request;
pub use self::response::Response;
pub use self::response_builder::ResponseBuilder;
pub use self::message::Message;
#[allow(deprecated)]
pub use self::payload::{BoxedPayloadStream, Payload, PayloadStream};
pub use self::requests::{Request, RequestHead, RequestHeadType};
pub use self::responses::{Response, ResponseBuilder, ResponseHead};
pub use self::service::HttpService;
pub use ::http::{uri, uri::Uri};
pub use ::http::{Method, StatusCode, Version};
/// A major HTTP protocol version.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]

View File

@@ -1,16 +1,7 @@
use std::{
cell::{Ref, RefCell, RefMut},
net,
rc::Rc,
};
use std::{cell::RefCell, rc::Rc};
use bitflags::bitflags;
use crate::{
header::{self, HeaderMap},
Extensions, Method, StatusCode, Uri, Version,
};
/// Represents various types of connection
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum ConnectionType {
@@ -44,294 +35,6 @@ pub trait Head: Default + 'static {
F: FnOnce(&MessagePool<Self>) -> R;
}
#[derive(Debug, Clone)]
pub struct RequestHead {
pub method: Method,
pub uri: Uri,
pub version: Version,
pub headers: HeaderMap,
pub peer_addr: Option<net::SocketAddr>,
flags: Flags,
}
impl Default for RequestHead {
fn default() -> RequestHead {
RequestHead {
method: Method::default(),
uri: Uri::default(),
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
peer_addr: None,
flags: Flags::empty(),
}
}
}
impl Head for RequestHead {
fn clear(&mut self) {
self.flags = Flags::empty();
self.headers.clear();
}
fn with_pool<F, R>(f: F) -> R
where
F: FnOnce(&MessagePool<Self>) -> R,
{
REQUEST_POOL.with(|p| f(p))
}
}
impl RequestHead {
/// Read the message headers.
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
/// Mutable reference to the message headers.
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
/// Is to uppercase headers with Camel-Case.
/// Default is `false`
#[inline]
pub fn camel_case_headers(&self) -> bool {
self.flags.contains(Flags::CAMEL_CASE)
}
/// Set `true` to send headers which are formatted as Camel-Case.
#[inline]
pub fn set_camel_case_headers(&mut self, val: bool) {
if val {
self.flags.insert(Flags::CAMEL_CASE);
} else {
self.flags.remove(Flags::CAMEL_CASE);
}
}
#[inline]
/// Set connection type of the message
pub fn set_connection_type(&mut self, ctype: ConnectionType) {
match ctype {
ConnectionType::Close => self.flags.insert(Flags::CLOSE),
ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE),
ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE),
}
}
#[inline]
/// Connection type
pub fn connection_type(&self) -> ConnectionType {
if self.flags.contains(Flags::CLOSE) {
ConnectionType::Close
} else if self.flags.contains(Flags::KEEP_ALIVE) {
ConnectionType::KeepAlive
} else if self.flags.contains(Flags::UPGRADE) {
ConnectionType::Upgrade
} else if self.version < Version::HTTP_11 {
ConnectionType::Close
} else {
ConnectionType::KeepAlive
}
}
/// Connection upgrade status
pub fn upgrade(&self) -> bool {
self.headers()
.get(header::CONNECTION)
.map(|hdr| {
if let Ok(s) = hdr.to_str() {
s.to_ascii_lowercase().contains("upgrade")
} else {
false
}
})
.unwrap_or(false)
}
#[inline]
/// Get response body chunking state
pub fn chunked(&self) -> bool {
!self.flags.contains(Flags::NO_CHUNKING)
}
#[inline]
pub fn no_chunking(&mut self, val: bool) {
if val {
self.flags.insert(Flags::NO_CHUNKING);
} else {
self.flags.remove(Flags::NO_CHUNKING);
}
}
#[inline]
/// Request contains `EXPECT` header
pub fn expect(&self) -> bool {
self.flags.contains(Flags::EXPECT)
}
#[inline]
pub(crate) fn set_expect(&mut self) {
self.flags.insert(Flags::EXPECT);
}
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum RequestHeadType {
Owned(RequestHead),
Rc(Rc<RequestHead>, Option<HeaderMap>),
}
impl RequestHeadType {
pub fn extra_headers(&self) -> Option<&HeaderMap> {
match self {
RequestHeadType::Owned(_) => None,
RequestHeadType::Rc(_, headers) => headers.as_ref(),
}
}
}
impl AsRef<RequestHead> for RequestHeadType {
fn as_ref(&self) -> &RequestHead {
match self {
RequestHeadType::Owned(head) => head,
RequestHeadType::Rc(head, _) => head.as_ref(),
}
}
}
impl From<RequestHead> for RequestHeadType {
fn from(head: RequestHead) -> Self {
RequestHeadType::Owned(head)
}
}
#[derive(Debug)]
pub struct ResponseHead {
pub version: Version,
pub status: StatusCode,
pub headers: HeaderMap,
pub reason: Option<&'static str>,
pub(crate) extensions: RefCell<Extensions>,
flags: Flags,
}
impl ResponseHead {
/// Create new instance of `ResponseHead` type
#[inline]
pub fn new(status: StatusCode) -> ResponseHead {
ResponseHead {
status,
version: Version::default(),
headers: HeaderMap::with_capacity(12),
reason: None,
flags: Flags::empty(),
extensions: RefCell::new(Extensions::new()),
}
}
/// Message extensions
#[inline]
pub fn extensions(&self) -> Ref<'_, Extensions> {
self.extensions.borrow()
}
/// Mutable reference to a the message's extensions
#[inline]
pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.extensions.borrow_mut()
}
#[inline]
/// Read the message headers.
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
#[inline]
/// Mutable reference to the message headers.
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
#[inline]
/// Set connection type of the message
pub fn set_connection_type(&mut self, ctype: ConnectionType) {
match ctype {
ConnectionType::Close => self.flags.insert(Flags::CLOSE),
ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE),
ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE),
}
}
#[inline]
pub fn connection_type(&self) -> ConnectionType {
if self.flags.contains(Flags::CLOSE) {
ConnectionType::Close
} else if self.flags.contains(Flags::KEEP_ALIVE) {
ConnectionType::KeepAlive
} else if self.flags.contains(Flags::UPGRADE) {
ConnectionType::Upgrade
} else if self.version < Version::HTTP_11 {
ConnectionType::Close
} else {
ConnectionType::KeepAlive
}
}
/// Check if keep-alive is enabled
#[inline]
pub fn keep_alive(&self) -> bool {
self.connection_type() == ConnectionType::KeepAlive
}
/// Check upgrade status of this message
#[inline]
pub fn upgrade(&self) -> bool {
self.connection_type() == ConnectionType::Upgrade
}
/// Get custom reason for the response
#[inline]
pub fn reason(&self) -> &str {
self.reason.unwrap_or_else(|| {
self.status
.canonical_reason()
.unwrap_or("<unknown status code>")
})
}
#[inline]
pub(crate) fn conn_type(&self) -> Option<ConnectionType> {
if self.flags.contains(Flags::CLOSE) {
Some(ConnectionType::Close)
} else if self.flags.contains(Flags::KEEP_ALIVE) {
Some(ConnectionType::KeepAlive)
} else if self.flags.contains(Flags::UPGRADE) {
Some(ConnectionType::Upgrade)
} else {
None
}
}
#[inline]
/// Get response body chunking state
pub fn chunked(&self) -> bool {
!self.flags.contains(Flags::NO_CHUNKING)
}
#[inline]
/// Set no chunking for payload
pub fn no_chunking(&mut self, val: bool) {
if val {
self.flags.insert(Flags::NO_CHUNKING);
} else {
self.flags.remove(Flags::NO_CHUNKING);
}
}
}
pub struct Message<T: Head> {
/// Rc here should not be cloned by anyone.
/// It's used to reuse allocation of T and no shared ownership is allowed.
@@ -340,6 +43,7 @@ pub struct Message<T: Head> {
impl<T: Head> Message<T> {
/// Get new message from the pool of objects
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
T::with_pool(MessagePool::get_message)
}
@@ -365,53 +69,12 @@ impl<T: Head> Drop for Message<T> {
}
}
pub(crate) struct BoxedResponseHead {
head: Option<Box<ResponseHead>>,
}
impl BoxedResponseHead {
/// Get new message from the pool of objects
pub fn new(status: StatusCode) -> Self {
RESPONSE_POOL.with(|p| p.get_message(status))
}
}
impl std::ops::Deref for BoxedResponseHead {
type Target = ResponseHead;
fn deref(&self) -> &Self::Target {
self.head.as_ref().unwrap()
}
}
impl std::ops::DerefMut for BoxedResponseHead {
fn deref_mut(&mut self) -> &mut Self::Target {
self.head.as_mut().unwrap()
}
}
impl Drop for BoxedResponseHead {
fn drop(&mut self) {
if let Some(head) = self.head.take() {
RESPONSE_POOL.with(move |p| p.release(head))
}
}
}
#[doc(hidden)]
/// Request's objects pool
pub struct MessagePool<T: Head>(RefCell<Vec<Rc<T>>>);
#[doc(hidden)]
#[allow(clippy::vec_box)]
/// Request's objects pool
pub struct BoxedResponsePool(RefCell<Vec<Box<ResponseHead>>>);
thread_local!(static REQUEST_POOL: MessagePool<RequestHead> = MessagePool::<RequestHead>::create());
thread_local!(static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create());
impl<T: Head> MessagePool<T> {
fn create() -> MessagePool<T> {
pub(crate) fn create() -> MessagePool<T> {
MessagePool(RefCell::new(Vec::with_capacity(128)))
}
@@ -433,43 +96,11 @@ impl<T: Head> MessagePool<T> {
}
#[inline]
/// Release request instance
/// Release message instance
fn release(&self, msg: Rc<T>) {
let v = &mut self.0.borrow_mut();
if v.len() < 128 {
v.push(msg);
}
}
}
impl BoxedResponsePool {
fn create() -> BoxedResponsePool {
BoxedResponsePool(RefCell::new(Vec::with_capacity(128)))
}
/// Get message from the pool
#[inline]
fn get_message(&self, status: StatusCode) -> BoxedResponseHead {
if let Some(mut head) = self.0.borrow_mut().pop() {
head.reason = None;
head.status = status;
head.headers.clear();
head.flags = Flags::empty();
BoxedResponseHead { head: Some(head) }
} else {
BoxedResponseHead {
head: Some(Box::new(ResponseHead::new(status))),
}
}
}
#[inline]
/// Release request instance
fn release(&self, mut msg: Box<ResponseHead>) {
let v = &mut self.0.borrow_mut();
if v.len() < 128 {
msg.extensions.get_mut().clear();
v.push(msg);
let pool = &mut self.0.borrow_mut();
if pool.len() < 128 {
pool.push(msg);
}
}
}

View File

@@ -1,67 +1,89 @@
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{
mem,
pin::Pin,
task::{Context, Poll},
};
use bytes::Bytes;
use futures_core::Stream;
use h2::RecvStream;
use crate::error::PayloadError;
/// A boxed payload.
pub type PayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>>>>;
/// A boxed payload stream.
pub type BoxedPayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>>>>;
/// A streaming payload.
pub enum Payload<S = PayloadStream> {
None,
H1(crate::h1::Payload),
H2(crate::h2::Payload),
Stream(S),
#[deprecated(since = "4.0.0", note = "Renamed to `BoxedPayloadStream`.")]
pub type PayloadStream = BoxedPayloadStream;
pin_project_lite::pin_project! {
/// A streaming payload.
#[project = PayloadProj]
pub enum Payload<S = BoxedPayloadStream> {
None,
H1 { payload: crate::h1::Payload },
H2 { payload: crate::h2::Payload },
Stream { #[pin] payload: S },
}
}
impl<S> From<crate::h1::Payload> for Payload<S> {
fn from(v: crate::h1::Payload) -> Self {
Payload::H1(v)
fn from(payload: crate::h1::Payload) -> Self {
Payload::H1 { payload }
}
}
impl<S> From<crate::h2::Payload> for Payload<S> {
fn from(v: crate::h2::Payload) -> Self {
Payload::H2(v)
fn from(payload: crate::h2::Payload) -> Self {
Payload::H2 { payload }
}
}
impl<S> From<RecvStream> for Payload<S> {
fn from(v: RecvStream) -> Self {
Payload::H2(crate::h2::Payload::new(v))
impl<S> From<h2::RecvStream> for Payload<S> {
fn from(stream: h2::RecvStream) -> Self {
Payload::H2 {
payload: crate::h2::Payload::new(stream),
}
}
}
impl From<PayloadStream> for Payload {
fn from(pl: PayloadStream) -> Self {
Payload::Stream(pl)
impl From<BoxedPayloadStream> for Payload {
fn from(payload: BoxedPayloadStream) -> Self {
Payload::Stream { payload }
}
}
impl<S> Payload<S> {
/// Takes current payload and replaces it with `None` value
pub fn take(&mut self) -> Payload<S> {
std::mem::replace(self, Payload::None)
mem::replace(self, Payload::None)
}
}
impl<S> Stream for Payload<S>
where
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
S: Stream<Item = Result<Bytes, PayloadError>>,
{
type Item = Result<Bytes, PayloadError>;
#[inline]
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match self.get_mut() {
Payload::None => Poll::Ready(None),
Payload::H1(ref mut pl) => pl.readany(cx),
Payload::H2(ref mut pl) => Pin::new(pl).poll_next(cx),
Payload::Stream(ref mut pl) => Pin::new(pl).poll_next(cx),
match self.project() {
PayloadProj::None => Poll::Ready(None),
PayloadProj::H1 { payload } => Pin::new(payload).poll_next(cx),
PayloadProj::H2 { payload } => Pin::new(payload).poll_next(cx),
PayloadProj::Stream { payload } => payload.poll_next(cx),
}
}
}
#[cfg(test)]
mod tests {
use std::panic::{RefUnwindSafe, UnwindSafe};
use static_assertions::{assert_impl_all, assert_not_impl_any};
use super::*;
assert_impl_all!(Payload: Unpin);
assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe);
}

View File

@@ -0,0 +1,174 @@
use std::{net, rc::Rc};
use crate::{
header::{self, HeaderMap},
message::{Flags, Head, MessagePool},
ConnectionType, Method, Uri, Version,
};
thread_local! {
static REQUEST_POOL: MessagePool<RequestHead> = MessagePool::<RequestHead>::create()
}
#[derive(Debug, Clone)]
pub struct RequestHead {
pub method: Method,
pub uri: Uri,
pub version: Version,
pub headers: HeaderMap,
pub peer_addr: Option<net::SocketAddr>,
flags: Flags,
}
impl Default for RequestHead {
fn default() -> RequestHead {
RequestHead {
method: Method::default(),
uri: Uri::default(),
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
peer_addr: None,
flags: Flags::empty(),
}
}
}
impl Head for RequestHead {
fn clear(&mut self) {
self.flags = Flags::empty();
self.headers.clear();
}
fn with_pool<F, R>(f: F) -> R
where
F: FnOnce(&MessagePool<Self>) -> R,
{
REQUEST_POOL.with(|p| f(p))
}
}
impl RequestHead {
/// Read the message headers.
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
/// Mutable reference to the message headers.
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
/// Is to uppercase headers with Camel-Case.
/// Default is `false`
#[inline]
pub fn camel_case_headers(&self) -> bool {
self.flags.contains(Flags::CAMEL_CASE)
}
/// Set `true` to send headers which are formatted as Camel-Case.
#[inline]
pub fn set_camel_case_headers(&mut self, val: bool) {
if val {
self.flags.insert(Flags::CAMEL_CASE);
} else {
self.flags.remove(Flags::CAMEL_CASE);
}
}
#[inline]
/// Set connection type of the message
pub fn set_connection_type(&mut self, ctype: ConnectionType) {
match ctype {
ConnectionType::Close => self.flags.insert(Flags::CLOSE),
ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE),
ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE),
}
}
#[inline]
/// Connection type
pub fn connection_type(&self) -> ConnectionType {
if self.flags.contains(Flags::CLOSE) {
ConnectionType::Close
} else if self.flags.contains(Flags::KEEP_ALIVE) {
ConnectionType::KeepAlive
} else if self.flags.contains(Flags::UPGRADE) {
ConnectionType::Upgrade
} else if self.version < Version::HTTP_11 {
ConnectionType::Close
} else {
ConnectionType::KeepAlive
}
}
/// Connection upgrade status
pub fn upgrade(&self) -> bool {
self.headers()
.get(header::CONNECTION)
.map(|hdr| {
if let Ok(s) = hdr.to_str() {
s.to_ascii_lowercase().contains("upgrade")
} else {
false
}
})
.unwrap_or(false)
}
#[inline]
/// Get response body chunking state
pub fn chunked(&self) -> bool {
!self.flags.contains(Flags::NO_CHUNKING)
}
#[inline]
pub fn no_chunking(&mut self, val: bool) {
if val {
self.flags.insert(Flags::NO_CHUNKING);
} else {
self.flags.remove(Flags::NO_CHUNKING);
}
}
#[inline]
/// Request contains `EXPECT` header
pub fn expect(&self) -> bool {
self.flags.contains(Flags::EXPECT)
}
#[inline]
pub(crate) fn set_expect(&mut self) {
self.flags.insert(Flags::EXPECT);
}
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum RequestHeadType {
Owned(RequestHead),
Rc(Rc<RequestHead>, Option<HeaderMap>),
}
impl RequestHeadType {
pub fn extra_headers(&self) -> Option<&HeaderMap> {
match self {
RequestHeadType::Owned(_) => None,
RequestHeadType::Rc(_, headers) => headers.as_ref(),
}
}
}
impl AsRef<RequestHead> for RequestHeadType {
fn as_ref(&self) -> &RequestHead {
match self {
RequestHeadType::Owned(head) => head,
RequestHeadType::Rc(head, _) => head.as_ref(),
}
}
}
impl From<RequestHead> for RequestHeadType {
fn from(head: RequestHead) -> Self {
RequestHeadType::Owned(head)
}
}

View File

@@ -0,0 +1,7 @@
//! HTTP requests.
mod head;
mod request;
pub use self::head::{RequestHead, RequestHeadType};
pub use self::request::Request;

View File

@@ -10,15 +10,12 @@ use std::{
use http::{header, Method, Uri, Version};
use crate::{
extensions::Extensions,
header::HeaderMap,
message::{Message, RequestHead},
payload::{Payload, PayloadStream},
HttpMessage,
header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, Message, Payload,
RequestHead,
};
/// An HTTP request.
pub struct Request<P = PayloadStream> {
pub struct Request<P = BoxedPayloadStream> {
pub(crate) payload: Payload<P>,
pub(crate) head: Message<RequestHead>,
pub(crate) conn_data: Option<Rc<Extensions>>,
@@ -50,7 +47,7 @@ impl<P> HttpMessage for Request<P> {
}
}
impl From<Message<RequestHead>> for Request<PayloadStream> {
impl From<Message<RequestHead>> for Request<BoxedPayloadStream> {
fn from(head: Message<RequestHead>) -> Self {
Request {
head,
@@ -61,9 +58,10 @@ impl From<Message<RequestHead>> for Request<PayloadStream> {
}
}
impl Request<PayloadStream> {
impl Request<BoxedPayloadStream> {
/// Create new Request instance
pub fn new() -> Request<PayloadStream> {
#[allow(clippy::new_without_default)]
pub fn new() -> Request<BoxedPayloadStream> {
Request {
head: Message::new(),
payload: Payload::None,
@@ -206,7 +204,7 @@ impl<P> Request<P> {
/// Returns the request data container, leaving an empty one in it's place.
pub fn take_req_data(&mut self) -> Extensions {
mem::take(&mut self.req_data.get_mut())
mem::take(self.req_data.get_mut())
}
}

View File

@@ -9,8 +9,8 @@ use crate::{
body::{EitherBody, MessageBody},
error::{Error, HttpError},
header::{self, TryIntoHeaderPair, TryIntoHeaderValue},
message::{BoxedResponseHead, ConnectionType, ResponseHead},
Extensions, Response, StatusCode,
responses::{BoxedResponseHead, ResponseHead},
ConnectionType, Extensions, Response, StatusCode,
};
/// An HTTP response builder.

View File

@@ -0,0 +1,208 @@
//! Response head type and caching pool.
use std::{
cell::{Ref, RefCell, RefMut},
ops,
};
use crate::{
header::HeaderMap, message::Flags, ConnectionType, Extensions, StatusCode, Version,
};
thread_local! {
static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create();
}
#[derive(Debug)]
pub struct ResponseHead {
pub version: Version,
pub status: StatusCode,
pub headers: HeaderMap,
pub reason: Option<&'static str>,
pub(crate) extensions: RefCell<Extensions>,
flags: Flags,
}
impl ResponseHead {
/// Create new instance of `ResponseHead` type
#[inline]
pub fn new(status: StatusCode) -> ResponseHead {
ResponseHead {
status,
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(12),
reason: None,
flags: Flags::empty(),
extensions: RefCell::new(Extensions::new()),
}
}
#[inline]
/// Read the message headers.
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
#[inline]
/// Mutable reference to the message headers.
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
/// Message extensions
#[inline]
pub fn extensions(&self) -> Ref<'_, Extensions> {
self.extensions.borrow()
}
/// Mutable reference to a the message's extensions
#[inline]
pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.extensions.borrow_mut()
}
#[inline]
/// Set connection type of the message
pub fn set_connection_type(&mut self, ctype: ConnectionType) {
match ctype {
ConnectionType::Close => self.flags.insert(Flags::CLOSE),
ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE),
ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE),
}
}
#[inline]
pub fn connection_type(&self) -> ConnectionType {
if self.flags.contains(Flags::CLOSE) {
ConnectionType::Close
} else if self.flags.contains(Flags::KEEP_ALIVE) {
ConnectionType::KeepAlive
} else if self.flags.contains(Flags::UPGRADE) {
ConnectionType::Upgrade
} else if self.version < Version::HTTP_11 {
ConnectionType::Close
} else {
ConnectionType::KeepAlive
}
}
/// Check if keep-alive is enabled
#[inline]
pub fn keep_alive(&self) -> bool {
self.connection_type() == ConnectionType::KeepAlive
}
/// Check upgrade status of this message
#[inline]
pub fn upgrade(&self) -> bool {
self.connection_type() == ConnectionType::Upgrade
}
/// Get custom reason for the response
#[inline]
pub fn reason(&self) -> &str {
self.reason.unwrap_or_else(|| {
self.status
.canonical_reason()
.unwrap_or("<unknown status code>")
})
}
#[inline]
pub(crate) fn conn_type(&self) -> Option<ConnectionType> {
if self.flags.contains(Flags::CLOSE) {
Some(ConnectionType::Close)
} else if self.flags.contains(Flags::KEEP_ALIVE) {
Some(ConnectionType::KeepAlive)
} else if self.flags.contains(Flags::UPGRADE) {
Some(ConnectionType::Upgrade)
} else {
None
}
}
#[inline]
/// Get response body chunking state
pub fn chunked(&self) -> bool {
!self.flags.contains(Flags::NO_CHUNKING)
}
#[inline]
/// Set no chunking for payload
pub fn no_chunking(&mut self, val: bool) {
if val {
self.flags.insert(Flags::NO_CHUNKING);
} else {
self.flags.remove(Flags::NO_CHUNKING);
}
}
}
pub(crate) struct BoxedResponseHead {
head: Option<Box<ResponseHead>>,
}
impl BoxedResponseHead {
/// Get new message from the pool of objects
pub fn new(status: StatusCode) -> Self {
RESPONSE_POOL.with(|p| p.get_message(status))
}
}
impl ops::Deref for BoxedResponseHead {
type Target = ResponseHead;
fn deref(&self) -> &Self::Target {
self.head.as_ref().unwrap()
}
}
impl ops::DerefMut for BoxedResponseHead {
fn deref_mut(&mut self) -> &mut Self::Target {
self.head.as_mut().unwrap()
}
}
impl Drop for BoxedResponseHead {
fn drop(&mut self) {
if let Some(head) = self.head.take() {
RESPONSE_POOL.with(move |p| p.release(head))
}
}
}
/// Request's objects pool
#[doc(hidden)]
pub struct BoxedResponsePool(#[allow(clippy::vec_box)] RefCell<Vec<Box<ResponseHead>>>);
impl BoxedResponsePool {
fn create() -> BoxedResponsePool {
BoxedResponsePool(RefCell::new(Vec::with_capacity(128)))
}
/// Get message from the pool
#[inline]
fn get_message(&self, status: StatusCode) -> BoxedResponseHead {
if let Some(mut head) = self.0.borrow_mut().pop() {
head.reason = None;
head.status = status;
head.headers.clear();
head.flags = Flags::empty();
BoxedResponseHead { head: Some(head) }
} else {
BoxedResponseHead {
head: Some(Box::new(ResponseHead::new(status))),
}
}
}
/// Release request instance
#[inline]
fn release(&self, mut msg: Box<ResponseHead>) {
let pool = &mut self.0.borrow_mut();
if pool.len() < 128 {
msg.extensions.get_mut().clear();
pool.push(msg);
}
}
}

View File

@@ -0,0 +1,11 @@
//! HTTP response.
mod builder;
mod head;
#[allow(clippy::module_inception)]
mod response;
pub use self::builder::ResponseBuilder;
pub(crate) use self::head::BoxedResponseHead;
pub use self::head::ResponseHead;
pub use self::response::Response;

View File

@@ -10,10 +10,9 @@ use bytestring::ByteString;
use crate::{
body::{BoxBody, MessageBody},
extensions::Extensions,
header::{self, HeaderMap, TryIntoHeaderValue},
message::{BoxedResponseHead, ResponseHead},
Error, ResponseBuilder, StatusCode,
responses::BoxedResponseHead,
Error, Extensions, ResponseBuilder, ResponseHead, StatusCode,
};
/// An HTTP response.

View File

@@ -120,7 +120,7 @@ impl TestRequest {
}
/// Set request payload.
pub fn set_payload<B: Into<Bytes>>(&mut self, data: B) -> &mut Self {
pub fn set_payload(&mut self, data: impl Into<Bytes>) -> &mut Self {
let mut payload = crate::h1::Payload::empty();
payload.unread_data(data.into());
parts(&mut self.0).payload = Some(payload.into());

View File

@@ -9,7 +9,7 @@ use derive_more::{Display, Error, From};
use http::{header, Method, StatusCode};
use crate::body::BoxBody;
use crate::{header::HeaderValue, message::RequestHead, response::Response, ResponseBuilder};
use crate::{header::HeaderValue, RequestHead, Response, ResponseBuilder};
mod codec;
mod dispatcher;
@@ -99,8 +99,9 @@ impl From<HandshakeError> for Response<BoxBody> {
match err {
HandshakeError::GetMethodRequired => {
let mut res = Response::new(StatusCode::METHOD_NOT_ALLOWED);
res.headers_mut()
.insert(header::ALLOW, HeaderValue::from_static("GET"));
#[allow(clippy::declare_interior_mutable_const)]
const HV_GET: HeaderValue = HeaderValue::from_static("GET");
res.headers_mut().insert(header::ALLOW, HV_GET);
res
}

View File

@@ -7,6 +7,7 @@ use std::{
io::{self, BufReader, Write},
net::{SocketAddr, TcpStream as StdTcpStream},
sync::Arc,
task::Poll,
};
use actix_http::{
@@ -16,25 +17,37 @@ use actix_http::{
Error, HttpService, Method, Request, Response, StatusCode, Version,
};
use actix_http_test::test_server;
use actix_rt::pin;
use actix_service::{fn_factory_with_config, fn_service};
use actix_tls::connect::rustls::webpki_roots_cert_store;
use actix_utils::future::{err, ok};
use actix_utils::future::{err, ok, poll_fn};
use bytes::{Bytes, BytesMut};
use derive_more::{Display, Error};
use futures_core::Stream;
use futures_util::stream::{once, StreamExt as _};
use futures_core::{ready, Stream};
use futures_util::stream::once;
use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig, ServerName};
use rustls_pemfile::{certs, pkcs8_private_keys};
async fn load_body<S>(mut stream: S) -> Result<BytesMut, PayloadError>
async fn load_body<S>(stream: S) -> Result<BytesMut, PayloadError>
where
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
S: Stream<Item = Result<Bytes, PayloadError>>,
{
let mut body = BytesMut::new();
while let Some(item) = stream.next().await {
body.extend_from_slice(&item?)
}
Ok(body)
let mut buf = BytesMut::new();
pin!(stream);
poll_fn(|cx| loop {
let body = stream.as_mut();
match ready!(body.poll_next(cx)) {
Some(Ok(bytes)) => buf.extend_from_slice(&*bytes),
None => return Poll::Ready(Ok(())),
Some(Err(err)) => return Poll::Ready(Err(err)),
}
})
.await?;
Ok(buf)
}
fn tls_config() -> RustlsServerConfig {

View File

@@ -3,120 +3,124 @@
## Unreleased - 2021-xx-xx
## 0.4.0-beta.11 - 2021-12-27
* No significant changes since `0.4.0-beta.10`.
## 0.4.0-beta.10 - 2021-12-11
* No significant changes since `0.4.0-beta.9`.
- No significant changes since `0.4.0-beta.9`.
## 0.4.0-beta.9 - 2021-12-01
* Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463]
- Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463]
[#2463]: https://github.com/actix/actix-web/pull/2463
## 0.4.0-beta.8 - 2021-11-22
* Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451]
* Added `MultipartError::NoContentDisposition` variant. [#2451]
* Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451]
* Added `Field::name` method for getting the field name. [#2451]
* `MultipartError` now marks variants with inner errors as the source. [#2451]
* `MultipartError` is now marked as non-exhaustive. [#2451]
- Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451]
- Added `MultipartError::NoContentDisposition` variant. [#2451]
- Since Content-Disposition is now ensured, `Field::content_disposition` is now infallible. [#2451]
- Added `Field::name` method for getting the field name. [#2451]
- `MultipartError` now marks variants with inner errors as the source. [#2451]
- `MultipartError` is now marked as non-exhaustive. [#2451]
[#2451]: https://github.com/actix/actix-web/pull/2451
## 0.4.0-beta.7 - 2021-10-20
* Minimum supported Rust version (MSRV) is now 1.52.
- Minimum supported Rust version (MSRV) is now 1.52.
## 0.4.0-beta.6 - 2021-09-09
* Minimum supported Rust version (MSRV) is now 1.51.
- Minimum supported Rust version (MSRV) is now 1.51.
## 0.4.0-beta.5 - 2021-06-17
* No notable changes.
- No notable changes.
## 0.4.0-beta.4 - 2021-04-02
* No notable changes.
- No notable changes.
## 0.4.0-beta.3 - 2021-03-09
* No notable changes.
- No notable changes.
## 0.4.0-beta.2 - 2021-02-10
* No notable changes.
- No notable changes.
## 0.4.0-beta.1 - 2021-01-07
* Fix multipart consuming payload before header checks. [#1513]
* Update `bytes` to `1.0`. [#1813]
- Fix multipart consuming payload before header checks. [#1513]
- Update `bytes` to `1.0`. [#1813]
[#1813]: https://github.com/actix/actix-web/pull/1813
[#1513]: https://github.com/actix/actix-web/pull/1513
## 0.3.0 - 2020-09-11
* No significant changes from `0.3.0-beta.2`.
- No significant changes from `0.3.0-beta.2`.
## 0.3.0-beta.2 - 2020-09-10
* Update `actix-*` dependencies to latest versions.
- Update `actix-*` dependencies to latest versions.
## 0.3.0-beta.1 - 2020-07-15
* Update `actix-web` to 3.0.0-beta.1
- Update `actix-web` to 3.0.0-beta.1
## 0.3.0-alpha.1 - 2020-05-25
* Update `actix-web` to 3.0.0-alpha.3
* Bump minimum supported Rust version to 1.40
* Minimize `futures` dependencies
* Remove the unused `time` dependency
* Fix missing `std::error::Error` implement for `MultipartError`.
- Update `actix-web` to 3.0.0-alpha.3
- Bump minimum supported Rust version to 1.40
- Minimize `futures` dependencies
- Remove the unused `time` dependency
- Fix missing `std::error::Error` implement for `MultipartError`.
## [0.2.0] - 2019-12-20
* Release
- Release
## [0.2.0-alpha.4] - 2019-12-xx
* Multipart handling now handles Pending during read of boundary #1205
- Multipart handling now handles Pending during read of boundary #1205
## [0.2.0-alpha.2] - 2019-12-03
* Migrate to `std::future`
- Migrate to `std::future`
## [0.1.4] - 2019-09-12
* Multipart handling now parses requests which do not end in CRLF #1038
- Multipart handling now parses requests which do not end in CRLF #1038
## [0.1.3] - 2019-08-18
* Fix ring dependency from actix-web default features for #741.
- Fix ring dependency from actix-web default features for #741.
## [0.1.2] - 2019-06-02
* Fix boundary parsing #876
- Fix boundary parsing #876
## [0.1.1] - 2019-05-25
* Fix disconnect handling #834
- Fix disconnect handling #834
## [0.1.0] - 2019-05-18
* Release
- Release
## [0.1.0-beta.4] - 2019-05-12
* Handle cancellation of uploads #736
- Handle cancellation of uploads #736
* Upgrade to actix-web 1.0.0-beta.4
- Upgrade to actix-web 1.0.0-beta.4
## [0.1.0-beta.1] - 2019-04-21
* Do not support nested multipart
- Do not support nested multipart
* Split multipart support to separate crate
- Split multipart support to separate crate
* Optimize multipart handling #634, #769
- Optimize multipart handling #634, #769

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-multipart"
version = "0.4.0-beta.10"
version = "0.4.0-beta.11"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Multipart form support for Actix Web"
keywords = ["http", "web", "framework", "async", "futures"]
@@ -15,7 +15,7 @@ path = "src/lib.rs"
[dependencies]
actix-utils = "3.0.0"
actix-web = { version = "4.0.0-beta.15", default-features = false }
actix-web = { version = "4.0.0-beta.16", default-features = false }
bytes = "1"
derive_more = "0.99.5"
@@ -28,7 +28,7 @@ twoway = "0.2"
[dev-dependencies]
actix-rt = "2.2"
actix-http = "3.0.0-beta.16"
actix-http = "3.0.0-beta.17"
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
tokio = { version = "1", features = ["sync"] }
tokio = { version = "1.8", features = ["sync"] }
tokio-stream = "0.1"

View File

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

View File

@@ -1233,7 +1233,7 @@ mod tests {
// and should not consume the payload
match payload {
actix_web::dev::Payload::H1(_) => {} //expected
actix_web::dev::Payload::H1 { .. } => {} //expected
_ => unreachable!(),
}
}

View File

@@ -4,20 +4,20 @@
## 0.5.0-beta.3 - 2021-12-17
* Minimum supported Rust version (MSRV) is now 1.52.
- Minimum supported Rust version (MSRV) is now 1.52.
## 0.5.0-beta.2 - 2021-09-09
* Introduce `ResourceDef::join`. [#380]
* Disallow prefix routes with tail segments. [#379]
* Enforce path separators on dynamic prefixes. [#378]
* Improve malformed path error message. [#384]
* 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]
* Support multi-pattern prefixes and joins. [#2356]
* `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356]
* Support `build_resource_path` on multi-pattern resources. [#2356]
* Minimum supported Rust version (MSRV) is now 1.51.
- Introduce `ResourceDef::join`. [#380]
- Disallow prefix routes with tail segments. [#379]
- Enforce path separators on dynamic prefixes. [#378]
- Improve malformed path error message. [#384]
- 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]
- Support multi-pattern prefixes and joins. [#2356]
- `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356]
- Support `build_resource_path` on multi-pattern resources. [#2356]
- Minimum supported Rust version (MSRV) is now 1.51.
[#378]: https://github.com/actix/actix-net/pull/378
[#379]: https://github.com/actix/actix-net/pull/379
@@ -28,23 +28,23 @@
## 0.5.0-beta.1 - 2021-07-20
* Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366]
* Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373]
* Fix segment interpolation leaving `Path` in unintended state after matching. [#368]
* Fix `ResourceDef` `PartialEq` implementation. [#373]
* Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372]
* Implement `IntoPatterns` for `bytestring::ByteString`. [#372]
* Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370]
* Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371]
* `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373]
* Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371]
* Rename `ResourceDef::{is_prefix_match => find_match}`. [#373]
* Rename `ResourceDef::{match_path => capture_match_info}`. [#373]
* Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373]
* Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373]
* Rename `Router::{*_checked => *_fn}`. [#373]
* Return type of `ResourceDef::name` is now `Option<&str>`. [#373]
* Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373]
- Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366]
- Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373]
- Fix segment interpolation leaving `Path` in unintended state after matching. [#368]
- Fix `ResourceDef` `PartialEq` implementation. [#373]
- Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372]
- Implement `IntoPatterns` for `bytestring::ByteString`. [#372]
- Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370]
- Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371]
- `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373]
- Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371]
- Rename `ResourceDef::{is_prefix_match => find_match}`. [#373]
- Rename `ResourceDef::{match_path => capture_match_info}`. [#373]
- Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373]
- Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373]
- Rename `Router::{*_checked => *_fn}`. [#373]
- Return type of `ResourceDef::name` is now `Option<&str>`. [#373]
- Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373]
[#368]: https://github.com/actix/actix-net/pull/368
[#366]: https://github.com/actix/actix-net/pull/366
@@ -56,10 +56,10 @@
## 0.4.0 - 2021-06-06
* When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357]
* Path tail patterns now match new lines (`\n`) in request URL. [#360]
* Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359]
* Methods `Path::{add, add_static}` now take `impl Into<Cow<'static, str>>`. [#345]
- When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357]
- Path tail patterns now match new lines (`\n`) in request URL. [#360]
- Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359]
- Methods `Path::{add, add_static}` now take `impl Into<Cow<'static, str>>`. [#345]
[#345]: https://github.com/actix/actix-net/pull/345
[#357]: https://github.com/actix/actix-net/pull/357
@@ -68,68 +68,68 @@
## 0.3.0 - 2019-12-31
* Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0
- Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0
## 0.2.7 - 2021-02-06
* Add `Router::recognize_checked` [#247]
- Add `Router::recognize_checked` [#247]
[#247]: https://github.com/actix/actix-net/pull/247
## 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]
[#246]: https://github.com/actix/actix-net/pull/246
## 0.2.5 - 2020-09-20
* Fix `from_hex()` method
- Fix `from_hex()` method
## 0.2.4 - 2019-12-31
* Add `ResourceDef::resource_path_named()` path generation method
- Add `ResourceDef::resource_path_named()` path generation method
## 0.2.3 - 2019-12-25
* Add impl `IntoPattern` for `&String`
- Add impl `IntoPattern` for `&String`
## 0.2.2 - 2019-12-25
* Use `IntoPattern` for `RouterBuilder::path()`
- Use `IntoPattern` for `RouterBuilder::path()`
## 0.2.1 - 2019-12-25
* Add `IntoPattern` trait
* Add multi-pattern resources
- Add `IntoPattern` trait
- Add multi-pattern resources
## 0.2.0 - 2019-12-07
* Update http to 0.2
* Update regex to 1.3
* Use bytestring instead of string
- Update http to 0.2
- Update regex to 1.3
- Use bytestring instead of string
## 0.1.5 - 2019-05-15
* Remove debug prints
- Remove debug prints
## 0.1.4 - 2019-05-15
* Fix checked resource match
- Fix checked resource match
## 0.1.3 - 2019-04-22
* Added support for `remainder match` (i.e "/path/{tail}*")
- Added support for `remainder match` (i.e "/path/{tail}*")
## 0.1.2 - 2019-04-07
* Export `Quoter` type
* Allow to reset `Path` instance
- Export `Quoter` type
- Allow to reset `Path` instance
## 0.1.1 - 2019-04-03
* Get dynamic segment by name instead of iterator.
- Get dynamic segment by name instead of iterator.
## 0.1.0 - 2019-03-09
* Initial release
- Initial release

View File

@@ -21,7 +21,7 @@ default = ["http"]
[dependencies]
bytestring = ">=0.1.5, <2"
firestorm = "0.4"
firestorm = "0.5"
http = { version = "0.2.3", optional = true }
log = "0.4"
regex = "1.5"
@@ -29,7 +29,7 @@ serde = "1"
[dev-dependencies]
criterion = { version = "0.3", features = ["html_reports"] }
firestorm = { version = "0.4", features = ["enable_system_time"] }
firestorm = { version = "0.5", features = ["enable_system_time"] }
http = "0.2.5"
serde = { version = "1", features = ["derive"] }

View File

@@ -29,26 +29,25 @@ const REGEX_FLAGS: &str = "(?s-m)";
///
///
/// # Pattern Format and Matching Behavior
///
/// Resource pattern is defined as a string of zero or more _segments_ where each segment is
/// preceded by a slash `/`.
///
/// This means that pattern string __must__ either be empty or begin with a slash (`/`).
/// This also implies that a trailing slash in pattern defines an empty segment.
/// For example, the pattern `"/user/"` has two segments: `["user", ""]`
/// This means that pattern string __must__ either be empty or begin with a slash (`/`). This also
/// implies that a trailing slash in pattern defines an empty segment. For example, the pattern
/// `"/user/"` has two segments: `["user", ""]`
///
/// A key point to underhand is that `ResourceDef` matches segments, not strings.
/// It matches segments individually.
/// For example, the pattern `/user/` is not considered a prefix for the path `/user/123/456`,
/// because the second segment doesn't match: `["user", ""]` vs `["user", "123", "456"]`.
/// A key point to understand is that `ResourceDef` matches segments, not strings. Segments are
/// matched individually. For example, the pattern `/user/` is not considered a prefix for the path
/// `/user/123/456`, because the second segment doesn't match: `["user", ""]`
/// vs `["user", "123", "456"]`.
///
/// This definition is consistent with the definition of absolute URL path in
/// [RFC 3986 (section 3.3)](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3)
/// [RFC 3986 §3.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3)
///
///
/// # Static Resources
/// A static resource is the most basic type of definition. Pass a pattern to
/// [new][Self::new]. Conforming paths must match the pattern exactly.
/// A static resource is the most basic type of definition. Pass a pattern to [new][Self::new].
/// Conforming paths must match the pattern exactly.
///
/// ## Examples
/// ```
@@ -63,7 +62,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!resource.is_match("/search"));
/// ```
///
///
/// # Dynamic Segments
/// Also known as "path parameters". Resources can define sections of a pattern that be extracted
/// from a conforming path, if it conforms to (one of) the resource pattern(s).
@@ -102,15 +100,15 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert_eq!(path.get("id").unwrap(), "123");
/// ```
///
///
/// # Prefix Resources
/// A prefix resource is defined as pattern that can match just the start of a path, up to a
/// segment boundary.
///
/// Prefix patterns with a trailing slash may have an unexpected, though correct, behavior.
/// They define and therefore require an empty segment in order to match. Examples are given below.
/// They define and therefore require an empty segment in order to match. It is easier to understand
/// this behavior after reading the [matching behavior section]. Examples are given below.
///
/// Empty pattern matches any path as a prefix.
/// The empty pattern (`""`), as a prefix, matches any path.
///
/// Prefix resources can contain dynamic segments.
///
@@ -130,7 +128,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!resource.is_match("/user/123"));
/// ```
///
///
/// # Custom Regex Segments
/// Dynamic segments can be customised to only match a specific regular expression. It can be
/// helpful to do this if resource definitions would otherwise conflict and cause one to
@@ -158,7 +155,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!resource.is_match("/user/abc"));
/// ```
///
///
/// # Tail Segments
/// As a shortcut to defining a custom regex for matching _all_ remaining characters (not just those
/// up until a `/` character), there is a special pattern to match (and capture) the remaining
@@ -179,7 +175,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert_eq!(path.get("tail").unwrap(), "main/LICENSE");
/// ```
///
///
/// # Multi-Pattern Resources
/// For resources that can map to multiple distinct paths, it may be suitable to use
/// multi-pattern resources by passing an array/vec to [`new`][Self::new]. They will be combined
@@ -198,7 +193,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(resource.is_match("/index"));
/// ```
///
///
/// # Trailing Slashes
/// It should be noted that this library takes no steps to normalize intra-path or trailing slashes.
/// As such, all resource definitions implicitly expect a pre-processing step to normalize paths if
@@ -212,6 +206,8 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!ResourceDef::new("/root/").is_match("/root"));
/// assert!(!ResourceDef::prefix("/root/").is_match("/root"));
/// ```
///
/// [matching behavior section]: #pattern-format-and-matching-behavior
#[derive(Clone, Debug)]
pub struct ResourceDef {
id: u16,
@@ -279,7 +275,7 @@ impl ResourceDef {
/// ```
pub fn new<T: IntoPatterns>(paths: T) -> Self {
profile_method!(new);
Self::new2(paths, false)
Self::construct(paths, false)
}
/// Constructs a new resource definition using a pattern that performs prefix matching.
@@ -292,7 +288,7 @@ impl ResourceDef {
/// resource definition with a tail segment; use [`new`][Self::new] in this case.
///
/// # Panics
/// Panics if path regex pattern is malformed.
/// Panics if path pattern is malformed.
///
/// # Examples
/// ```
@@ -307,14 +303,14 @@ impl ResourceDef {
/// ```
pub fn prefix<T: IntoPatterns>(paths: T) -> Self {
profile_method!(prefix);
ResourceDef::new2(paths, true)
ResourceDef::construct(paths, true)
}
/// Constructs a new resource definition using a string pattern that performs prefix matching,
/// inserting a `/` to beginning of the pattern if absent and pattern is not empty.
/// ensuring a leading `/` if pattern is not empty.
///
/// # Panics
/// Panics if path regex pattern is malformed.
/// Panics if path pattern is malformed.
///
/// # Examples
/// ```
@@ -515,8 +511,8 @@ impl ResourceDef {
.collect::<Vec<_>>();
match patterns.len() {
1 => ResourceDef::new2(&patterns[0], other.is_prefix()),
_ => ResourceDef::new2(patterns, other.is_prefix()),
1 => ResourceDef::construct(&patterns[0], other.is_prefix()),
_ => ResourceDef::construct(patterns, other.is_prefix()),
}
}
@@ -881,8 +877,8 @@ impl ResourceDef {
}
}
fn new2<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self {
profile_method!(new2);
fn construct<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self {
profile_method!(construct);
let patterns = paths.patterns();
let (pat_type, segments) = match &patterns {
@@ -1814,7 +1810,7 @@ mod tests {
#[test]
#[should_panic]
fn prefix_plus_tail_match_is_allowed() {
fn prefix_plus_tail_match_disallowed() {
ResourceDef::prefix("/user/{id}*");
}
}

View File

@@ -3,47 +3,51 @@
## Unreleased - 2021-xx-xx
## 0.1.0-beta.10 - 2021-12-27
* No significant changes since `0.1.0-beta.9`.
## 0.1.0-beta.9 - 2021-12-17
* Re-export `actix_http::body::to_bytes`. [#2518]
* Update `actix_web::test` re-exports. [#2518]
- Re-export `actix_http::body::to_bytes`. [#2518]
- Update `actix_web::test` re-exports. [#2518]
[#2518]: https://github.com/actix/actix-web/pull/2518
## 0.1.0-beta.8 - 2021-12-11
* No significant changes since `0.1.0-beta.7`.
- No significant changes since `0.1.0-beta.7`.
## 0.1.0-beta.7 - 2021-11-22
* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408]
- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408]
[#2408]: https://github.com/actix/actix-web/pull/2408
## 0.1.0-beta.6 - 2021-11-15
* No significant changes from `0.1.0-beta.5`.
- No significant changes from `0.1.0-beta.5`.
## 0.1.0-beta.5 - 2021-10-20
* Updated rustls to v0.20. [#2414]
* Minimum supported Rust version (MSRV) is now 1.52.
- Updated rustls to v0.20. [#2414]
- Minimum supported Rust version (MSRV) is now 1.52.
[#2414]: https://github.com/actix/actix-web/pull/2414
## 0.1.0-beta.4 - 2021-09-09
* Minimum supported Rust version (MSRV) is now 1.51.
- Minimum supported Rust version (MSRV) is now 1.51.
## 0.1.0-beta.3 - 2021-06-20
* No significant changes from `0.1.0-beta.2`.
- No significant changes from `0.1.0-beta.2`.
## 0.1.0-beta.2 - 2021-04-17
* No significant changes from `0.1.0-beta.1`.
- No significant changes from `0.1.0-beta.1`.
## 0.1.0-beta.1 - 2021-04-02
* Move integration testing structs from `actix-web`. [#2112]
- Move integration testing structs from `actix-web`. [#2112]
[#2112]: https://github.com/actix/actix-web/pull/2112

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-test"
version = "0.1.0-beta.9"
version = "0.1.0-beta.10"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
@@ -29,13 +29,13 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
[dependencies]
actix-codec = "0.4.1"
actix-http = "3.0.0-beta.16"
actix-http-test = "3.0.0-beta.9"
actix-http = "3.0.0-beta.17"
actix-http-test = "3.0.0-beta.10"
actix-rt = "2.1"
actix-service = "2.0.0"
actix-utils = "3.0.0"
actix-web = { version = "4.0.0-beta.15", default-features = false, features = ["cookies"] }
awc = { version = "3.0.0-beta.14", default-features = false, features = ["cookies"] }
actix-web = { version = "4.0.0-beta.16", default-features = false, features = ["cookies"] }
awc = { version = "3.0.0-beta.15", default-features = false, features = ["cookies"] }
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
futures-util = { version = "0.3.7", default-features = false, features = [] }
@@ -45,4 +45,4 @@ serde_json = "1"
serde_urlencoded = "0.7"
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
tls-rustls = { package = "rustls", version = "0.20.0", optional = true }
tokio = { version = "1.2", features = ["sync"] }
tokio = { version = "1.8", features = ["sync"] }

View File

@@ -340,7 +340,7 @@ where
Connector::new()
.conn_lifetime(Duration::from_secs(0))
.timeout(Duration::from_millis(30000))
.ssl(builder.build())
.openssl(builder.build())
}
#[cfg(not(feature = "openssl"))]
{

View File

@@ -3,106 +3,110 @@
## Unreleased - 2021-xx-xx
## 4.0.0-beta.9 - 2021-12-27
* No significant changes since `4.0.0-beta.8`.
## 4.0.0-beta.8 - 2021-12-11
* Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920]
* Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920]
* Minimum supported Rust version (MSRV) is now 1.52.
- Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920]
- Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920]
- Minimum supported Rust version (MSRV) is now 1.52.
[#1920]: https://github.com/actix/actix-web/pull/1920
## 4.0.0-beta.7 - 2021-09-09
* Minimum supported Rust version (MSRV) is now 1.51.
- Minimum supported Rust version (MSRV) is now 1.51.
## 4.0.0-beta.6 - 2021-06-26
* Update `actix` to `0.12`. [#2277]
- Update `actix` to `0.12`. [#2277]
[#2277]: https://github.com/actix/actix-web/pull/2277
## 4.0.0-beta.5 - 2021-06-17
* No notable changes.
- No notable changes.
## 4.0.0-beta.4 - 2021-04-02
* No notable changes.
- No notable changes.
## 4.0.0-beta.3 - 2021-03-09
* No notable changes.
- No notable changes.
## 4.0.0-beta.2 - 2021-02-10
* No notable changes.
- No notable changes.
## 4.0.0-beta.1 - 2021-01-07
* Update `pin-project` to `1.0`.
* Update `bytes` to `1.0`. [#1813]
* `WebsocketContext::text` now takes an `Into<bytestring::ByteString>`. [#1864]
- Update `pin-project` to `1.0`.
- Update `bytes` to `1.0`. [#1813]
- `WebsocketContext::text` now takes an `Into<bytestring::ByteString>`. [#1864]
[#1813]: https://github.com/actix/actix-web/pull/1813
[#1864]: https://github.com/actix/actix-web/pull/1864
## 3.0.0 - 2020-09-11
* No significant changes from `3.0.0-beta.2`.
- No significant changes from `3.0.0-beta.2`.
## 3.0.0-beta.2 - 2020-09-10
* Update `actix-*` dependencies to latest versions.
- Update `actix-*` dependencies to latest versions.
## [3.0.0-beta.1] - 2020-xx-xx
* Update `actix-web` & `actix-http` dependencies to beta.1
* Bump minimum supported Rust version to 1.40
- Update `actix-web` & `actix-http` dependencies to beta.1
- Bump minimum supported Rust version to 1.40
## [3.0.0-alpha.1] - 2020-05-08
* Update the actix-web dependency to 3.0.0-alpha.1
* Update the actix dependency to 0.10.0-alpha.2
* Update the actix-http dependency to 2.0.0-alpha.3
- Update the actix-web dependency to 3.0.0-alpha.1
- Update the actix dependency to 0.10.0-alpha.2
- Update the actix-http dependency to 2.0.0-alpha.3
## [2.0.0] - 2019-12-20
* Release
- Release
## [2.0.0-alpha.1] - 2019-12-15
* Migrate to actix-web 2.0.0
- Migrate to actix-web 2.0.0
## [1.0.4] - 2019-12-07
* Allow comma-separated websocket subprotocols without spaces (#1172)
- Allow comma-separated websocket subprotocols without spaces (#1172)
## [1.0.3] - 2019-11-14
* Update actix-web and actix-http dependencies
- Update actix-web and actix-http dependencies
## [1.0.2] - 2019-07-20
* Add `ws::start_with_addr()`, returning the address of the created actor, along
- Add `ws::start_with_addr()`, returning the address of the created actor, along
with the `HttpResponse`.
* Add support for specifying protocols on websocket handshake #835
- Add support for specifying protocols on websocket handshake #835
## [1.0.1] - 2019-06-28
* Allow to use custom ws codec with `WebsocketContext` #925
- Allow to use custom ws codec with `WebsocketContext` #925
## [1.0.0] - 2019-05-29
* Update actix-http and actix-web
- Update actix-http and actix-web
## [0.1.0-alpha.3] - 2019-04-02
* Update actix-http and actix-web
- Update actix-http and actix-web
## [0.1.0-alpha.2] - 2019-03-29
* Update actix-http and actix-web
- Update actix-http and actix-web
## [0.1.0-alpha.1] - 2019-03-28
* Initial impl
- Initial impl

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-web-actors"
version = "4.0.0-beta.8"
version = "4.0.0-beta.9"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for Actix Web"
keywords = ["actix", "http", "web", "framework", "async"]
@@ -16,19 +16,19 @@ path = "src/lib.rs"
[dependencies]
actix = { version = "0.12.0", default-features = false }
actix-codec = "0.4.1"
actix-http = "3.0.0-beta.16"
actix-web = { version = "4.0.0-beta.15", default-features = false }
actix-http = "3.0.0-beta.17"
actix-web = { version = "4.0.0-beta.16", default-features = false }
bytes = "1"
bytestring = "1"
futures-core = { version = "0.3.7", default-features = false }
pin-project-lite = "0.2"
tokio = { version = "1", features = ["sync"] }
tokio = { version = "1.8", features = ["sync"] }
[dev-dependencies]
actix-rt = "2.2"
actix-test = "0.1.0-beta.9"
awc = { version = "3.0.0-beta.14", default-features = false }
actix-test = "0.1.0-beta.10"
awc = { version = "3.0.0-beta.15", default-features = false }
env_logger = "0.9"
futures-util = { version = "0.3.7", default-features = false }

View File

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

View File

@@ -4,101 +4,101 @@
## 0.5.0-beta.6 - 2021-12-11
* No significant changes since `0.5.0-beta.5`.
- No significant changes since `0.5.0-beta.5`.
## 0.5.0-beta.5 - 2021-10-20
* Improve error recovery potential when macro input is invalid. [#2410]
* Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409]
* Minimum supported Rust version (MSRV) is now 1.52.
- Improve error recovery potential when macro input is invalid. [#2410]
- Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409]
- Minimum supported Rust version (MSRV) is now 1.52.
[#2410]: https://github.com/actix/actix-web/pull/2410
[#2409]: https://github.com/actix/actix-web/pull/2409
## 0.5.0-beta.4 - 2021-09-09
* In routing macros, paths are now validated at compile time. [#2350]
* Minimum supported Rust version (MSRV) is now 1.51.
- In routing macros, paths are now validated at compile time. [#2350]
- Minimum supported Rust version (MSRV) is now 1.51.
[#2350]: https://github.com/actix/actix-web/pull/2350
## 0.5.0-beta.3 - 2021-06-17
* No notable changes.
- No notable changes.
## 0.5.0-beta.2 - 2021-03-09
* Preserve doc comments when using route macros. [#2022]
* Add `name` attribute to `route` macro. [#1934]
- Preserve doc comments when using route macros. [#2022]
- Add `name` attribute to `route` macro. [#1934]
[#2022]: https://github.com/actix/actix-web/pull/2022
[#1934]: https://github.com/actix/actix-web/pull/1934
## 0.5.0-beta.1 - 2021-02-10
* Use new call signature for `System::new`.
- Use new call signature for `System::new`.
## 0.4.0 - 2020-09-20
* Added compile success and failure testing. [#1677]
* Add `route` macro for supporting multiple HTTP methods guards. [#1674]
- Added compile success and failure testing. [#1677]
- Add `route` macro for supporting multiple HTTP methods guards. [#1674]
[#1677]: https://github.com/actix/actix-web/pull/1677
[#1674]: https://github.com/actix/actix-web/pull/1674
## 0.3.0 - 2020-09-11
* No significant changes from `0.3.0-beta.1`.
- No significant changes from `0.3.0-beta.1`.
## 0.3.0-beta.1 - 2020-07-14
* Add main entry-point macro that uses re-exported runtime. [#1559]
- Add main entry-point macro that uses re-exported runtime. [#1559]
[#1559]: https://github.com/actix/actix-web/pull/1559
## 0.2.2 - 2020-05-23
* Add resource middleware on actix-web-codegen [#1467]
- Add resource middleware on actix-web-codegen [#1467]
[#1467]: https://github.com/actix/actix-web/pull/1467
## 0.2.1 - 2020-02-25
* Add `#[allow(missing_docs)]` attribute to generated structs [#1368]
* Allow the handler function to be named as `config` [#1290]
- Add `#[allow(missing_docs)]` attribute to generated structs [#1368]
- Allow the handler function to be named as `config` [#1290]
[#1368]: https://github.com/actix/actix-web/issues/1368
[#1290]: https://github.com/actix/actix-web/issues/1290
## 0.2.0 - 2019-12-13
* Generate code for actix-web 2.0
- Generate code for actix-web 2.0
## 0.1.3 - 2019-10-14
* Bump up `syn` & `quote` to 1.0
* Provide better error message
- Bump up `syn` & `quote` to 1.0
- Provide better error message
## 0.1.2 - 2019-06-04
* Add macros for head, options, trace, connect and patch http methods
- Add macros for head, options, trace, connect and patch http methods
## 0.1.1 - 2019-06-01
* Add syn "extra-traits" feature
- Add syn "extra-traits" feature
## 0.1.0 - 2019-05-18
* Release
- Release
## 0.1.0-beta.1 - 2019-04-20
* Gen code for actix-web 1.0.0-beta.1
- Gen code for actix-web 1.0.0-beta.1
## 0.1.0-alpha.6 - 2019-04-14
* Gen code for actix-web 1.0.0-alpha.6
- Gen code for actix-web 1.0.0-alpha.6
## 0.1.0-alpha.1 - 2019-03-28
* Initial impl
- Initial impl

View File

@@ -23,9 +23,9 @@ actix-router = "0.5.0-beta.3"
[dev-dependencies]
actix-macros = "0.2.3"
actix-rt = "2.2"
actix-test = "0.1.0-beta.9"
actix-test = "0.1.0-beta.10"
actix-utils = "3.0.0"
actix-web = "4.0.0-beta.15"
actix-web = "4.0.0-beta.16"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
trybuild = "1"

View File

@@ -3,69 +3,82 @@
## Unreleased - 2021-xx-xx
## 3.0.0-beta.15 - 2021-12-27
- Rename `Connector::{ssl => openssl}`. [#2503]
- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503]
- `ClientRequest::send_body` now takes an `impl MessageBody`. [#2546]
- Rename `MessageBody => ResponseBody` to avoid conflicts with `MessageBody` trait. [#2546]
- `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]
[#2503]: https://github.com/actix/actix-web/pull/2503
[#2546]: https://github.com/actix/actix-web/pull/2546
## 3.0.0-beta.14 - 2021-12-17
* Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510]
- Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510]
[#2510]: https://github.com/actix/actix-web/pull/2510
## 3.0.0-beta.13 - 2021-12-11
* No significant changes since `3.0.0-beta.12`.
- No significant changes since `3.0.0-beta.12`.
## 3.0.0-beta.12 - 2021-11-30
* Update `actix-tls` to `3.0.0-rc.1`. [#2474]
- Update `actix-tls` to `3.0.0-rc.1`. [#2474]
[#2474]: https://github.com/actix/actix-web/pull/2474
## 3.0.0-beta.11 - 2021-11-22
* No significant changes from `3.0.0-beta.10`.
- No significant changes from `3.0.0-beta.10`.
## 3.0.0-beta.10 - 2021-11-15
* No significant changes from `3.0.0-beta.9`.
- No significant changes from `3.0.0-beta.9`.
## 3.0.0-beta.9 - 2021-10-20
* Updated rustls to v0.20. [#2414]
- Updated rustls to v0.20. [#2414]
[#2414]: https://github.com/actix/actix-web/pull/2414
## 3.0.0-beta.8 - 2021-09-09
### Changed
* Send headers within the redirect requests. [#2310]
- Send headers within the redirect requests. [#2310]
[#2310]: https://github.com/actix/actix-web/pull/2310
## 3.0.0-beta.7 - 2021-06-26
### Changed
* Change compression algorithm features flags. [#2250]
- Change compression algorithm features flags. [#2250]
[#2250]: https://github.com/actix/actix-web/pull/2250
## 3.0.0-beta.6 - 2021-06-17
* No significant changes since 3.0.0-beta.5.
- No significant changes since 3.0.0-beta.5.
## 3.0.0-beta.5 - 2021-04-17
### Removed
* Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148]
- Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148]
[#2148]: https://github.com/actix/actix-web/pull/2148
## 3.0.0-beta.4 - 2021-04-02
### Added
* Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114]
- Add `Client::headers` to get default mut reference of `HeaderMap` of client object. [#2114]
### Changed
* `ConnectorService` type is renamed to `BoxConnectorService`. [#2081]
* Fix http/https encoding when enabling `compress` feature. [#2116]
* Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header
- `ConnectorService` type is renamed to `BoxConnectorService`. [#2081]
- Fix http/https encoding when enabling `compress` feature. [#2116]
- Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header
methods now take `TryIntoHeaderPair` tuples. [#2094]
[#2081]: https://github.com/actix/actix-web/pull/2081
@@ -76,16 +89,16 @@
## 3.0.0-beta.3 - 2021-03-08
### Added
* `ClientResponse::timeout` for set the timeout of collecting response body. [#1931]
* `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024]
- `ClientResponse::timeout` for set the timeout of collecting response body. [#1931]
- `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024]
### Changed
* Feature `cookies` is now optional and enabled by default. [#1981]
* `ClientBuilder::connector` method would take `actix_http::client::Connector<T, U>` type. [#2008]
* Basic auth password now takes blank passwords as an empty string instead of Option. [#2050]
- Feature `cookies` is now optional and enabled by default. [#1981]
- `ClientBuilder::connector` method would take `actix_http::client::Connector<T, U>` type. [#2008]
- Basic auth password now takes blank passwords as an empty string instead of Option. [#2050]
### Removed
* `ClientBuilder::default` function [#2008]
- `ClientBuilder::default` function [#2008]
[#1931]: https://github.com/actix/actix-web/pull/1931
[#1981]: https://github.com/actix/actix-web/pull/1981
@@ -96,18 +109,18 @@
## 3.0.0-beta.2 - 2021-02-10
### Added
* `ClientRequest::insert_header` method which allows using typed headers. [#1869]
* `ClientRequest::append_header` method which allows using typed headers. [#1869]
* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
- `ClientRequest::insert_header` method which allows using typed headers. [#1869]
- `ClientRequest::append_header` method which allows using typed headers. [#1869]
- `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
### Changed
* Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905]
- Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905]
### Removed
* `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]
- `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]
[#1869]: https://github.com/actix/actix-web/pull/1869
[#1905]: https://github.com/actix/actix-web/pull/1905
@@ -116,32 +129,32 @@
## 3.0.0-beta.1 - 2021-01-07
### Changed
* Update `rand` to `0.8`
* Update `bytes` to `1.0`. [#1813]
* Update `rust-tls` to `0.19`. [#1813]
- Update `rand` to `0.8`
- Update `bytes` to `1.0`. [#1813]
- Update `rust-tls` to `0.19`. [#1813]
[#1813]: https://github.com/actix/actix-web/pull/1813
## 2.0.3 - 2020-11-29
### Fixed
* Ensure `actix-http` dependency uses same `serde_urlencoded`.
- Ensure `actix-http` dependency uses same `serde_urlencoded`.
## 2.0.2 - 2020-11-25
### Changed
* Upgrade `serde_urlencoded` to `0.7`. [#1773]
- Upgrade `serde_urlencoded` to `0.7`. [#1773]
[#1773]: https://github.com/actix/actix-web/pull/1773
## 2.0.1 - 2020-10-30
### Changed
* Upgrade `base64` to `0.13`. [#1744]
* Deprecate `ClientRequest::{if_some, if_true}`. [#1760]
- Upgrade `base64` to `0.13`. [#1744]
- Deprecate `ClientRequest::{if_some, if_true}`. [#1760]
### Fixed
* Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature
- Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature
is enabled [#1737]
[#1737]: https://github.com/actix/actix-web/pull/1737
@@ -151,209 +164,209 @@
## 2.0.0 - 2020-09-11
### Changed
* `Client::build` was renamed to `Client::builder`.
- `Client::build` was renamed to `Client::builder`.
## 2.0.0-beta.4 - 2020-09-09
### Changed
* Update actix-codec & actix-tls dependencies.
- Update actix-codec & actix-tls dependencies.
## 2.0.0-beta.3 - 2020-08-17
### Changed
* Update `rustls` to 0.18
- Update `rustls` to 0.18
## 2.0.0-beta.2 - 2020-07-21
### Changed
* Update `actix-http` dependency to 2.0.0-beta.2
- Update `actix-http` dependency to 2.0.0-beta.2
## [2.0.0-beta.1] - 2020-07-14
### Changed
* Update `actix-http` dependency to 2.0.0-beta.1
- Update `actix-http` dependency to 2.0.0-beta.1
## [2.0.0-alpha.2] - 2020-05-21
### Changed
* Implement `std::error::Error` for our custom errors [#1422]
* Bump minimum supported Rust version to 1.40
* Update `base64` dependency to 0.12
- Implement `std::error::Error` for our custom errors [#1422]
- Bump minimum supported Rust version to 1.40
- Update `base64` dependency to 0.12
[#1422]: https://github.com/actix/actix-web/pull/1422
## [2.0.0-alpha.1] - 2020-03-11
* Update `actix-http` dependency to 2.0.0-alpha.2
* Update `rustls` dependency to 0.17
* ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration
* ClientBuilder allowing to set max_http_version to limit HTTP version to be used
- Update `actix-http` dependency to 2.0.0-alpha.2
- Update `rustls` dependency to 0.17
- ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration
- ClientBuilder allowing to set max_http_version to limit HTTP version to be used
## [1.0.1] - 2019-12-15
* Fix compilation with default features off
- Fix compilation with default features off
## [1.0.0] - 2019-12-13
* Release
- Release
## [1.0.0-alpha.3]
* Migrate to `std::future`
- Migrate to `std::future`
## [0.2.8] - 2019-11-06
* Add support for setting query from Serialize type for client request.
- Add support for setting query from Serialize type for client request.
## [0.2.7] - 2019-09-25
### Added
* Remaining getter methods for `ClientRequest`'s private `head` field #1101
- Remaining getter methods for `ClientRequest`'s private `head` field #1101
## [0.2.6] - 2019-09-12
### Added
* Export frozen request related types.
- Export frozen request related types.
## [0.2.5] - 2019-09-11
### Added
* Add `FrozenClientRequest` to support retries for sending HTTP requests
- Add `FrozenClientRequest` to support retries for sending HTTP requests
### Changed
* Ensure that the `Host` header is set when initiating a WebSocket client connection.
- Ensure that the `Host` header is set when initiating a WebSocket client connection.
## [0.2.4] - 2019-08-13
### Changed
* Update percent-encoding to "2.1"
- Update percent-encoding to "2.1"
* Update serde_urlencoded to "0.6.1"
- Update serde_urlencoded to "0.6.1"
## [0.2.3] - 2019-08-01
### Added
* Add `rustls` support
- Add `rustls` support
## [0.2.2] - 2019-07-01
### Changed
* Always append a colon after username in basic auth
- Always append a colon after username in basic auth
* Upgrade `rand` dependency version to 0.7
- Upgrade `rand` dependency version to 0.7
## [0.2.1] - 2019-06-05
### Added
* Add license files
- Add license files
## [0.2.0] - 2019-05-12
### Added
* Allow to send headers in `Camel-Case` form.
- Allow to send headers in `Camel-Case` form.
### Changed
* Upgrade actix-http dependency.
- Upgrade actix-http dependency.
## [0.1.1] - 2019-04-19
### Added
* Allow to specify server address for http and ws requests.
- Allow to specify server address for http and ws requests.
### Changed
* `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref
- `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref
## [0.1.0] - 2019-04-16
* No changes
- No changes
## [0.1.0-alpha.6] - 2019-04-14
### Changed
* Do not set default headers for websocket request
- Do not set default headers for websocket request
## [0.1.0-alpha.5] - 2019-04-12
### Changed
* Do not set any default headers
- Do not set any default headers
### Added
* Add Debug impl for BoxedSocket
- Add Debug impl for BoxedSocket
## [0.1.0-alpha.4] - 2019-04-08
### Changed
* Update actix-http dependency
- Update actix-http dependency
## [0.1.0-alpha.3] - 2019-04-02
### Added
* Export `MessageBody` type
- Export `MessageBody` type
* `ClientResponse::json()` - Loads and parse `application/json` encoded body
- `ClientResponse::json()` - Loads and parse `application/json` encoded body
### Changed
* `ClientRequest::json()` accepts reference instead of object.
- `ClientRequest::json()` accepts reference instead of object.
* `ClientResponse::body()` does not consume response object.
- `ClientResponse::body()` does not consume response object.
* Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()`
- Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()`
## [0.1.0-alpha.2] - 2019-03-29
### Added
* Per request and session wide request timeout.
- Per request and session wide request timeout.
* Session wide headers.
- Session wide headers.
* Session wide basic and bearer auth.
- Session wide basic and bearer auth.
* Re-export `actix_http::client::Connector`.
- Re-export `actix_http::client::Connector`.
### Changed
* Allow to override request's uri
- Allow to override request's uri
* Export `ws` sub-module with websockets related types
- Export `ws` sub-module with websockets related types
## [0.1.0-alpha.1] - 2019-03-28
* Initial impl
- Initial impl

View File

@@ -1,6 +1,6 @@
[package]
name = "awc"
version = "3.0.0-beta.14"
version = "3.0.0-beta.15"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>",
@@ -60,9 +60,9 @@ dangerous-h2c = []
[dependencies]
actix-codec = "0.4.1"
actix-service = "2.0.0"
actix-http = "3.0.0-beta.16"
actix-http = "3.0.0-beta.17"
actix-rt = { version = "2.1", default-features = false }
actix-tls = { version = "3.0.0-rc.1", features = ["connect", "uri"] }
actix-tls = { version = "3.0.0", features = ["connect", "uri"] }
actix-utils = "3.0.0"
ahash = "0.7"
@@ -74,7 +74,7 @@ futures-core = { version = "0.3.7", default-features = false, features = ["alloc
futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] }
h2 = "0.3.9"
http = "0.2.5"
itoa = "0.4"
itoa = "1"
log =" 0.4"
mime = "0.3"
percent-encoding = "2.1"
@@ -83,7 +83,7 @@ rand = "0.8"
serde = "1.0"
serde_json = "1.0"
serde_urlencoded = "0.7"
tokio = { version = "1", features = ["sync"] }
tokio = { version = "1.8", features = ["sync"] }
cookie = { version = "0.15", features = ["percent-encode"], optional = true }
@@ -93,13 +93,13 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features
trust-dns-resolver = { version = "0.20.0", optional = true }
[dev-dependencies]
actix-http = { version = "3.0.0-beta.16", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] }
actix-server = "2.0.0-rc.1"
actix-test = { version = "0.1.0-beta.9", features = ["openssl", "rustls"] }
actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] }
actix-http = { version = "3.0.0-beta.17", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.10", features = ["openssl"] }
actix-server = "2.0.0-rc.2"
actix-test = { version = "0.1.0-beta.10", features = ["openssl", "rustls"] }
actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] }
actix-utils = "3.0.0"
actix-web = { version = "4.0.0-beta.15", features = ["openssl"] }
actix-web = { version = "4.0.0-beta.16", features = ["openssl"] }
brotli2 = "0.3.2"
env_logger = "0.9"

View File

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

View File

@@ -77,10 +77,27 @@ impl<B> AnyBody<B>
where
B: MessageBody + 'static,
{
/// Converts a [`MessageBody`] type into the best possible representation.
///
/// Checks size for `None` and tries to convert to `Bytes`. Otherwise, uses the `Body` variant.
pub fn from_message_body(body: B) -> Self
where
B: MessageBody,
{
if matches!(body.size(), BodySize::None) {
return Self::None;
}
match body.try_into_bytes() {
Ok(body) => Self::Bytes { body },
Err(body) => Self::new(body),
}
}
pub fn into_boxed(self) -> AnyBody {
match self {
Self::None => AnyBody::None,
Self::Bytes { body: bytes } => AnyBody::Bytes { body: bytes },
Self::Bytes { body } => AnyBody::Bytes { body },
Self::Body { body } => AnyBody::new_boxed(body),
}
}

View File

@@ -9,11 +9,13 @@ use actix_rt::net::{ActixStream, TcpStream};
use actix_service::{boxed, Service};
use crate::{
client::{ConnectInfo, Connector, ConnectorService, TcpConnectError, TcpConnection},
client::{
ClientConfig, ConnectInfo, Connector, ConnectorService, TcpConnectError, TcpConnection,
},
connect::DefaultConnector,
error::SendRequestError,
middleware::{NestTransform, Redirect, Transform},
Client, ClientConfig, ConnectRequest, ConnectResponse,
Client, ConnectRequest, ConnectResponse,
};
/// An HTTP Client builder

View File

@@ -267,7 +267,9 @@ where
Connection::Tls(ConnectionType::H2(conn)) => {
h2proto::send_request(conn, head.into(), body).await
}
_ => unreachable!("Plain Tcp connection can be used only in Http1 protocol"),
_ => {
unreachable!("Plain TCP connection can be used only with HTTP/1.1 protocol")
}
}
})
}

View File

@@ -22,11 +22,13 @@ use futures_core::{future::LocalBoxFuture, ready};
use http::Uri;
use pin_project_lite::pin_project;
use super::config::ConnectorConfig;
use super::connection::{Connection, ConnectionIo};
use super::error::ConnectError;
use super::pool::ConnectionPool;
use super::Connect;
use super::{
config::ConnectorConfig,
connection::{Connection, ConnectionIo},
error::ConnectError,
pool::ConnectionPool,
Connect,
};
enum OurTlsConnector {
#[allow(dead_code)] // only dead when no TLS feature is enabled
@@ -35,6 +37,12 @@ enum OurTlsConnector {
#[cfg(feature = "openssl")]
Openssl(actix_tls::connect::openssl::reexports::SslConnector),
/// Provided because building the OpenSSL context on newer versions can be very slow.
/// This prevents unnecessary calls to `.build()` while constructing the client connector.
#[cfg(feature = "openssl")]
#[allow(dead_code)] // false positive; used in build_ssl
OpensslBuilder(actix_tls::connect::openssl::reexports::SslConnectorBuilder),
#[cfg(feature = "rustls")]
Rustls(std::sync::Arc<actix_tls::connect::rustls::reexports::ClientConfig>),
}
@@ -57,7 +65,7 @@ pub struct Connector<T> {
config: ConnectorConfig,
#[allow(dead_code)] // only dead when no TLS feature is enabled
ssl: OurTlsConnector,
tls: OurTlsConnector,
}
impl Connector<()> {
@@ -72,7 +80,7 @@ impl Connector<()> {
Connector {
connector: TcpConnector::new(resolver::resolver()).service(),
config: ConnectorConfig::default(),
ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]),
tls: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]),
}
}
@@ -116,7 +124,7 @@ impl Connector<()> {
log::error!("Can not set ALPN protocol: {:?}", err);
}
OurTlsConnector::Openssl(ssl.build())
OurTlsConnector::OpensslBuilder(ssl)
}
}
@@ -134,7 +142,7 @@ impl<S> Connector<S> {
Connector {
connector,
config: self.config,
ssl: self.ssl,
tls: self.tls,
}
}
}
@@ -167,23 +175,35 @@ where
self
}
/// Use custom OpenSSL `SslConnector` instance.
#[cfg(feature = "openssl")]
/// Use custom `SslConnector` instance.
pub fn openssl(
mut self,
connector: actix_tls::connect::openssl::reexports::SslConnector,
) -> Self {
self.tls = OurTlsConnector::Openssl(connector);
self
}
/// See docs for [`Connector::openssl`].
#[doc(hidden)]
#[cfg(feature = "openssl")]
#[deprecated(since = "3.0.0", note = "Renamed to `Connector::openssl`.")]
pub fn ssl(
mut self,
connector: actix_tls::connect::openssl::reexports::SslConnector,
) -> Self {
self.ssl = OurTlsConnector::Openssl(connector);
self.tls = OurTlsConnector::Openssl(connector);
self
}
/// Use custom Rustls `ClientConfig` instance.
#[cfg(feature = "rustls")]
/// Use custom `ClientConfig` instance.
pub fn rustls(
mut self,
connector: std::sync::Arc<actix_tls::connect::rustls::reexports::ClientConfig>,
) -> Self {
self.ssl = OurTlsConnector::Rustls(connector);
self.tls = OurTlsConnector::Rustls(connector);
self
}
@@ -198,7 +218,7 @@ where
unimplemented!("actix-http client only supports versions http/1.1 & http/2")
}
};
self.ssl = Connector::build_ssl(versions);
self.tls = Connector::build_ssl(versions);
self
}
@@ -270,8 +290,8 @@ where
}
/// Finish configuration process and create connector service.
/// The Connector builder always concludes by calling `finish()` last in
/// its combinator chain.
///
/// The `Connector` builder always concludes by calling `finish()` last in its combinator chain.
pub fn finish(self) -> ConnectorService<S, IO> {
let local_address = self.config.local_address;
let timeout = self.config.timeout;
@@ -284,7 +304,15 @@ where
service: tcp_service_inner.clone(),
};
let tls_service = match self.ssl {
let tls = match self.tls {
#[cfg(feature = "openssl")]
OurTlsConnector::OpensslBuilder(builder) => {
OurTlsConnector::Openssl(builder.build())
}
tls => tls,
};
let tls_service = match tls {
OurTlsConnector::None => {
#[cfg(not(feature = "dangerous-h2c"))]
{
@@ -374,6 +402,11 @@ where
Some(actix_service::boxed::rc_service(tls_service))
}
#[cfg(feature = "openssl")]
OurTlsConnector::OpensslBuilder(_) => {
unreachable!("OpenSSL builder is built before this match.");
}
#[cfg(feature = "rustls")]
OurTlsConnector::Rustls(tls) => {
const H2: &[u8] = b"h2";
@@ -853,7 +886,7 @@ mod tests {
let connector = Connector {
connector: TcpConnector::new(resolver::resolver()).service(),
config: ConnectorConfig::default(),
ssl: OurTlsConnector::None,
tls: OurTlsConnector::None,
};
let client = Client::builder().connector(connector).finish();

View File

@@ -13,16 +13,17 @@ use actix_http::{
Payload, RequestHeadType, ResponseHead, StatusCode,
};
use actix_utils::future::poll_fn;
use bytes::buf::BufMut;
use bytes::{Bytes, BytesMut};
use bytes::{buf::BufMut, Bytes, BytesMut};
use futures_core::{ready, Stream};
use futures_util::SinkExt as _;
use pin_project_lite::pin_project;
use crate::BoxError;
use super::connection::{ConnectionIo, H1Connection};
use super::error::{ConnectError, SendRequestError};
use super::{
connection::{ConnectionIo, H1Connection},
error::{ConnectError, SendRequestError},
};
pub(crate) async fn send_request<Io, B>(
io: H1Connection<Io>,
@@ -123,7 +124,12 @@ where
Ok((head, Payload::None))
}
_ => Ok((head, Payload::Stream(Box::pin(PlStream::new(framed))))),
_ => Ok((
head,
Payload::Stream {
payload: Box::pin(PlStream::new(framed)),
},
)),
}
}

View File

@@ -52,9 +52,11 @@ where
let _ = match length {
BodySize::None => None,
BodySize::Sized(0) => req
.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0")),
BodySize::Sized(0) => {
#[allow(clippy::declare_interior_mutable_const)]
const HV_ZERO: HeaderValue = HeaderValue::from_static("0");
req.headers_mut().insert(CONTENT_LENGTH, HV_ZERO)
}
BodySize::Sized(len) => {
let mut buf = itoa::Buffer::new();

View File

@@ -1,6 +1,15 @@
//! HTTP client.
use http::Uri;
use std::{convert::TryFrom, rc::Rc, time::Duration};
use actix_http::{error::HttpError, header::HeaderMap, Method, RequestHead, Uri};
use actix_rt::net::TcpStream;
use actix_service::Service;
pub use actix_tls::connect::{
ConnectError as TcpConnectError, ConnectInfo, Connection as TcpConnection,
};
use crate::{ws, BoxConnectorService, ClientBuilder, ClientRequest};
mod config;
mod connection;
@@ -10,10 +19,6 @@ mod h1proto;
mod h2proto;
mod pool;
pub use actix_tls::connect::{
ConnectError as TcpConnectError, ConnectInfo, Connection as TcpConnection,
};
pub use self::connection::{Connection, ConnectionIo};
pub use self::connector::{Connector, ConnectorService};
pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError};
@@ -23,3 +28,176 @@ pub struct Connect {
pub uri: Uri,
pub addr: Option<std::net::SocketAddr>,
}
/// An asynchronous HTTP and WebSocket client.
///
/// You should take care to create, at most, one `Client` per thread. Otherwise, expect higher CPU
/// and memory usage.
///
/// # Examples
/// ```
/// use awc::Client;
///
/// #[actix_rt::main]
/// async fn main() {
/// let mut client = Client::default();
///
/// let res = client.get("http://www.rust-lang.org")
/// .insert_header(("User-Agent", "my-app/1.2"))
/// .send()
/// .await;
///
/// println!("Response: {:?}", res);
/// }
/// ```
#[derive(Clone)]
pub struct Client(pub(crate) ClientConfig);
#[derive(Clone)]
pub(crate) struct ClientConfig {
pub(crate) connector: BoxConnectorService,
pub(crate) default_headers: Rc<HeaderMap>,
pub(crate) timeout: Option<Duration>,
}
impl Default for Client {
fn default() -> Self {
ClientBuilder::new().finish()
}
}
impl Client {
/// Create new client instance with default settings.
pub fn new() -> Client {
Client::default()
}
/// Create `Client` builder.
/// This function is equivalent of `ClientBuilder::new()`.
pub fn builder() -> ClientBuilder<
impl Service<
ConnectInfo<Uri>,
Response = TcpConnection<Uri, TcpStream>,
Error = TcpConnectError,
> + Clone,
> {
ClientBuilder::new()
}
/// Construct HTTP request.
pub fn request<U>(&self, method: Method, url: U) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
let mut req = ClientRequest::new(method, url, self.0.clone());
for header in self.0.default_headers.iter() {
// header map is empty
// TODO: probably append instead
req = req.insert_header_if_none(header);
}
req
}
/// Create `ClientRequest` from `RequestHead`
///
/// It is useful for proxy requests. This implementation
/// copies all headers and the method.
pub fn request_from<U>(&self, url: U, head: &RequestHead) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
let mut req = self.request(head.method.clone(), url);
for header in head.headers.iter() {
req = req.insert_header_if_none(header);
}
req
}
/// Construct HTTP *GET* request.
pub fn get<U>(&self, url: U) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
self.request(Method::GET, url)
}
/// Construct HTTP *HEAD* request.
pub fn head<U>(&self, url: U) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
self.request(Method::HEAD, url)
}
/// Construct HTTP *PUT* request.
pub fn put<U>(&self, url: U) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
self.request(Method::PUT, url)
}
/// Construct HTTP *POST* request.
pub fn post<U>(&self, url: U) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
self.request(Method::POST, url)
}
/// Construct HTTP *PATCH* request.
pub fn patch<U>(&self, url: U) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
self.request(Method::PATCH, url)
}
/// Construct HTTP *DELETE* request.
pub fn delete<U>(&self, url: U) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
self.request(Method::DELETE, url)
}
/// Construct HTTP *OPTIONS* request.
pub fn options<U>(&self, url: U) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
self.request(Method::OPTIONS, url)
}
/// Initialize a WebSocket connection.
/// Returns a WebSocket connection builder.
pub fn ws<U>(&self, url: U) -> ws::WebsocketsRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
let mut req = ws::WebsocketsRequest::new(url, self.0.clone());
for (key, value) in self.0.default_headers.iter() {
req.head.headers.insert(key.clone(), value.clone());
}
req
}
/// Get default HeaderMap of Client.
///
/// Returns Some(&mut HeaderMap) when Client object is unique
/// (No other clone of client exists at the same time).
pub fn headers(&mut self) -> Option<&mut HeaderMap> {
Rc::get_mut(&mut self.0.default_headers)
}
}

View File

@@ -16,7 +16,7 @@ use crate::{
client::{
Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError,
},
response::ClientResponse,
ClientResponse,
};
pub type BoxConnectorService = Rc<

View File

@@ -1,5 +1,6 @@
//! HTTP client errors
// TODO: figure out how best to expose http::Error vs actix_http::Error
pub use actix_http::{
error::{HttpError, PayloadError},
header::HeaderValue,

View File

@@ -5,15 +5,16 @@ use futures_core::Stream;
use serde::Serialize;
use actix_http::{
body::MessageBody,
error::HttpError,
header::{HeaderMap, HeaderName, TryIntoHeaderValue},
Method, RequestHead, Uri,
};
use crate::{
any_body::AnyBody,
client::ClientConfig,
sender::{RequestSender, SendClientRequest},
BoxError, ClientConfig,
BoxError,
};
/// `FrozenClientRequest` struct represents cloneable client request.
@@ -46,7 +47,7 @@ impl FrozenClientRequest {
/// Send a body.
pub fn send_body<B>(&self, body: B) -> SendClientRequest
where
B: Into<AnyBody>,
B: MessageBody + 'static,
{
RequestSender::Rc(self.head.clone(), None).send_body(
self.addr,
@@ -159,7 +160,7 @@ impl FrozenSendBuilder {
/// Complete request construction and send a body.
pub fn send_body<B>(self, body: B) -> SendClientRequest
where
B: Into<AnyBody>,
B: MessageBody + 'static,
{
if let Some(e) = self.err {
return e.into();

View File

@@ -105,6 +105,11 @@
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
pub use actix_http::body;
#[cfg(feature = "cookies")]
pub use cookie;
mod any_body;
mod builder;
mod client;
@@ -113,203 +118,27 @@ pub mod error;
mod frozen;
pub mod middleware;
mod request;
mod response;
mod responses;
mod sender;
pub mod test;
pub mod ws;
// TODO: hmmmmmm
pub use actix_http as http;
#[cfg(feature = "cookies")]
pub use cookie;
pub mod http {
//! Various HTTP related types.
// TODO: figure out how best to expose http::Error vs actix_http::Error
pub use actix_http::{
header, uri, ConnectionType, Error, Method, StatusCode, Uri, Version,
};
}
pub use self::builder::ClientBuilder;
pub use self::client::Connector;
pub use self::client::{Client, Connector};
pub use self::connect::{BoxConnectorService, BoxedSocket, ConnectRequest, ConnectResponse};
pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder};
pub use self::request::ClientRequest;
pub use self::response::{ClientResponse, JsonBody, MessageBody};
#[allow(deprecated)]
pub use self::responses::{ClientResponse, JsonBody, MessageBody, ResponseBody};
pub use self::sender::SendClientRequest;
use std::{convert::TryFrom, rc::Rc, time::Duration};
use actix_http::{error::HttpError, header::HeaderMap, Method, RequestHead, Uri};
use actix_rt::net::TcpStream;
use actix_service::Service;
use self::client::{ConnectInfo, TcpConnectError, TcpConnection};
pub(crate) type BoxError = Box<dyn std::error::Error>;
/// An asynchronous HTTP and WebSocket client.
///
/// You should take care to create, at most, one `Client` per thread. Otherwise, expect higher CPU
/// and memory usage.
///
/// # Examples
/// ```
/// use awc::Client;
///
/// #[actix_rt::main]
/// async fn main() {
/// let mut client = Client::default();
///
/// let res = client.get("http://www.rust-lang.org")
/// .insert_header(("User-Agent", "my-app/1.2"))
/// .send()
/// .await;
///
/// println!("Response: {:?}", res);
/// }
/// ```
#[derive(Clone)]
pub struct Client(ClientConfig);
#[derive(Clone)]
pub(crate) struct ClientConfig {
pub(crate) connector: BoxConnectorService,
pub(crate) default_headers: Rc<HeaderMap>,
pub(crate) timeout: Option<Duration>,
}
impl Default for Client {
fn default() -> Self {
ClientBuilder::new().finish()
}
}
impl Client {
/// Create new client instance with default settings.
pub fn new() -> Client {
Client::default()
}
/// Create `Client` builder.
/// This function is equivalent of `ClientBuilder::new()`.
pub fn builder() -> ClientBuilder<
impl Service<
ConnectInfo<Uri>,
Response = TcpConnection<Uri, TcpStream>,
Error = TcpConnectError,
> + Clone,
> {
ClientBuilder::new()
}
/// Construct HTTP request.
pub fn request<U>(&self, method: Method, url: U) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
let mut req = ClientRequest::new(method, url, self.0.clone());
for header in self.0.default_headers.iter() {
// header map is empty
// TODO: probably append instead
req = req.insert_header_if_none(header);
}
req
}
/// Create `ClientRequest` from `RequestHead`
///
/// It is useful for proxy requests. This implementation
/// copies all headers and the method.
pub fn request_from<U>(&self, url: U, head: &RequestHead) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
let mut req = self.request(head.method.clone(), url);
for header in head.headers.iter() {
req = req.insert_header_if_none(header);
}
req
}
/// Construct HTTP *GET* request.
pub fn get<U>(&self, url: U) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
self.request(Method::GET, url)
}
/// Construct HTTP *HEAD* request.
pub fn head<U>(&self, url: U) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
self.request(Method::HEAD, url)
}
/// Construct HTTP *PUT* request.
pub fn put<U>(&self, url: U) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
self.request(Method::PUT, url)
}
/// Construct HTTP *POST* request.
pub fn post<U>(&self, url: U) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
self.request(Method::POST, url)
}
/// Construct HTTP *PATCH* request.
pub fn patch<U>(&self, url: U) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
self.request(Method::PATCH, url)
}
/// Construct HTTP *DELETE* request.
pub fn delete<U>(&self, url: U) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
self.request(Method::DELETE, url)
}
/// Construct HTTP *OPTIONS* request.
pub fn options<U>(&self, url: U) -> ClientRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
self.request(Method::OPTIONS, url)
}
/// Initialize a WebSocket connection.
/// Returns a WebSocket connection builder.
pub fn ws<U>(&self, url: U) -> ws::WebsocketsRequest
where
Uri: TryFrom<U>,
<Uri as TryFrom<U>>::Error: Into<HttpError>,
{
let mut req = ws::WebsocketsRequest::new(url, self.0.clone());
for (key, value) in self.0.default_headers.iter() {
req.head.headers.insert(key.clone(), value.clone());
}
req
}
/// Get default HeaderMap of Client.
///
/// Returns Some(&mut HeaderMap) when Client object is unique
/// (No other clone of client exists at the same time).
pub fn headers(&mut self) -> Option<&mut HeaderMap> {
Rc::get_mut(&mut self.0.default_headers)
}
}

View File

@@ -190,9 +190,7 @@ where
let body_new = if is_redirect {
// try to reuse body
match body {
Some(ref bytes) => AnyBody::Bytes {
body: bytes.clone(),
},
Some(ref bytes) => AnyBody::from(bytes.clone()),
// TODO: should this be AnyBody::Empty or AnyBody::None.
_ => AnyBody::empty(),
}

View File

@@ -5,17 +5,18 @@ use futures_core::Stream;
use serde::Serialize;
use actix_http::{
body::MessageBody,
error::HttpError,
header::{self, HeaderMap, HeaderValue, TryIntoHeaderPair},
ConnectionType, Method, RequestHead, Uri, Version,
};
use crate::{
any_body::AnyBody,
client::ClientConfig,
error::{FreezeRequestError, InvalidUrl},
frozen::FrozenClientRequest,
sender::{PrepForSendingError, RequestSender, SendClientRequest},
BoxError, ClientConfig,
BoxError,
};
#[cfg(feature = "cookies")]
@@ -26,20 +27,20 @@ use crate::cookie::{Cookie, CookieJar};
/// This type can be used to construct an instance of `ClientRequest` through a
/// builder-like pattern.
///
/// ```
/// #[actix_rt::main]
/// async fn main() {
/// let response = awc::Client::new()
/// .get("http://www.rust-lang.org") // <- Create request builder
/// .insert_header(("User-Agent", "Actix-web"))
/// .send() // <- Send HTTP request
/// .await;
/// ```no_run
/// # #[actix_rt::main]
/// # async fn main() {
/// let response = awc::Client::new()
/// .get("http://www.rust-lang.org") // <- Create request builder
/// .insert_header(("User-Agent", "Actix-web"))
/// .send() // <- Send HTTP request
/// .await;
///
/// response.and_then(|response| { // <- server HTTP response
/// println!("Response: {:?}", response);
/// Ok(())
/// });
/// }
/// response.and_then(|response| { // <- server HTTP response
/// println!("Response: {:?}", response);
/// Ok(())
/// });
/// # }
/// ```
pub struct ClientRequest {
pub(crate) head: RequestHead,
@@ -174,17 +175,13 @@ impl ClientRequest {
/// Append a header, keeping any that were set with an equivalent field name.
///
/// ```
/// # #[actix_rt::main]
/// # async fn main() {
/// # use awc::Client;
/// use awc::http::header::CONTENT_TYPE;
/// ```no_run
/// use awc::{http::header, Client};
///
/// Client::new()
/// .get("http://www.rust-lang.org")
/// .insert_header(("X-TEST", "value"))
/// .insert_header((CONTENT_TYPE, mime::APPLICATION_JSON));
/// # }
/// .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
/// ```
pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
match header.try_into_pair() {
@@ -252,23 +249,18 @@ impl ClientRequest {
/// Set a cookie
///
/// ```
/// #[actix_rt::main]
/// async fn main() {
/// let resp = awc::Client::new().get("https://www.rust-lang.org")
/// .cookie(
/// awc::cookie::Cookie::build("name", "value")
/// .domain("www.rust-lang.org")
/// .path("/")
/// .secure(true)
/// .http_only(true)
/// .finish(),
/// )
/// .send()
/// .await;
/// ```no_run
/// use awc::{cookie::Cookie, Client};
///
/// println!("Response: {:?}", resp);
/// }
/// # #[actix_rt::main]
/// # async fn main() {
/// let res = Client::new().get("https://httpbin.org/cookies")
/// .cookie(Cookie::new("name", "value"))
/// .send()
/// .await;
///
/// println!("Response: {:?}", res);
/// # }
/// ```
#[cfg(feature = "cookies")]
pub fn cookie(mut self, cookie: Cookie<'_>) -> Self {
@@ -340,7 +332,7 @@ impl ClientRequest {
/// Complete request construction and send body.
pub fn send_body<B>(self, body: B) -> SendClientRequest
where
B: Into<AnyBody>,
B: MessageBody + 'static,
{
let slf = match self.prep_for_sending() {
Ok(slf) => slf,

View File

@@ -1,556 +0,0 @@
use std::{
cell::{Ref, RefMut},
fmt,
future::Future,
io,
marker::PhantomData,
pin::Pin,
task::{Context, Poll},
time::{Duration, Instant},
};
use actix_http::{
error::PayloadError, header, header::HeaderMap, Extensions, HttpMessage, Payload,
PayloadStream, ResponseHead, StatusCode, Version,
};
use actix_rt::time::{sleep, Sleep};
use bytes::{Bytes, BytesMut};
use futures_core::{ready, Stream};
use serde::de::DeserializeOwned;
#[cfg(feature = "cookies")]
use crate::cookie::{Cookie, ParseError as CookieParseError};
use crate::error::JsonPayloadError;
/// Client Response
pub struct ClientResponse<S = PayloadStream> {
pub(crate) head: ResponseHead,
pub(crate) payload: Payload<S>,
pub(crate) timeout: ResponseTimeout,
}
/// helper enum with reusable sleep passed from `SendClientResponse`.
/// See `ClientResponse::_timeout` for reason.
pub(crate) enum ResponseTimeout {
Disabled(Option<Pin<Box<Sleep>>>),
Enabled(Pin<Box<Sleep>>),
}
impl Default for ResponseTimeout {
fn default() -> Self {
Self::Disabled(None)
}
}
impl ResponseTimeout {
fn poll_timeout(&mut self, cx: &mut Context<'_>) -> Result<(), PayloadError> {
match *self {
Self::Enabled(ref mut timeout) => {
if timeout.as_mut().poll(cx).is_ready() {
Err(PayloadError::Io(io::Error::new(
io::ErrorKind::TimedOut,
"Response Payload IO timed out",
)))
} else {
Ok(())
}
}
Self::Disabled(_) => Ok(()),
}
}
}
impl<S> HttpMessage for ClientResponse<S> {
type Stream = S;
fn headers(&self) -> &HeaderMap {
&self.head.headers
}
fn take_payload(&mut self) -> Payload<S> {
std::mem::replace(&mut self.payload, Payload::None)
}
fn extensions(&self) -> Ref<'_, Extensions> {
self.head.extensions()
}
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.head.extensions_mut()
}
}
impl<S> ClientResponse<S> {
/// Create new Request instance
pub(crate) fn new(head: ResponseHead, payload: Payload<S>) -> Self {
ClientResponse {
head,
payload,
timeout: ResponseTimeout::default(),
}
}
#[inline]
pub(crate) fn head(&self) -> &ResponseHead {
&self.head
}
/// Read the Request Version.
#[inline]
pub fn version(&self) -> Version {
self.head().version
}
/// Get the status from the server.
#[inline]
pub fn status(&self) -> StatusCode {
self.head().status
}
#[inline]
/// Returns request's headers.
pub fn headers(&self) -> &HeaderMap {
&self.head().headers
}
/// Set a body and return previous body value
pub fn map_body<F, U>(mut self, f: F) -> ClientResponse<U>
where
F: FnOnce(&mut ResponseHead, Payload<S>) -> Payload<U>,
{
let payload = f(&mut self.head, self.payload);
ClientResponse {
payload,
head: self.head,
timeout: self.timeout,
}
}
/// Set a timeout duration for [`ClientResponse`](self::ClientResponse).
///
/// This duration covers the duration of processing the response body stream
/// and would end it as timeout error when deadline met.
///
/// Disabled by default.
pub fn timeout(self, dur: Duration) -> Self {
let timeout = match self.timeout {
ResponseTimeout::Disabled(Some(mut timeout))
| ResponseTimeout::Enabled(mut timeout) => match Instant::now().checked_add(dur) {
Some(deadline) => {
timeout.as_mut().reset(deadline.into());
ResponseTimeout::Enabled(timeout)
}
None => ResponseTimeout::Enabled(Box::pin(sleep(dur))),
},
_ => ResponseTimeout::Enabled(Box::pin(sleep(dur))),
};
Self {
payload: self.payload,
head: self.head,
timeout,
}
}
/// This method does not enable timeout. It's used to pass the boxed `Sleep` from
/// `SendClientRequest` and reuse it's heap allocation together with it's slot in
/// timer wheel.
pub(crate) fn _timeout(mut self, timeout: Option<Pin<Box<Sleep>>>) -> Self {
self.timeout = ResponseTimeout::Disabled(timeout);
self
}
/// Load request cookies.
#[cfg(feature = "cookies")]
pub fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
struct Cookies(Vec<Cookie<'static>>);
if self.extensions().get::<Cookies>().is_none() {
let mut cookies = Vec::new();
for hdr in self.headers().get_all(&header::SET_COOKIE) {
let s = std::str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
cookies.push(Cookie::parse_encoded(s)?.into_owned());
}
self.extensions_mut().insert(Cookies(cookies));
}
Ok(Ref::map(self.extensions(), |ext| {
&ext.get::<Cookies>().unwrap().0
}))
}
/// Return request cookie.
#[cfg(feature = "cookies")]
pub fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
if let Ok(cookies) = self.cookies() {
for cookie in cookies.iter() {
if cookie.name() == name {
return Some(cookie.to_owned());
}
}
}
None
}
}
impl<S> ClientResponse<S>
where
S: Stream<Item = Result<Bytes, PayloadError>>,
{
/// Loads HTTP response's body.
pub fn body(&mut self) -> MessageBody<S> {
MessageBody::new(self)
}
/// Loads and parse `application/json` encoded body.
/// Return `JsonBody<T>` future. It resolves to a `T` value.
///
/// Returns error:
///
/// * content type is not `application/json`
/// * content length is greater than 256k
pub fn json<T: DeserializeOwned>(&mut self) -> JsonBody<S, T> {
JsonBody::new(self)
}
}
impl<S> Stream for ClientResponse<S>
where
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
{
type Item = Result<Bytes, PayloadError>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
this.timeout.poll_timeout(cx)?;
Pin::new(&mut this.payload).poll_next(cx)
}
}
impl<S> fmt::Debug for ClientResponse<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status(),)?;
writeln!(f, " headers:")?;
for (key, val) in self.headers().iter() {
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())
}
}
const DEFAULT_BODY_LIMIT: usize = 2 * 1024 * 1024;
/// Future that resolves to a complete HTTP message body.
pub struct MessageBody<S> {
length: Option<usize>,
timeout: ResponseTimeout,
body: Result<ReadBody<S>, Option<PayloadError>>,
}
impl<S> MessageBody<S>
where
S: Stream<Item = Result<Bytes, PayloadError>>,
{
/// Create `MessageBody` for request.
pub fn new(res: &mut ClientResponse<S>) -> MessageBody<S> {
let length = match res.headers().get(&header::CONTENT_LENGTH) {
Some(value) => {
let len = value.to_str().ok().and_then(|s| s.parse::<usize>().ok());
match len {
None => return Self::err(PayloadError::UnknownLength),
len => len,
}
}
None => None,
};
MessageBody {
length,
timeout: std::mem::take(&mut res.timeout),
body: Ok(ReadBody::new(res.take_payload(), DEFAULT_BODY_LIMIT)),
}
}
/// Change max size of payload. By default max size is 2048kB
pub fn limit(mut self, limit: usize) -> Self {
if let Ok(ref mut body) = self.body {
body.limit = limit;
}
self
}
fn err(e: PayloadError) -> Self {
MessageBody {
length: None,
timeout: ResponseTimeout::default(),
body: Err(Some(e)),
}
}
}
impl<S> Future for MessageBody<S>
where
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
{
type Output = Result<Bytes, PayloadError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
match this.body {
Err(ref mut err) => Poll::Ready(Err(err.take().unwrap())),
Ok(ref mut body) => {
if let Some(len) = this.length.take() {
if len > body.limit {
return Poll::Ready(Err(PayloadError::Overflow));
}
}
this.timeout.poll_timeout(cx)?;
Pin::new(body).poll(cx)
}
}
}
}
/// Response's payload json parser, it resolves to a deserialized `T` value.
///
/// Returns error:
///
/// * content type is not `application/json`
/// * content length is greater than 64k
pub struct JsonBody<S, U> {
length: Option<usize>,
err: Option<JsonPayloadError>,
timeout: ResponseTimeout,
fut: Option<ReadBody<S>>,
_phantom: PhantomData<U>,
}
impl<S, U> JsonBody<S, U>
where
S: Stream<Item = Result<Bytes, PayloadError>>,
U: DeserializeOwned,
{
/// Create `JsonBody` for request.
pub fn new(res: &mut ClientResponse<S>) -> Self {
// check content-type
let json = if let Ok(Some(mime)) = res.mime_type() {
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
} else {
false
};
if !json {
return JsonBody {
length: None,
fut: None,
timeout: ResponseTimeout::default(),
err: Some(JsonPayloadError::ContentType),
_phantom: PhantomData,
};
}
let mut len = None;
if let Some(l) = res.headers().get(&header::CONTENT_LENGTH) {
if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::<usize>() {
len = Some(l)
}
}
}
JsonBody {
length: len,
err: None,
timeout: std::mem::take(&mut res.timeout),
fut: Some(ReadBody::new(res.take_payload(), 65536)),
_phantom: PhantomData,
}
}
/// Change max size of payload. By default max size is 64kB
pub fn limit(mut self, limit: usize) -> Self {
if let Some(ref mut fut) = self.fut {
fut.limit = limit;
}
self
}
}
impl<T, U> Unpin for JsonBody<T, U>
where
T: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
U: DeserializeOwned,
{
}
impl<T, U> Future for JsonBody<T, U>
where
T: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
U: DeserializeOwned,
{
type Output = Result<U, JsonPayloadError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(err) = self.err.take() {
return Poll::Ready(Err(err));
}
if let Some(len) = self.length.take() {
if len > self.fut.as_ref().unwrap().limit {
return Poll::Ready(Err(JsonPayloadError::Payload(PayloadError::Overflow)));
}
}
self.timeout
.poll_timeout(cx)
.map_err(JsonPayloadError::Payload)?;
let body = ready!(Pin::new(&mut self.get_mut().fut.as_mut().unwrap()).poll(cx))?;
Poll::Ready(serde_json::from_slice::<U>(&body).map_err(JsonPayloadError::from))
}
}
struct ReadBody<S> {
stream: Payload<S>,
buf: BytesMut,
limit: usize,
}
impl<S> ReadBody<S> {
fn new(stream: Payload<S>, limit: usize) -> Self {
Self {
stream,
buf: BytesMut::new(),
limit,
}
}
}
impl<S> Future for ReadBody<S>
where
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
{
type Output = Result<Bytes, PayloadError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
while let Some(chunk) = ready!(Pin::new(&mut this.stream).poll_next(cx)?) {
if (this.buf.len() + chunk.len()) > this.limit {
return Poll::Ready(Err(PayloadError::Overflow));
}
this.buf.extend_from_slice(&chunk);
}
Poll::Ready(Ok(this.buf.split().freeze()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::{Deserialize, Serialize};
use crate::{http::header, test::TestResponse};
#[actix_rt::test]
async fn test_body() {
let mut req = TestResponse::with_header((header::CONTENT_LENGTH, "xxxx")).finish();
match req.body().await.err().unwrap() {
PayloadError::UnknownLength => {}
_ => unreachable!("error"),
}
let mut req = TestResponse::with_header((header::CONTENT_LENGTH, "10000000")).finish();
match req.body().await.err().unwrap() {
PayloadError::Overflow => {}
_ => unreachable!("error"),
}
let mut req = TestResponse::default()
.set_payload(Bytes::from_static(b"test"))
.finish();
assert_eq!(req.body().await.ok().unwrap(), Bytes::from_static(b"test"));
let mut req = TestResponse::default()
.set_payload(Bytes::from_static(b"11111111111111"))
.finish();
match req.body().limit(5).await.err().unwrap() {
PayloadError::Overflow => {}
_ => unreachable!("error"),
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct MyObject {
name: String,
}
fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool {
match err {
JsonPayloadError::Payload(PayloadError::Overflow) => {
matches!(other, JsonPayloadError::Payload(PayloadError::Overflow))
}
JsonPayloadError::ContentType => matches!(other, JsonPayloadError::ContentType),
_ => false,
}
}
#[actix_rt::test]
async fn test_json_body() {
let mut req = TestResponse::default().finish();
let json = JsonBody::<_, MyObject>::new(&mut req).await;
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let mut req = TestResponse::default()
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/text"),
))
.finish();
let json = JsonBody::<_, MyObject>::new(&mut req).await;
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let mut req = TestResponse::default()
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
))
.insert_header((
header::CONTENT_LENGTH,
header::HeaderValue::from_static("10000"),
))
.finish();
let json = JsonBody::<_, MyObject>::new(&mut req).limit(100).await;
assert!(json_eq(
json.err().unwrap(),
JsonPayloadError::Payload(PayloadError::Overflow)
));
let mut req = TestResponse::default()
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
))
.insert_header((
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
))
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
let json = JsonBody::<_, MyObject>::new(&mut req).await;
assert_eq!(
json.ok().unwrap(),
MyObject {
name: "test".to_owned()
}
);
}
}

View File

@@ -0,0 +1,192 @@
use std::{
future::Future,
marker::PhantomData,
mem,
pin::Pin,
task::{Context, Poll},
};
use actix_http::{error::PayloadError, header, HttpMessage};
use bytes::Bytes;
use futures_core::{ready, Stream};
use pin_project_lite::pin_project;
use serde::de::DeserializeOwned;
use super::{read_body::ReadBody, ResponseTimeout, DEFAULT_BODY_LIMIT};
use crate::{error::JsonPayloadError, ClientResponse};
pin_project! {
/// A `Future` that reads a body stream, parses JSON, resolving to a deserialized `T`.
///
/// # Errors
/// `Future` implementation returns error if:
/// - content type is not `application/json`;
/// - content length is greater than [limit](JsonBody::limit) (default: 2 MiB).
pub struct JsonBody<S, T> {
#[pin]
body: Option<ReadBody<S>>,
length: Option<usize>,
timeout: ResponseTimeout,
err: Option<JsonPayloadError>,
_phantom: PhantomData<T>,
}
}
impl<S, T> JsonBody<S, T>
where
S: Stream<Item = Result<Bytes, PayloadError>>,
T: DeserializeOwned,
{
/// Creates a JSON body stream reader from a response by taking its payload.
pub fn new(res: &mut ClientResponse<S>) -> Self {
// check content-type
let json = if let Ok(Some(mime)) = res.mime_type() {
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
} else {
false
};
if !json {
return JsonBody {
length: None,
body: None,
timeout: ResponseTimeout::default(),
err: Some(JsonPayloadError::ContentType),
_phantom: PhantomData,
};
}
let length = res
.headers()
.get(&header::CONTENT_LENGTH)
.and_then(|len_hdr| len_hdr.to_str().ok())
.and_then(|len_str| len_str.parse::<usize>().ok());
JsonBody {
body: Some(ReadBody::new(res.take_payload(), DEFAULT_BODY_LIMIT)),
length,
timeout: mem::take(&mut res.timeout),
err: None,
_phantom: PhantomData,
}
}
/// Change max size of payload. Default limit is 2 MiB.
pub fn limit(mut self, limit: usize) -> Self {
if let Some(ref mut fut) = self.body {
fut.limit = limit;
}
self
}
}
impl<S, T> Future for JsonBody<S, T>
where
S: Stream<Item = Result<Bytes, PayloadError>>,
T: DeserializeOwned,
{
type Output = Result<T, JsonPayloadError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
if let Some(err) = this.err.take() {
return Poll::Ready(Err(err));
}
if let Some(len) = this.length.take() {
let body = Option::as_ref(&this.body).unwrap();
if len > body.limit {
return Poll::Ready(Err(JsonPayloadError::Payload(PayloadError::Overflow)));
}
}
this.timeout
.poll_timeout(cx)
.map_err(JsonPayloadError::Payload)?;
let body = ready!(this.body.as_pin_mut().unwrap().poll(cx))?;
Poll::Ready(serde_json::from_slice::<T>(&body).map_err(JsonPayloadError::from))
}
}
#[cfg(test)]
mod tests {
use actix_http::BoxedPayloadStream;
use serde::{Deserialize, Serialize};
use static_assertions::assert_impl_all;
use super::*;
use crate::{http::header, test::TestResponse};
assert_impl_all!(JsonBody<BoxedPayloadStream, String>: Unpin);
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct MyObject {
name: String,
}
fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool {
match err {
JsonPayloadError::Payload(PayloadError::Overflow) => {
matches!(other, JsonPayloadError::Payload(PayloadError::Overflow))
}
JsonPayloadError::ContentType => matches!(other, JsonPayloadError::ContentType),
_ => false,
}
}
#[actix_rt::test]
async fn read_json_body() {
let mut req = TestResponse::default().finish();
let json = JsonBody::<_, MyObject>::new(&mut req).await;
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let mut req = TestResponse::default()
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/text"),
))
.finish();
let json = JsonBody::<_, MyObject>::new(&mut req).await;
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let mut req = TestResponse::default()
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
))
.insert_header((
header::CONTENT_LENGTH,
header::HeaderValue::from_static("10000"),
))
.finish();
let json = JsonBody::<_, MyObject>::new(&mut req).limit(100).await;
assert!(json_eq(
json.err().unwrap(),
JsonPayloadError::Payload(PayloadError::Overflow)
));
let mut req = TestResponse::default()
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
))
.insert_header((
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
))
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
let json = JsonBody::<_, MyObject>::new(&mut req).await;
assert_eq!(
json.ok().unwrap(),
MyObject {
name: "test".to_owned()
}
);
}
}

49
awc/src/responses/mod.rs Normal file
View File

@@ -0,0 +1,49 @@
use std::{future::Future, io, pin::Pin, task::Context};
use actix_http::error::PayloadError;
use actix_rt::time::Sleep;
mod json_body;
mod read_body;
mod response;
mod response_body;
pub use self::json_body::JsonBody;
pub use self::response::ClientResponse;
#[allow(deprecated)]
pub use self::response_body::{MessageBody, ResponseBody};
/// Default body size limit: 2 MiB
const DEFAULT_BODY_LIMIT: usize = 2 * 1024 * 1024;
/// Helper enum with reusable sleep passed from `SendClientResponse`.
///
/// See [`ClientResponse::_timeout`] for reason.
pub(crate) enum ResponseTimeout {
Disabled(Option<Pin<Box<Sleep>>>),
Enabled(Pin<Box<Sleep>>),
}
impl Default for ResponseTimeout {
fn default() -> Self {
Self::Disabled(None)
}
}
impl ResponseTimeout {
fn poll_timeout(&mut self, cx: &mut Context<'_>) -> Result<(), PayloadError> {
match *self {
Self::Enabled(ref mut timeout) => {
if timeout.as_mut().poll(cx).is_ready() {
Err(PayloadError::Io(io::Error::new(
io::ErrorKind::TimedOut,
"Response Payload IO timed out",
)))
} else {
Ok(())
}
}
Self::Disabled(_) => Ok(()),
}
}
}

View File

@@ -0,0 +1,61 @@
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
use actix_http::{error::PayloadError, Payload};
use bytes::{Bytes, BytesMut};
use futures_core::{ready, Stream};
use pin_project_lite::pin_project;
pin_project! {
pub(crate) struct ReadBody<S> {
#[pin]
pub(crate) stream: Payload<S>,
pub(crate) buf: BytesMut,
pub(crate) limit: usize,
}
}
impl<S> ReadBody<S> {
pub(crate) fn new(stream: Payload<S>, limit: usize) -> Self {
Self {
stream,
buf: BytesMut::new(),
limit,
}
}
}
impl<S> Future for ReadBody<S>
where
S: Stream<Item = Result<Bytes, PayloadError>>,
{
type Output = Result<Bytes, PayloadError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.project();
while let Some(chunk) = ready!(this.stream.as_mut().poll_next(cx)?) {
if (this.buf.len() + chunk.len()) > *this.limit {
return Poll::Ready(Err(PayloadError::Overflow));
}
this.buf.extend_from_slice(&chunk);
}
Poll::Ready(Ok(this.buf.split().freeze()))
}
}
#[cfg(test)]
mod tests {
use static_assertions::assert_impl_all;
use super::*;
use crate::any_body::AnyBody;
assert_impl_all!(ReadBody<()>: Unpin);
assert_impl_all!(ReadBody<AnyBody>: Unpin);
}

View File

@@ -0,0 +1,257 @@
use std::{
cell::{Ref, RefMut},
fmt, mem,
pin::Pin,
task::{Context, Poll},
time::{Duration, Instant},
};
use actix_http::{
error::PayloadError, header, header::HeaderMap, BoxedPayloadStream, Extensions,
HttpMessage, Payload, ResponseHead, StatusCode, Version,
};
use actix_rt::time::{sleep, Sleep};
use bytes::Bytes;
use futures_core::Stream;
use pin_project_lite::pin_project;
use serde::de::DeserializeOwned;
#[cfg(feature = "cookies")]
use crate::cookie::{Cookie, ParseError as CookieParseError};
use super::{JsonBody, ResponseBody, ResponseTimeout};
pin_project! {
/// Client Response
pub struct ClientResponse<S = BoxedPayloadStream> {
pub(crate) head: ResponseHead,
#[pin]
pub(crate) payload: Payload<S>,
pub(crate) timeout: ResponseTimeout,
}
}
impl<S> ClientResponse<S> {
/// Create new Request instance
pub(crate) fn new(head: ResponseHead, payload: Payload<S>) -> Self {
ClientResponse {
head,
payload,
timeout: ResponseTimeout::default(),
}
}
#[inline]
pub(crate) fn head(&self) -> &ResponseHead {
&self.head
}
/// Read the Request Version.
#[inline]
pub fn version(&self) -> Version {
self.head().version
}
/// Get the status from the server.
#[inline]
pub fn status(&self) -> StatusCode {
self.head().status
}
#[inline]
/// Returns request's headers.
pub fn headers(&self) -> &HeaderMap {
&self.head().headers
}
/// Set a body and return previous body value
pub fn map_body<F, U>(mut self, f: F) -> ClientResponse<U>
where
F: FnOnce(&mut ResponseHead, Payload<S>) -> Payload<U>,
{
let payload = f(&mut self.head, self.payload);
ClientResponse {
payload,
head: self.head,
timeout: self.timeout,
}
}
/// Set a timeout duration for [`ClientResponse`](self::ClientResponse).
///
/// This duration covers the duration of processing the response body stream
/// and would end it as timeout error when deadline met.
///
/// Disabled by default.
pub fn timeout(self, dur: Duration) -> Self {
let timeout = match self.timeout {
ResponseTimeout::Disabled(Some(mut timeout))
| ResponseTimeout::Enabled(mut timeout) => match Instant::now().checked_add(dur) {
Some(deadline) => {
timeout.as_mut().reset(deadline.into());
ResponseTimeout::Enabled(timeout)
}
None => ResponseTimeout::Enabled(Box::pin(sleep(dur))),
},
_ => ResponseTimeout::Enabled(Box::pin(sleep(dur))),
};
Self {
payload: self.payload,
head: self.head,
timeout,
}
}
/// This method does not enable timeout. It's used to pass the boxed `Sleep` from
/// `SendClientRequest` and reuse it's heap allocation together with it's slot in
/// timer wheel.
pub(crate) fn _timeout(mut self, timeout: Option<Pin<Box<Sleep>>>) -> Self {
self.timeout = ResponseTimeout::Disabled(timeout);
self
}
/// Load request cookies.
#[cfg(feature = "cookies")]
pub fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
struct Cookies(Vec<Cookie<'static>>);
if self.extensions().get::<Cookies>().is_none() {
let mut cookies = Vec::new();
for hdr in self.headers().get_all(&header::SET_COOKIE) {
let s = std::str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
cookies.push(Cookie::parse_encoded(s)?.into_owned());
}
self.extensions_mut().insert(Cookies(cookies));
}
Ok(Ref::map(self.extensions(), |ext| {
&ext.get::<Cookies>().unwrap().0
}))
}
/// Return request cookie.
#[cfg(feature = "cookies")]
pub fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
if let Ok(cookies) = self.cookies() {
for cookie in cookies.iter() {
if cookie.name() == name {
return Some(cookie.to_owned());
}
}
}
None
}
}
impl<S> ClientResponse<S>
where
S: Stream<Item = Result<Bytes, PayloadError>>,
{
/// Returns a [`Future`] that consumes the body stream and resolves to [`Bytes`].
///
/// # Errors
/// `Future` implementation returns error if:
/// - content type is not `application/json`
/// - content length is greater than [limit](JsonBody::limit) (default: 2 MiB)
///
/// # Examples
/// ```no_run
/// # use awc::Client;
/// # use bytes::Bytes;
/// # #[actix_rt::main]
/// # async fn async_ctx() -> Result<(), Box<dyn std::error::Error>> {
/// let client = Client::default();
/// let mut res = client.get("https://httpbin.org/robots.txt").send().await?;
/// let body: Bytes = res.body().await?;
/// # Ok(())
/// # }
/// ```
///
/// [`Future`]: std::future::Future
pub fn body(&mut self) -> ResponseBody<S> {
ResponseBody::new(self)
}
/// Returns a [`Future`] consumes the body stream, parses JSON, and resolves to a deserialized
/// `T` value.
///
/// # Errors
/// Future returns error if:
/// - content type is not `application/json`;
/// - content length is greater than [limit](JsonBody::limit) (default: 2 MiB).
///
/// # Examples
/// ```no_run
/// # use awc::Client;
/// # #[actix_rt::main]
/// # async fn async_ctx() -> Result<(), Box<dyn std::error::Error>> {
/// let client = Client::default();
/// let mut res = client.get("https://httpbin.org/json").send().await?;
/// let val = res.json::<serde_json::Value>().await?;
/// assert!(val.is_object());
/// # Ok(())
/// # }
/// ```
///
/// [`Future`]: std::future::Future
pub fn json<T: DeserializeOwned>(&mut self) -> JsonBody<S, T> {
JsonBody::new(self)
}
}
impl<S> fmt::Debug for ClientResponse<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status(),)?;
writeln!(f, " headers:")?;
for (key, val) in self.headers().iter() {
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())
}
}
impl<S> HttpMessage for ClientResponse<S> {
type Stream = S;
fn headers(&self) -> &HeaderMap {
&self.head.headers
}
fn take_payload(&mut self) -> Payload<S> {
mem::replace(&mut self.payload, Payload::None)
}
fn extensions(&self) -> Ref<'_, Extensions> {
self.head.extensions()
}
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.head.extensions_mut()
}
}
impl<S> Stream for ClientResponse<S>
where
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
{
type Item = Result<Bytes, PayloadError>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.project();
this.timeout.poll_timeout(cx)?;
this.payload.poll_next(cx)
}
}
#[cfg(test)]
mod tests {
use static_assertions::assert_impl_all;
use super::*;
use crate::any_body::AnyBody;
assert_impl_all!(ClientResponse: Unpin);
assert_impl_all!(ClientResponse<()>: Unpin);
assert_impl_all!(ClientResponse<AnyBody>: Unpin);
}

View File

@@ -0,0 +1,144 @@
use std::{
future::Future,
mem,
pin::Pin,
task::{Context, Poll},
};
use actix_http::{error::PayloadError, header, HttpMessage};
use bytes::Bytes;
use futures_core::Stream;
use pin_project_lite::pin_project;
use super::{read_body::ReadBody, ResponseTimeout, DEFAULT_BODY_LIMIT};
use crate::ClientResponse;
pin_project! {
/// A `Future` that reads a body stream, resolving as [`Bytes`].
///
/// # Errors
/// `Future` implementation returns error if:
/// - content type is not `application/json`;
/// - content length is greater than [limit](JsonBody::limit) (default: 2 MiB).
pub struct ResponseBody<S> {
#[pin]
body: Option<ReadBody<S>>,
length: Option<usize>,
timeout: ResponseTimeout,
err: Option<PayloadError>,
}
}
#[deprecated(since = "3.0.0", note = "Renamed to `ResponseBody`.")]
pub type MessageBody<B> = ResponseBody<B>;
impl<S> ResponseBody<S>
where
S: Stream<Item = Result<Bytes, PayloadError>>,
{
/// Creates a body stream reader from a response by taking its payload.
pub fn new(res: &mut ClientResponse<S>) -> ResponseBody<S> {
let length = match res.headers().get(&header::CONTENT_LENGTH) {
Some(value) => {
let len = value.to_str().ok().and_then(|s| s.parse::<usize>().ok());
match len {
None => return Self::err(PayloadError::UnknownLength),
len => len,
}
}
None => None,
};
ResponseBody {
body: Some(ReadBody::new(res.take_payload(), DEFAULT_BODY_LIMIT)),
length,
timeout: mem::take(&mut res.timeout),
err: None,
}
}
/// Change max size limit of payload.
///
/// The default limit is 2 MiB.
pub fn limit(mut self, limit: usize) -> Self {
if let Some(ref mut body) = self.body {
body.limit = limit;
}
self
}
fn err(err: PayloadError) -> Self {
ResponseBody {
body: None,
length: None,
timeout: ResponseTimeout::default(),
err: Some(err),
}
}
}
impl<S> Future for ResponseBody<S>
where
S: Stream<Item = Result<Bytes, PayloadError>>,
{
type Output = Result<Bytes, PayloadError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
if let Some(err) = this.err.take() {
return Poll::Ready(Err(err));
}
if let Some(len) = this.length.take() {
let body = Option::as_ref(&this.body).unwrap();
if len > body.limit {
return Poll::Ready(Err(PayloadError::Overflow));
}
}
this.timeout.poll_timeout(cx)?;
this.body.as_pin_mut().unwrap().poll(cx)
}
}
#[cfg(test)]
mod tests {
use static_assertions::assert_impl_all;
use super::*;
use crate::{http::header, test::TestResponse};
assert_impl_all!(ResponseBody<()>: Unpin);
#[actix_rt::test]
async fn read_body() {
let mut req = TestResponse::with_header((header::CONTENT_LENGTH, "xxxx")).finish();
match req.body().await.err().unwrap() {
PayloadError::UnknownLength => {}
_ => unreachable!("error"),
}
let mut req = TestResponse::with_header((header::CONTENT_LENGTH, "10000000")).finish();
match req.body().await.err().unwrap() {
PayloadError::Overflow => {}
_ => unreachable!("error"),
}
let mut req = TestResponse::default()
.set_payload(Bytes::from_static(b"test"))
.finish();
assert_eq!(req.body().await.ok().unwrap(), Bytes::from_static(b"test"));
let mut req = TestResponse::default()
.set_payload(Bytes::from_static(b"11111111111111"))
.finish();
match req.body().limit(5).await.err().unwrap() {
PayloadError::Overflow => {}
_ => unreachable!("error"),
}
}
}

View File

@@ -8,7 +8,7 @@ use std::{
};
use actix_http::{
body::BodyStream,
body::{BodyStream, MessageBody},
error::HttpError,
header::{self, HeaderMap, HeaderName, TryIntoHeaderValue},
RequestHead, RequestHeadType,
@@ -20,12 +20,13 @@ use futures_core::Stream;
use serde::Serialize;
#[cfg(feature = "__compress")]
use actix_http::{encoding::Decoder, header::ContentEncoding, Payload, PayloadStream};
use actix_http::{encoding::Decoder, header::ContentEncoding, Payload};
use crate::{
any_body::AnyBody,
client::ClientConfig,
error::{FreezeRequestError, InvalidUrl, SendRequestError},
BoxError, ClientConfig, ClientResponse, ConnectRequest, ConnectResponse,
BoxError, ClientResponse, ConnectRequest, ConnectResponse,
};
#[derive(Debug, From)]
@@ -91,7 +92,7 @@ impl SendClientRequest {
#[cfg(feature = "__compress")]
impl Future for SendClientRequest {
type Output = Result<ClientResponse<Decoder<Payload<PayloadStream>>>, SendRequestError>;
type Output = Result<ClientResponse<Decoder<Payload>>, SendRequestError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
@@ -108,12 +109,13 @@ impl Future for SendClientRequest {
res.into_client_response()._timeout(delay.take()).map_body(
|head, payload| {
if *response_decompress {
Payload::Stream(Decoder::from_headers(payload, &head.headers))
Payload::Stream {
payload: Decoder::from_headers(payload, &head.headers),
}
} else {
Payload::Stream(Decoder::new(
payload,
ContentEncoding::Identity,
))
Payload::Stream {
payload: Decoder::new(payload, ContentEncoding::Identity),
}
}
},
)
@@ -188,15 +190,17 @@ impl RequestSender {
body: B,
) -> SendClientRequest
where
B: Into<AnyBody>,
B: MessageBody + 'static,
{
let req = match self {
RequestSender::Owned(head) => {
ConnectRequest::Client(RequestHeadType::Owned(head), body.into(), addr)
}
RequestSender::Owned(head) => ConnectRequest::Client(
RequestHeadType::Owned(head),
AnyBody::from_message_body(body).into_boxed(),
addr,
),
RequestSender::Rc(head, extra_headers) => ConnectRequest::Client(
RequestHeadType::Rc(head, extra_headers),
body.into(),
AnyBody::from_message_body(body).into_boxed(),
addr,
),
};
@@ -228,9 +232,7 @@ impl RequestSender {
response_decompress,
timeout,
config,
AnyBody::Bytes {
body: Bytes::from(body),
},
AnyBody::from_message_body(body.into_bytes()),
)
}
@@ -259,9 +261,7 @@ impl RequestSender {
response_decompress,
timeout,
config,
AnyBody::Bytes {
body: Bytes::from(body),
},
AnyBody::from_message_body(body.into_bytes()),
)
}

View File

@@ -65,7 +65,7 @@ impl TestResponse {
/// Set response's payload
pub fn set_payload<B: Into<Bytes>>(mut self, data: B) -> Self {
let mut payload = h1::Payload::empty();
let (_, mut payload) = h1::Payload::create(true);
payload.unread_data(data.into());
self.payload = Some(payload.into());
self
@@ -90,7 +90,8 @@ impl TestResponse {
if let Some(pl) = self.payload {
ClientResponse::new(head, pl)
} else {
ClientResponse::new(head, h1::Payload::empty().into())
let (_, payload) = h1::Payload::create(true);
ClientResponse::new(head, payload.into())
}
}
}

View File

@@ -31,19 +31,19 @@ use std::{convert::TryFrom, fmt, net::SocketAddr, str};
use actix_codec::Framed;
use actix_http::{ws, Payload, RequestHead};
use actix_rt::time::timeout;
use actix_service::Service;
use actix_service::Service as _;
pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message};
use crate::{
client::ClientConfig,
connect::{BoxedSocket, ConnectRequest},
error::{HttpError, InvalidUrl, SendRequestError, WsClientError},
http::{
header::{self, HeaderName, HeaderValue, TryIntoHeaderValue, AUTHORIZATION},
ConnectionType, Method, StatusCode, Uri, Version,
},
response::ClientResponse,
ClientConfig,
ClientResponse,
};
#[cfg(feature = "cookies")]
@@ -300,13 +300,16 @@ impl WebsocketsRequest {
}
self.head.set_connection_type(ConnectionType::Upgrade);
#[allow(clippy::declare_interior_mutable_const)]
const HV_WEBSOCKET: HeaderValue = HeaderValue::from_static("websocket");
self.head.headers.insert(header::UPGRADE, HV_WEBSOCKET);
#[allow(clippy::declare_interior_mutable_const)]
const HV_THIRTEEN: HeaderValue = HeaderValue::from_static("13");
self.head
.headers
.insert(header::UPGRADE, HeaderValue::from_static("websocket"));
self.head.headers.insert(
header::SEC_WEBSOCKET_VERSION,
HeaderValue::from_static("13"),
);
.insert(header::SEC_WEBSOCKET_VERSION, HV_THIRTEEN);
if let Some(protocols) = self.protocols.take() {
self.head.headers.insert(

View File

@@ -58,7 +58,7 @@ async fn test_connection_window_size() {
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
let client = awc::Client::builder()
.connector(awc::Connector::new().ssl(builder.build()))
.connector(awc::Connector::new().openssl(builder.build()))
.initial_window_size(100)
.initial_connection_window_size(100)
.finish();

View File

@@ -72,7 +72,7 @@ async fn test_connection_reuse_h2() {
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
let client = awc::Client::builder()
.connector(awc::Connector::new().ssl(builder.build()))
.connector(awc::Connector::new().openssl(builder.build()))
.finish();
// req 1

View File

@@ -55,6 +55,11 @@ else
read -p "Update version to: " NEW_VERSION
fi
# strip leading v from input
if [ "${NEW_VERSION:0:1}" = "v" ]; then
NEW_VERSION="${NEW_VERSION:1}"
fi
DATE="$(date -u +"%Y-%m-%d")"
echo "updating from $CURRENT_VERSION => $NEW_VERSION ($DATE)"
@@ -124,15 +129,20 @@ SHORT_PACKAGE_NAME="$(echo $PACKAGE_NAME | sed 's/^actix-web-//' | sed 's/^actix
GIT_TAG="$(echo $SHORT_PACKAGE_NAME-v$NEW_VERSION)"
RELEASE_TITLE="$(echo $PACKAGE_NAME: v$NEW_VERSION)"
if [ "$(echo $NEW_VERSION | grep beta)" ] || [ "$(echo $NEW_VERSION | grep rc)" ] || [ "$(echo $NEW_VERSION | grep alpha)" ]; then
PRERELEASE="--prerelease"
fi
echo
echo "GitHub release command:"
echo "gh release create \"$GIT_TAG\" --draft --title \"$RELEASE_TITLE\" --notes-file \"$CHANGE_CHUNK_FILE\" --prerelease"
GH_CMD="gh release create \"$GIT_TAG\" --draft --title \"$RELEASE_TITLE\" --notes-file \"$CHANGE_CHUNK_FILE\" ${PRERELEASE:-}"
echo "$GH_CMD"
read -p "Submit draft GH release: (y/N) " GH_RELEASE
GH_RELEASE="${GH_RELEASE:-n}"
if [ "$GH_RELEASE" = 'y' ] || [ "$GH_RELEASE" = 'Y' ]; then
gh release create "$GIT_TAG" --draft --title "$RELEASE_TITLE" --notes-file "$CHANGE_CHUNK_FILE" --prerelease
eval "$GH_CMD"
fi
echo

View File

@@ -122,9 +122,10 @@ impl<T> App<T> {
self.app_data(Data::new(data))
}
/// Add application data factory. This function is similar to `.data()` but it accepts a
/// "data factory". Data values are constructed asynchronously during application
/// initialization, before the server starts accepting requests.
/// Add application data factory that resolves asynchronously.
///
/// Data items are constructed during application initialization, before the server starts
/// accepting requests.
pub fn data_factory<F, Out, D, E>(mut self, data: F) -> Self
where
F: Fn() -> Out + 'static,
@@ -150,6 +151,7 @@ impl<T> App<T> {
}
.boxed_local()
}));
self
}
@@ -200,11 +202,9 @@ impl<T> App<T> {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new()
/// .route("/test1", web::get().to(index))
/// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()));
/// }
/// let app = App::new()
/// .route("/test1", web::get().to(index))
/// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()));
/// ```
pub fn route(self, path: &str, mut route: Route) -> Self {
self.service(
@@ -243,13 +243,11 @@ impl<T> App<T> {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new()
/// .service(
/// web::resource("/index.html").route(web::get().to(index)))
/// .default_service(
/// web::route().to(|| HttpResponse::NotFound()));
/// }
/// let app = App::new()
/// .service(
/// web::resource("/index.html").route(web::get().to(index)))
/// .default_service(
/// web::route().to(|| HttpResponse::NotFound()));
/// ```
///
/// It is also possible to use static files as default service.
@@ -257,14 +255,12 @@ impl<T> App<T> {
/// ```
/// use actix_web::{web, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .service(
/// web::resource("/index.html").to(|| HttpResponse::Ok()))
/// .default_service(
/// web::to(|| HttpResponse::NotFound())
/// );
/// }
/// let app = App::new()
/// .service(
/// web::resource("/index.html").to(|| HttpResponse::Ok()))
/// .default_service(
/// web::to(|| HttpResponse::NotFound())
/// );
/// ```
pub fn default_service<F, U>(mut self, svc: F) -> Self
where
@@ -486,19 +482,21 @@ where
#[cfg(test)]
mod tests {
use actix_service::Service;
use actix_service::Service as _;
use actix_utils::future::{err, ok};
use bytes::Bytes;
use super::*;
use crate::http::{
header::{self, HeaderValue},
Method, StatusCode,
use crate::{
http::{
header::{self, HeaderValue},
Method, StatusCode,
},
middleware::DefaultHeaders,
service::ServiceRequest,
test::{call_service, init_service, read_body, try_init_service, TestRequest},
web, HttpRequest, HttpResponse,
};
use crate::middleware::DefaultHeaders;
use crate::service::ServiceRequest;
use crate::test::{call_service, init_service, read_body, try_init_service, TestRequest};
use crate::{web, HttpRequest, HttpResponse};
#[actix_rt::test]
async fn test_default_resource() {
@@ -707,24 +705,24 @@ mod tests {
assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
}
/// compile-only test for returning app type from function
pub fn foreign_app_type() -> App<
impl ServiceFactory<
ServiceRequest,
Response = ServiceResponse<impl MessageBody>,
Config = (),
InitError = (),
Error = Error,
>,
> {
App::new()
// logger can be removed without affecting the return type
.wrap(crate::middleware::Logger::default())
.route("/", web::to(|| async { "hello" }))
}
#[test]
fn return_foreign_app_type() {
let _app = foreign_app_type();
fn can_be_returned_from_fn() {
/// compile-only test for returning app type from function
pub fn my_app() -> App<
impl ServiceFactory<
ServiceRequest,
Response = ServiceResponse<impl MessageBody>,
Config = (),
InitError = (),
Error = Error,
>,
> {
App::new()
// logger can be removed without affecting the return type
.wrap(crate::middleware::Logger::default())
.route("/", web::to(|| async { "hello" }))
}
let _ = init_service(my_app());
}
}

View File

@@ -221,6 +221,7 @@ where
req_data,
)
};
self.service.call(ServiceRequest::new(req, payload))
}
}
@@ -235,6 +236,7 @@ where
}
pub struct AppRoutingFactory {
#[allow(clippy::type_complexity)]
services: Rc<
[(
ResourceDef,

View File

@@ -24,6 +24,7 @@ pub struct AppService {
config: AppConfig,
root: bool,
default: Rc<HttpNewService>,
#[allow(clippy::type_complexity)]
services: Vec<(
ResourceDef,
HttpNewService,
@@ -48,6 +49,7 @@ impl AppService {
self.root
}
#[allow(clippy::type_complexity)]
pub(crate) fn into_services(
self,
) -> (

View File

@@ -14,7 +14,7 @@ pub use crate::types::form::UrlEncoded;
pub use crate::types::json::JsonBody;
pub use crate::types::readlines::Readlines;
pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, Response, ResponseHead};
pub use actix_http::{Extensions, Payload, RequestHead, Response, ResponseHead};
pub use actix_router::{Path, ResourceDef, ResourcePath, Url};
pub use actix_server::{Server, ServerHandle};
pub use actix_service::{
@@ -102,49 +102,3 @@ impl<B> BodyEncoding for crate::HttpResponse<B> {
self
}
}
// TODO: remove this if it doesn't appear to be needed
#[allow(dead_code)]
#[derive(Debug)]
pub(crate) enum AnyBody {
None,
Full { body: crate::web::Bytes },
Boxed { body: actix_http::body::BoxBody },
}
impl crate::body::MessageBody for AnyBody {
type Error = crate::BoxError;
/// Body size hint.
fn size(&self) -> crate::body::BodySize {
match self {
AnyBody::None => crate::body::BodySize::None,
AnyBody::Full { body } => body.size(),
AnyBody::Boxed { body } => body.size(),
}
}
/// Attempt to pull out the next chunk of body bytes.
fn poll_next(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Result<crate::web::Bytes, Self::Error>>> {
match self.get_mut() {
AnyBody::None => std::task::Poll::Ready(None),
AnyBody::Full { body } => {
let bytes = std::mem::take(body);
std::task::Poll::Ready(Some(Ok(bytes)))
}
AnyBody::Boxed { body } => body.as_pin_mut().poll_next(cx),
}
}
fn try_into_bytes(self) -> Result<crate::web::Bytes, Self> {
match self {
AnyBody::None => Ok(crate::web::Bytes::new()),
AnyBody::Full { body } => Ok(body),
_ => Err(self),
}
}
}

View File

@@ -15,14 +15,12 @@
//! ```
//! use actix_web::{web, http, dev, guard, App, HttpResponse};
//!
//! fn main() {
//! App::new().service(web::resource("/index.html").route(
//! web::route()
//! .guard(guard::Post())
//! .guard(guard::fn_guard(|head| head.method == http::Method::GET))
//! .to(|| HttpResponse::MethodNotAllowed()))
//! );
//! }
//! App::new().service(web::resource("/index.html").route(
//! web::route()
//! .guard(guard::Post())
//! .guard(guard::fn_guard(|head| head.method == http::Method::GET))
//! .to(|| HttpResponse::MethodNotAllowed()))
//! );
//! ```
#![allow(non_snake_case)]
@@ -53,16 +51,14 @@ impl Guard for Rc<dyn Guard> {
/// ```
/// use actix_web::{guard, web, App, HttpResponse};
///
/// fn main() {
/// App::new().service(web::resource("/index.html").route(
/// web::route()
/// .guard(
/// guard::fn_guard(
/// |req| req.headers()
/// .contains_key("content-type")))
/// .to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// App::new().service(web::resource("/index.html").route(
/// web::route()
/// .guard(
/// guard::fn_guard(
/// |req| req.headers()
/// .contains_key("content-type")))
/// .to(|| HttpResponse::MethodNotAllowed()))
/// );
/// ```
pub fn fn_guard<F>(f: F) -> impl Guard
where
@@ -96,13 +92,11 @@ where
/// ```
/// use actix_web::{web, guard, App, HttpResponse};
///
/// fn main() {
/// App::new().service(web::resource("/index.html").route(
/// web::route()
/// .guard(guard::Any(guard::Get()).or(guard::Post()))
/// .to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// App::new().service(web::resource("/index.html").route(
/// web::route()
/// .guard(guard::Any(guard::Get()).or(guard::Post()))
/// .to(|| HttpResponse::MethodNotAllowed()))
/// );
/// ```
pub fn Any<F: Guard + 'static>(guard: F) -> AnyGuard {
AnyGuard(vec![Box::new(guard)])
@@ -135,14 +129,12 @@ impl Guard for AnyGuard {
/// ```
/// use actix_web::{guard, web, App, HttpResponse};
///
/// fn main() {
/// App::new().service(web::resource("/index.html").route(
/// web::route()
/// .guard(
/// guard::All(guard::Get()).and(guard::Header("content-type", "text/plain")))
/// .to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// App::new().service(web::resource("/index.html").route(
/// web::route()
/// .guard(
/// guard::All(guard::Get()).and(guard::Header("content-type", "text/plain")))
/// .to(|| HttpResponse::MethodNotAllowed()))
/// );
/// ```
pub fn All<F: Guard + 'static>(guard: F) -> AllGuard {
AllGuard(vec![Box::new(guard)])
@@ -270,13 +262,11 @@ impl Guard for HeaderGuard {
/// ```
/// use actix_web::{web, guard::Host, App, HttpResponse};
///
/// fn main() {
/// App::new().service(
/// web::resource("/index.html")
/// .guard(Host("www.rust-lang.org"))
/// .to(|| HttpResponse::MethodNotAllowed())
/// );
/// }
/// App::new().service(
/// web::resource("/index.html")
/// .guard(Host("www.rust-lang.org"))
/// .to(|| HttpResponse::MethodNotAllowed())
/// );
/// ```
pub fn Host<H: AsRef<str>>(host: H) -> HostGuard {
HostGuard(host.as_ref().to_string(), None)

View File

@@ -3,35 +3,92 @@ use std::future::Future;
use actix_service::{boxed, fn_service};
use crate::{
body::MessageBody,
service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse},
BoxError, FromRequest, HttpResponse, Responder,
FromRequest, HttpResponse, Responder,
};
/// A request handler is an async function that accepts zero or more parameters that can be
/// extracted from a request (i.e., [`impl FromRequest`]) and returns a type that can be converted
/// into an [`HttpResponse`] (that is, it impls the [`Responder`] trait).
/// The interface for request handlers.
///
/// If you got the error `the trait Handler<_, _, _> is not implemented`, then your function is not
/// a valid handler. See <https://actix.rs/docs/handlers> for more information.
/// # What Is A Request Handler
/// A request handler has three requirements:
/// 1. It is an async function (or a function/closure that returns an appropriate future);
/// 1. The function accepts zero or more parameters that implement [`FromRequest`];
/// 1. The async function (or future) resolves to a type that can be converted into an
/// [`HttpResponse`] (i.e., it implements the [`Responder`] trait).
///
/// [`impl FromRequest`]: crate::FromRequest
pub trait Handler<T, R>: Clone + 'static
where
R: Future,
R::Output: Responder,
{
fn call(&self, param: T) -> R;
/// # Compiler Errors
/// If you get the error `the trait Handler<_> is not implemented`, then your handler does not
/// fulfill one or more of the above requirements.
///
/// Unfortunately we cannot provide a better compile error message (while keeping the trait's
/// flexibility) unless a stable alternative to [`#[rustc_on_unimplemented]`][on_unimpl] is added
/// to Rust.
///
/// # How Do Handlers Receive Variable Numbers Of Arguments
/// Rest assured there is no macro magic here; it's just traits.
///
/// The first thing to note is that [`FromRequest`] is implemented for tuples (up to 12 in length).
///
/// Secondly, the `Handler` trait is implemented for functions (up to an [arity] of 12) in a way
/// that aligns their parameter positions with a corresponding tuple of types (becoming the `Args`
/// type parameter for this trait).
///
/// Thanks to Rust's type system, Actix Web can infer the function parameter types. During the
/// extraction step, the parameter types are described as a tuple type, [`from_request`] is run on
/// that tuple, and the `Handler::call` implementation for that particular function arity
/// destructures the tuple into it's component types and calls your handler function with them.
///
/// In pseudo-code the process looks something like this:
/// ```ignore
/// async fn my_handler(body: String, state: web::Data<MyState>) -> impl Responder {
/// ...
/// }
///
/// // the function params above described as a tuple, names do not matter, only position
/// type InferredMyHandlerArgs = (String, web::Data<MyState>);
///
/// // create tuple of arguments to be passed to handler
/// let args = InferredMyHandlerArgs::from_request(&request, &payload).await;
///
/// // call handler with argument tuple
/// let response = Handler::call(&my_handler, args).await;
///
/// // which is effectively...
///
/// let (body, state) = args;
/// let response = my_handler(body, state).await;
/// ```
///
/// This is the source code for the 2-parameter implementation of `Handler` to help illustrate the
/// bounds of the handler call after argument extraction:
/// ```ignore
/// impl<Func, Arg1, Arg2, R> Handler<(Arg1, Arg2), R> for Func
/// where
/// Func: Fn(Arg1, Arg2) -> R + Clone + 'static,
/// R: Future,
/// R::Output: Responder,
/// {
/// fn call(&self, (arg1, arg2): (Arg1, Arg2)) -> R {
/// (self)(arg1, arg2)
/// }
/// }
/// ```
///
/// [arity]: https://en.wikipedia.org/wiki/Arity
/// [`from_request`]: FromRequest::from_request
/// [on_unimpl]: https://github.com/rust-lang/rust/issues/29628
pub trait Handler<Args>: Clone + 'static {
type Output;
type Future: Future<Output = Self::Output>;
fn call(&self, args: Args) -> Self::Future;
}
pub(crate) fn handler_service<F, T, R>(handler: F) -> BoxedHttpServiceFactory
pub(crate) fn handler_service<F, Args>(handler: F) -> BoxedHttpServiceFactory
where
F: Handler<T, R>,
T: FromRequest,
R: Future,
R::Output: Responder,
<R::Output as Responder>::Body: MessageBody,
<<R::Output as Responder>::Body as MessageBody>::Error: Into<BoxError>,
F: Handler<Args>,
Args: FromRequest,
F::Output: Responder,
{
boxed::factory(fn_service(move |req: ServiceRequest| {
let handler = handler.clone();
@@ -39,7 +96,7 @@ where
async move {
let (req, mut payload) = req.into_parts();
let res = match T::from_request(&req, &mut payload).await {
let res = match Args::from_request(&req, &mut payload).await {
Err(err) => HttpResponse::from_error(err),
Ok(data) => handler
@@ -59,17 +116,20 @@ where
///
/// # Examples
/// ```ignore
/// factory_tuple! {} // implements Handler for types: fn() -> Res
/// factory_tuple! { A B C } // implements Handler for types: fn(A, B, C) -> Res
/// factory_tuple! {} // implements Handler for types: fn() -> R
/// factory_tuple! { A B C } // implements Handler for types: fn(A, B, C) -> R
/// ```
macro_rules! factory_tuple ({ $($param:ident)* } => {
impl<Func, $($param,)* Res> Handler<($($param,)*), Res> for Func
where Func: Fn($($param),*) -> Res + Clone + 'static,
Res: Future,
Res::Output: Responder,
impl<Func, Fut, $($param,)*> Handler<($($param,)*)> for Func
where Func: Fn($($param),*) -> Fut + Clone + 'static,
Fut: Future,
{
type Output = Fut::Output;
type Future = Fut;
#[inline]
#[allow(non_snake_case)]
fn call(&self, ($($param,)*): ($($param,)*)) -> Res {
fn call(&self, ($($param,)*): ($($param,)*)) -> Self::Future {
(self)($($param,)*)
}
}

View File

@@ -2,4 +2,5 @@
pub mod header;
// TODO: figure out how best to expose http::Error vs actix_http::Error
pub use actix_http::{uri, ConnectionType, Error, Method, StatusCode, Uri, Version};

View File

@@ -66,7 +66,6 @@
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![allow(clippy::needless_doctest_main, clippy::type_complexity)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
@@ -106,6 +105,7 @@ pub use cookie;
pub use crate::app::App;
pub use crate::error::{Error, ResponseError, Result};
pub use crate::extract::FromRequest;
pub use crate::handler::Handler;
pub use crate::request::HttpRequest;
pub use crate::resource::Resource;
pub use crate::response::{CustomizeResponder, HttpResponse, HttpResponseBuilder, Responder};

Some files were not shown because too many files have changed in this diff Show More