mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-12 21:43:41 +02:00
Compare commits
19 Commits
files-v0.6
...
router-v0.
Author | SHA1 | Date | |
---|---|---|---|
9668a2396f | |||
cb7347216c | |||
ae7f71e317 | |||
bc89f0bfc2 | |||
c959916346 | |||
f227e880d7 | |||
68ad81f989 | |||
f2e736719a | |||
81ef12a0fd | |||
1bc1538118 | |||
1cc3e7b24c | |||
3dd98c308c | |||
cb5d9a7e64 | |||
5ee555462f | |||
ad159f5219 | |||
2ffc21dd4f | |||
7b8a392ef5 | |||
3c7ccf5521 | |||
e7cae5a95b |
12
CHANGES.md
12
CHANGES.md
@ -1,6 +1,18 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
### Added
|
||||||
|
- `HttpResponse::add_removal_cookie`. [#2586]
|
||||||
|
- `Logger::log_target`. [#2594]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- `HttpRequest::req_data[_mut]()`; request-local data is still available through `.extensions()`. [#2585]
|
||||||
|
- `HttpRequestBuilder::del_cookie`. [#2591]
|
||||||
|
|
||||||
|
[#2585]: https://github.com/actix/actix-web/pull/2585
|
||||||
|
[#2586]: https://github.com/actix/actix-web/pull/2586
|
||||||
|
[#2591]: https://github.com/actix/actix-web/pull/2591
|
||||||
|
[#2594]: https://github.com/actix/actix-web/pull/2594
|
||||||
|
|
||||||
|
|
||||||
## 4.0.0-beta.20 - 2022-01-14
|
## 4.0.0-beta.20 - 2022-01-14
|
||||||
|
10
Cargo.toml
10
Cargo.toml
@ -72,13 +72,13 @@ experimental-io-uring = ["actix-server/io-uring"]
|
|||||||
actix-codec = "0.4.1"
|
actix-codec = "0.4.1"
|
||||||
actix-macros = "0.2.3"
|
actix-macros = "0.2.3"
|
||||||
actix-rt = "2.6"
|
actix-rt = "2.6"
|
||||||
actix-server = "2.0.0-rc.4"
|
actix-server = "2"
|
||||||
actix-service = "2.0.0"
|
actix-service = "2.0.0"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-tls = { version = "3.0.0", default-features = false, optional = true }
|
actix-tls = { version = "3.0.0", default-features = false, optional = true }
|
||||||
|
|
||||||
actix-http = "3.0.0-beta.18"
|
actix-http = "3.0.0-beta.18"
|
||||||
actix-router = "0.5.0-rc.1"
|
actix-router = "0.5.0-rc.2"
|
||||||
actix-web-codegen = "0.5.0-rc.1"
|
actix-web-codegen = "0.5.0-rc.1"
|
||||||
|
|
||||||
ahash = "0.7"
|
ahash = "0.7"
|
||||||
@ -109,7 +109,7 @@ actix-files = "0.6.0-beta.14"
|
|||||||
actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] }
|
actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] }
|
||||||
awc = { version = "3.0.0-beta.18", features = ["openssl"] }
|
awc = { version = "3.0.0-beta.18", features = ["openssl"] }
|
||||||
|
|
||||||
brotli2 = "0.3.2"
|
brotli = "3.3.3"
|
||||||
const-str = "0.3"
|
const-str = "0.3"
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
@ -157,6 +157,10 @@ awc = { path = "awc" }
|
|||||||
name = "test_server"
|
name = "test_server"
|
||||||
required-features = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
|
required-features = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "compression"
|
||||||
|
required-features = ["compress-brotli", "compress-gzip", "compress-zstd"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "basic"
|
name = "basic"
|
||||||
required-features = ["compress-gzip"]
|
required-features = ["compress-gzip"]
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! Provides a non-blocking service for serving static files from disk.
|
//! Provides a non-blocking service for serving static files from disk.
|
||||||
//!
|
//!
|
||||||
//! # Example
|
//! # Examples
|
||||||
//! ```
|
//! ```
|
||||||
//! use actix_web::App;
|
//! use actix_web::App;
|
||||||
//! use actix_files::Files;
|
//! use actix_files::Files;
|
||||||
|
@ -85,7 +85,7 @@ impl FromRequest for PathBufWrap {
|
|||||||
type Future = Ready<Result<Self, Self::Error>>;
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
ready(req.match_info().path().parse())
|
ready(req.match_info().unprocessed().parse())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,14 +120,16 @@ impl Service<ServiceRequest> for FilesService {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let real_path =
|
let path_on_disk = match PathBufWrap::parse_path(
|
||||||
match PathBufWrap::parse_path(req.match_info().path(), this.hidden_files) {
|
req.match_info().unprocessed(),
|
||||||
Ok(item) => item,
|
this.hidden_files,
|
||||||
Err(err) => return Ok(req.error_response(err)),
|
) {
|
||||||
};
|
Ok(item) => item,
|
||||||
|
Err(err) => return Ok(req.error_response(err)),
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(filter) = &this.path_filter {
|
if let Some(filter) = &this.path_filter {
|
||||||
if !filter(real_path.as_ref(), req.head()) {
|
if !filter(path_on_disk.as_ref(), req.head()) {
|
||||||
if let Some(ref default) = this.default {
|
if let Some(ref default) = this.default {
|
||||||
return default.call(req).await;
|
return default.call(req).await;
|
||||||
} else {
|
} else {
|
||||||
@ -137,7 +139,7 @@ impl Service<ServiceRequest> for FilesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// full file path
|
// full file path
|
||||||
let path = this.directory.join(&real_path);
|
let path = this.directory.join(&path_on_disk);
|
||||||
if let Err(err) = path.canonicalize() {
|
if let Err(err) = path.canonicalize() {
|
||||||
return this.handle_err(err, req).await;
|
return this.handle_err(err, req).await;
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ actix-codec = "0.4.1"
|
|||||||
actix-tls = "3.0.0"
|
actix-tls = "3.0.0"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-server = "2.0.0-rc.2"
|
actix-server = "2"
|
||||||
awc = { version = "3.0.0-beta.18", default-features = false }
|
awc = { version = "3.0.0-beta.18", default-features = false }
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
|
@ -1,6 +1,20 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
### Added
|
||||||
|
- Response headers can be sent as camel case using `res.head_mut().set_camel_case_headers(true)`. [#2587]
|
||||||
|
- `ResponseHead` now implements `Clone`. [#2585]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Brotli (de)compression support is now provided by the `brotli` crate. [#2538]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- `ResponseHead::extensions[_mut]()`. [#2585]
|
||||||
|
- `ResponseBuilder::extensions[_mut]()`. [#2585]
|
||||||
|
|
||||||
|
[#2538]: https://github.com/actix/actix-web/pull/2538
|
||||||
|
[#2585]: https://github.com/actix/actix-web/pull/2585
|
||||||
|
[#2587]: https://github.com/actix/actix-web/pull/2587
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.18 - 2022-01-04
|
## 3.0.0-beta.18 - 2022-01-04
|
||||||
@ -15,8 +29,8 @@
|
|||||||
- `Quality::MIN` is now the smallest non-zero value. [#2501]
|
- `Quality::MIN` is now the smallest non-zero value. [#2501]
|
||||||
- `QualityItem::min` semantics changed with `QualityItem::MIN`. [#2501]
|
- `QualityItem::min` semantics changed with `QualityItem::MIN`. [#2501]
|
||||||
- Rename `ContentEncoding::{Br => Brotli}`. [#2501]
|
- Rename `ContentEncoding::{Br => Brotli}`. [#2501]
|
||||||
- Minimum supported Rust version (MSRV) is now 1.54.
|
|
||||||
- Rename `header::EntityTag::{weak => new_weak, strong => new_strong}`. [#2565]
|
- Rename `header::EntityTag::{weak => new_weak, strong => new_strong}`. [#2565]
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.54.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- `ContentEncoding::Identity` can now be parsed from a string. [#2501]
|
- `ContentEncoding::Identity` can now be parsed from a string. [#2501]
|
||||||
|
@ -33,7 +33,7 @@ openssl = ["actix-tls/accept", "actix-tls/openssl"]
|
|||||||
rustls = ["actix-tls/accept", "actix-tls/rustls"]
|
rustls = ["actix-tls/accept", "actix-tls/rustls"]
|
||||||
|
|
||||||
# enable compression support
|
# enable compression support
|
||||||
compress-brotli = ["brotli2", "__compress"]
|
compress-brotli = ["brotli", "__compress"]
|
||||||
compress-gzip = ["flate2", "__compress"]
|
compress-gzip = ["flate2", "__compress"]
|
||||||
compress-zstd = ["zstd", "__compress"]
|
compress-zstd = ["zstd", "__compress"]
|
||||||
|
|
||||||
@ -74,13 +74,13 @@ smallvec = "1.6.1"
|
|||||||
actix-tls = { version = "3.0.0", default-features = false, optional = true }
|
actix-tls = { version = "3.0.0", default-features = false, optional = true }
|
||||||
|
|
||||||
# compression
|
# compression
|
||||||
brotli2 = { version="0.3.2", optional = true }
|
brotli = { version = "3.3.3", optional = true }
|
||||||
flate2 = { version = "1.0.13", optional = true }
|
flate2 = { version = "1.0.13", optional = true }
|
||||||
zstd = { version = "0.9", optional = true }
|
zstd = { version = "0.9", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] }
|
actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] }
|
||||||
actix-server = "2.0.0-rc.2"
|
actix-server = "2"
|
||||||
actix-tls = { version = "3.0.0", features = ["openssl"] }
|
actix-tls = { version = "3.0.0", features = ["openssl"] }
|
||||||
actix-web = "4.0.0-beta.20"
|
actix-web = "4.0.0-beta.20"
|
||||||
|
|
||||||
@ -88,6 +88,7 @@ async-stream = "0.3"
|
|||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
|
memchr = "2.4"
|
||||||
rcgen = "0.8"
|
rcgen = "0.8"
|
||||||
regex = "1.3"
|
regex = "1.3"
|
||||||
rustls-pemfile = "0.2"
|
rustls-pemfile = "0.2"
|
||||||
|
@ -15,6 +15,7 @@ async fn main() -> io::Result<()> {
|
|||||||
HttpService::build()
|
HttpService::build()
|
||||||
.client_timeout(1000)
|
.client_timeout(1000)
|
||||||
.client_disconnect(1000)
|
.client_disconnect(1000)
|
||||||
|
// handles HTTP/1.1 and HTTP/2
|
||||||
.finish(|mut req: Request| async move {
|
.finish(|mut req: Request| async move {
|
||||||
let mut body = BytesMut::new();
|
let mut body = BytesMut::new();
|
||||||
while let Some(item) = req.payload().next().await {
|
while let Some(item) = req.payload().next().await {
|
||||||
@ -23,12 +24,13 @@ async fn main() -> io::Result<()> {
|
|||||||
|
|
||||||
log::info!("request body: {:?}", body);
|
log::info!("request body: {:?}", body);
|
||||||
|
|
||||||
Ok::<_, Error>(
|
let res = Response::build(StatusCode::OK)
|
||||||
Response::build(StatusCode::OK)
|
.insert_header(("x-head", HeaderValue::from_static("dummy value!")))
|
||||||
.insert_header(("x-head", HeaderValue::from_static("dummy value!")))
|
.body(body);
|
||||||
.body(body),
|
|
||||||
)
|
Ok::<_, Error>(res)
|
||||||
})
|
})
|
||||||
|
// No TLS
|
||||||
.tcp()
|
.tcp()
|
||||||
})?
|
})?
|
||||||
.run()
|
.run()
|
||||||
|
@ -1,32 +1,34 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::MessageBody, header::HeaderValue, Error, HttpService, Request, Response, StatusCode,
|
body::{BodyStream, MessageBody},
|
||||||
|
header, Error, HttpMessage, HttpService, Request, Response, StatusCode,
|
||||||
};
|
};
|
||||||
use actix_server::Server;
|
|
||||||
use bytes::BytesMut;
|
|
||||||
use futures_util::StreamExt as _;
|
|
||||||
|
|
||||||
async fn handle_request(mut req: Request) -> Result<Response<impl MessageBody>, Error> {
|
async fn handle_request(mut req: Request) -> Result<Response<impl MessageBody>, Error> {
|
||||||
let mut body = BytesMut::new();
|
let mut res = Response::build(StatusCode::OK);
|
||||||
while let Some(item) = req.payload().next().await {
|
|
||||||
body.extend_from_slice(&item?)
|
if let Some(ct) = req.headers().get(header::CONTENT_TYPE) {
|
||||||
|
res.insert_header((header::CONTENT_TYPE, ct));
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!("request body: {:?}", body);
|
// echo request payload stream as (chunked) response body
|
||||||
|
let res = res.message_body(BodyStream::new(req.payload().take()))?;
|
||||||
|
|
||||||
Ok(Response::build(StatusCode::OK)
|
Ok(res)
|
||||||
.insert_header(("x-head", HeaderValue::from_static("dummy value!")))
|
|
||||||
.body(body))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
|
|
||||||
Server::build()
|
actix_server::Server::build()
|
||||||
.bind("echo", ("127.0.0.1", 8080), || {
|
.bind("echo", ("127.0.0.1", 8080), || {
|
||||||
HttpService::build().finish(handle_request).tcp()
|
HttpService::build()
|
||||||
|
// handles HTTP/1.1 only
|
||||||
|
.h1(handle_request)
|
||||||
|
// No TLS
|
||||||
|
.tcp()
|
||||||
})?
|
})?
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
|
@ -11,9 +11,6 @@ use actix_rt::task::{spawn_blocking, JoinHandle};
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::{ready, Stream};
|
use futures_core::{ready, Stream};
|
||||||
|
|
||||||
#[cfg(feature = "compress-brotli")]
|
|
||||||
use brotli2::write::BrotliDecoder;
|
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
use flate2::write::{GzDecoder, ZlibDecoder};
|
use flate2::write::{GzDecoder, ZlibDecoder};
|
||||||
|
|
||||||
@ -48,7 +45,7 @@ where
|
|||||||
let decoder = match encoding {
|
let decoder = match encoding {
|
||||||
#[cfg(feature = "compress-brotli")]
|
#[cfg(feature = "compress-brotli")]
|
||||||
ContentEncoding::Brotli => Some(ContentDecoder::Brotli(Box::new(
|
ContentEncoding::Brotli => Some(ContentDecoder::Brotli(Box::new(
|
||||||
BrotliDecoder::new(Writer::new()),
|
brotli::DecompressorWriter::new(Writer::new(), 8_096),
|
||||||
))),
|
))),
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
|
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
|
||||||
@ -165,7 +162,7 @@ enum ContentDecoder {
|
|||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
Gzip(Box<GzDecoder<Writer>>),
|
Gzip(Box<GzDecoder<Writer>>),
|
||||||
#[cfg(feature = "compress-brotli")]
|
#[cfg(feature = "compress-brotli")]
|
||||||
Brotli(Box<BrotliDecoder<Writer>>),
|
Brotli(Box<brotli::DecompressorWriter<Writer>>),
|
||||||
// We need explicit 'static lifetime here because ZstdDecoder need lifetime
|
// We need explicit 'static lifetime here because ZstdDecoder need lifetime
|
||||||
// argument, and we use `spawn_blocking` in `Decoder::poll_next` that require `FnOnce() -> R + Send + 'static`
|
// argument, and we use `spawn_blocking` in `Decoder::poll_next` that require `FnOnce() -> R + Send + 'static`
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
|
@ -14,9 +14,6 @@ use derive_more::Display;
|
|||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
#[cfg(feature = "compress-brotli")]
|
|
||||||
use brotli2::write::BrotliEncoder;
|
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||||
|
|
||||||
@ -268,7 +265,7 @@ enum ContentEncoder {
|
|||||||
Gzip(GzEncoder<Writer>),
|
Gzip(GzEncoder<Writer>),
|
||||||
|
|
||||||
#[cfg(feature = "compress-brotli")]
|
#[cfg(feature = "compress-brotli")]
|
||||||
Brotli(BrotliEncoder<Writer>),
|
Brotli(Box<brotli::CompressorWriter<Writer>>),
|
||||||
|
|
||||||
// Wwe need explicit 'static lifetime here because ZstdEncoder needs a lifetime argument and we
|
// Wwe need explicit 'static lifetime here because ZstdEncoder needs a lifetime argument and we
|
||||||
// use `spawn_blocking` in `Encoder::poll_next` that requires `FnOnce() -> R + Send + 'static`.
|
// use `spawn_blocking` in `Encoder::poll_next` that requires `FnOnce() -> R + Send + 'static`.
|
||||||
@ -292,9 +289,7 @@ impl ContentEncoder {
|
|||||||
))),
|
))),
|
||||||
|
|
||||||
#[cfg(feature = "compress-brotli")]
|
#[cfg(feature = "compress-brotli")]
|
||||||
ContentEncoding::Brotli => {
|
ContentEncoding::Brotli => Some(ContentEncoder::Brotli(new_brotli_compressor())),
|
||||||
Some(ContentEncoder::Brotli(BrotliEncoder::new(Writer::new(), 3)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
ContentEncoding::Zstd => {
|
ContentEncoding::Zstd => {
|
||||||
@ -326,8 +321,8 @@ impl ContentEncoder {
|
|||||||
fn finish(self) -> Result<Bytes, io::Error> {
|
fn finish(self) -> Result<Bytes, io::Error> {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "compress-brotli")]
|
#[cfg(feature = "compress-brotli")]
|
||||||
ContentEncoder::Brotli(encoder) => match encoder.finish() {
|
ContentEncoder::Brotli(mut encoder) => match encoder.flush() {
|
||||||
Ok(writer) => Ok(writer.buf.freeze()),
|
Ok(()) => Ok(encoder.into_inner().buf.freeze()),
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -392,6 +387,16 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "compress-brotli")]
|
||||||
|
fn new_brotli_compressor() -> Box<brotli::CompressorWriter<Writer>> {
|
||||||
|
Box::new(brotli::CompressorWriter::new(
|
||||||
|
Writer::new(),
|
||||||
|
8 * 1024, // 32 KiB buffer
|
||||||
|
3, // BROTLI_PARAM_QUALITY
|
||||||
|
22, // BROTLI_PARAM_LGWIN
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Display)]
|
#[derive(Debug, Display)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum EncoderError {
|
pub enum EncoderError {
|
||||||
|
@ -387,6 +387,7 @@ impl StdError for DispatchError {
|
|||||||
|
|
||||||
/// A set of error that can occur during parsing content type.
|
/// A set of error that can occur during parsing content type.
|
||||||
#[derive(Debug, Display, Error)]
|
#[derive(Debug, Display, Error)]
|
||||||
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum ContentTypeError {
|
pub enum ContentTypeError {
|
||||||
/// Can not parse content type
|
/// Can not parse content type
|
||||||
@ -398,28 +399,14 @@ pub enum ContentTypeError {
|
|||||||
UnknownEncoding,
|
UnknownEncoding,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod content_type_test_impls {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
impl std::cmp::PartialEq for ContentTypeError {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::ParseError => matches!(other, ContentTypeError::ParseError),
|
|
||||||
Self::UnknownEncoding => {
|
|
||||||
matches!(other, ContentTypeError::UnknownEncoding)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use http::{Error as HttpError, StatusCode};
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
|
use http::{Error as HttpError, StatusCode};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_into_response() {
|
fn test_into_response() {
|
||||||
let resp: Response<BoxBody> = ParseError::Incomplete.into();
|
let resp: Response<BoxBody> = ParseError::Incomplete.into();
|
||||||
|
@ -380,34 +380,36 @@ impl HeaderIndex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
/// Http payload item
|
/// Chunk type yielded while decoding a payload.
|
||||||
pub enum PayloadItem {
|
pub enum PayloadItem {
|
||||||
Chunk(Bytes),
|
Chunk(Bytes),
|
||||||
Eof,
|
Eof,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decoders to handle different Transfer-Encodings.
|
/// Decoder that can handle different payload types.
|
||||||
///
|
///
|
||||||
/// If a message body does not include a Transfer-Encoding, it *should*
|
/// If a message body does not use `Transfer-Encoding`, it should include a `Content-Length`.
|
||||||
/// include a Content-Length header.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct PayloadDecoder {
|
pub struct PayloadDecoder {
|
||||||
kind: Kind,
|
kind: Kind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PayloadDecoder {
|
impl PayloadDecoder {
|
||||||
|
/// Constructs a fixed-length payload decoder.
|
||||||
pub fn length(x: u64) -> PayloadDecoder {
|
pub fn length(x: u64) -> PayloadDecoder {
|
||||||
PayloadDecoder {
|
PayloadDecoder {
|
||||||
kind: Kind::Length(x),
|
kind: Kind::Length(x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs a chunked encoding decoder.
|
||||||
pub fn chunked() -> PayloadDecoder {
|
pub fn chunked() -> PayloadDecoder {
|
||||||
PayloadDecoder {
|
PayloadDecoder {
|
||||||
kind: Kind::Chunked(ChunkedState::Size, 0),
|
kind: Kind::Chunked(ChunkedState::Size, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an decoder that yields chunks until the stream returns EOF.
|
||||||
pub fn eof() -> PayloadDecoder {
|
pub fn eof() -> PayloadDecoder {
|
||||||
PayloadDecoder { kind: Kind::Eof }
|
PayloadDecoder { kind: Kind::Eof }
|
||||||
}
|
}
|
||||||
@ -415,25 +417,26 @@ impl PayloadDecoder {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
enum Kind {
|
enum Kind {
|
||||||
/// A Reader used when a Content-Length header is passed with a positive
|
/// A reader used when a `Content-Length` header is passed with a positive integer.
|
||||||
/// integer.
|
|
||||||
Length(u64),
|
Length(u64),
|
||||||
/// A Reader used when Transfer-Encoding is `chunked`.
|
|
||||||
|
/// A reader used when `Transfer-Encoding` is `chunked`.
|
||||||
Chunked(ChunkedState, u64),
|
Chunked(ChunkedState, u64),
|
||||||
/// A Reader used for responses that don't indicate a length or chunked.
|
|
||||||
|
/// A reader used for responses that don't indicate a length or chunked.
|
||||||
///
|
///
|
||||||
/// Note: This should only used for `Response`s. It is illegal for a
|
/// Note: This should only used for `Response`s. It is illegal for a `Request` to be made
|
||||||
/// `Request` to be made with both `Content-Length` and
|
/// without either of `Content-Length` and `Transfer-Encoding: chunked` missing, as explained
|
||||||
/// `Transfer-Encoding: chunked` missing, as explained from the spec:
|
/// in [RFC 7230 §3.3.3]:
|
||||||
///
|
///
|
||||||
/// > If a Transfer-Encoding header field is present in a response and
|
/// > If a Transfer-Encoding header field is present in a response and the chunked transfer
|
||||||
/// > the chunked transfer coding is not the final encoding, the
|
/// > coding is not the final encoding, the message body length is determined by reading the
|
||||||
/// > message body length is determined by reading the connection until
|
/// > connection until it is closed by the server. If a Transfer-Encoding header field is
|
||||||
/// > it is closed by the server. If a Transfer-Encoding header field
|
/// > present in a request and the chunked transfer coding is not the final encoding, the
|
||||||
/// > is present in a request and the chunked transfer coding is not
|
/// > message body length cannot be determined reliably; the server MUST respond with the 400
|
||||||
/// > the final encoding, the message body length cannot be determined
|
/// > (Bad Request) status code and then close the connection.
|
||||||
/// > reliably; the server MUST respond with the 400 (Bad Request)
|
///
|
||||||
/// > status code and then close the connection.
|
/// [RFC 7230 §3.3.3]: https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.3
|
||||||
Eof,
|
Eof,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -463,6 +466,7 @@ impl Decoder for PayloadDecoder {
|
|||||||
Ok(Some(PayloadItem::Chunk(buf)))
|
Ok(Some(PayloadItem::Chunk(buf)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Kind::Chunked(ref mut state, ref mut size) => {
|
Kind::Chunked(ref mut state, ref mut size) => {
|
||||||
loop {
|
loop {
|
||||||
let mut buf = None;
|
let mut buf = None;
|
||||||
@ -488,6 +492,7 @@ impl Decoder for PayloadDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Kind::Eof => {
|
Kind::Eof => {
|
||||||
if src.is_empty() {
|
if src.is_empty() {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
@ -258,6 +258,12 @@ impl MessageType for Response<()> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn camel_case(&self) -> bool {
|
||||||
|
self.head()
|
||||||
|
.flags
|
||||||
|
.contains(crate::message::Flags::CAMEL_CASE)
|
||||||
|
}
|
||||||
|
|
||||||
fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> {
|
fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> {
|
||||||
let head = self.head();
|
let head = self.head();
|
||||||
let reason = head.reason().as_bytes();
|
let reason = head.reason().as_bytes();
|
||||||
|
@ -25,10 +25,10 @@ pub trait HttpMessage: Sized {
|
|||||||
/// Message payload stream
|
/// Message payload stream
|
||||||
fn take_payload(&mut self) -> Payload<Self::Stream>;
|
fn take_payload(&mut self) -> Payload<Self::Stream>;
|
||||||
|
|
||||||
/// Request's extensions container
|
/// Returns a reference to the request-local data/extensions container.
|
||||||
fn extensions(&self) -> Ref<'_, Extensions>;
|
fn extensions(&self) -> Ref<'_, Extensions>;
|
||||||
|
|
||||||
/// Mutable reference to a the request's extensions container
|
/// Returns a mutable reference to the request-local data/extensions container.
|
||||||
fn extensions_mut(&self) -> RefMut<'_, Extensions>;
|
fn extensions_mut(&self) -> RefMut<'_, Extensions>;
|
||||||
|
|
||||||
/// Get a header.
|
/// Get a header.
|
||||||
|
@ -5,13 +5,13 @@ use bitflags::bitflags;
|
|||||||
/// Represents various types of connection
|
/// Represents various types of connection
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
pub enum ConnectionType {
|
pub enum ConnectionType {
|
||||||
/// Close connection after response
|
/// Close connection after response.
|
||||||
Close,
|
Close,
|
||||||
|
|
||||||
/// Keep connection alive after response
|
/// Keep connection alive after response.
|
||||||
KeepAlive,
|
KeepAlive,
|
||||||
|
|
||||||
/// Connection is upgraded to different type
|
/// Connection is upgraded to different type.
|
||||||
Upgrade,
|
Upgrade,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,8 +69,8 @@ impl<T: Head> Drop for Message<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generic `Head` object pool.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// Request's objects pool
|
|
||||||
pub struct MessagePool<T: Head>(RefCell<Vec<Rc<T>>>);
|
pub struct MessagePool<T: Head>(RefCell<Vec<Rc<T>>>);
|
||||||
|
|
||||||
impl<T: Head> MessagePool<T> {
|
impl<T: Head> MessagePool<T> {
|
||||||
|
@ -142,8 +142,8 @@ impl RequestHead {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum RequestHeadType {
|
pub enum RequestHeadType {
|
||||||
Owned(RequestHead),
|
Owned(RequestHead),
|
||||||
Rc(Rc<RequestHead>, Option<HeaderMap>),
|
Rc(Rc<RequestHead>, Option<HeaderMap>),
|
||||||
|
@ -19,7 +19,7 @@ pub struct Request<P = BoxedPayloadStream> {
|
|||||||
pub(crate) payload: Payload<P>,
|
pub(crate) payload: Payload<P>,
|
||||||
pub(crate) head: Message<RequestHead>,
|
pub(crate) head: Message<RequestHead>,
|
||||||
pub(crate) conn_data: Option<Rc<Extensions>>,
|
pub(crate) conn_data: Option<Rc<Extensions>>,
|
||||||
pub(crate) req_data: RefCell<Extensions>,
|
pub(crate) extensions: RefCell<Extensions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> HttpMessage for Request<P> {
|
impl<P> HttpMessage for Request<P> {
|
||||||
@ -34,16 +34,14 @@ impl<P> HttpMessage for Request<P> {
|
|||||||
mem::replace(&mut self.payload, Payload::None)
|
mem::replace(&mut self.payload, Payload::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request extensions
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn extensions(&self) -> Ref<'_, Extensions> {
|
fn extensions(&self) -> Ref<'_, Extensions> {
|
||||||
self.req_data.borrow()
|
self.extensions.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutable reference to a the request's extensions
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
||||||
self.req_data.borrow_mut()
|
self.extensions.borrow_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +50,7 @@ impl From<Message<RequestHead>> for Request<BoxedPayloadStream> {
|
|||||||
Request {
|
Request {
|
||||||
head,
|
head,
|
||||||
payload: Payload::None,
|
payload: Payload::None,
|
||||||
req_data: RefCell::new(Extensions::default()),
|
extensions: RefCell::new(Extensions::default()),
|
||||||
conn_data: None,
|
conn_data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,7 +63,7 @@ impl Request<BoxedPayloadStream> {
|
|||||||
Request {
|
Request {
|
||||||
head: Message::new(),
|
head: Message::new(),
|
||||||
payload: Payload::None,
|
payload: Payload::None,
|
||||||
req_data: RefCell::new(Extensions::default()),
|
extensions: RefCell::new(Extensions::default()),
|
||||||
conn_data: None,
|
conn_data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,7 +75,7 @@ impl<P> Request<P> {
|
|||||||
Request {
|
Request {
|
||||||
payload,
|
payload,
|
||||||
head: Message::new(),
|
head: Message::new(),
|
||||||
req_data: RefCell::new(Extensions::default()),
|
extensions: RefCell::new(Extensions::default()),
|
||||||
conn_data: None,
|
conn_data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,7 +88,7 @@ impl<P> Request<P> {
|
|||||||
Request {
|
Request {
|
||||||
payload,
|
payload,
|
||||||
head: self.head,
|
head: self.head,
|
||||||
req_data: self.req_data,
|
extensions: self.extensions,
|
||||||
conn_data: self.conn_data,
|
conn_data: self.conn_data,
|
||||||
},
|
},
|
||||||
pl,
|
pl,
|
||||||
@ -195,16 +193,17 @@ impl<P> Request<P> {
|
|||||||
.and_then(|container| container.get::<T>())
|
.and_then(|container| container.get::<T>())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the connection data container if an [on-connect] callback was registered.
|
/// Returns the connection-level data/extensions container if an [on-connect] callback was
|
||||||
|
/// registered, leaving an empty one in its place.
|
||||||
///
|
///
|
||||||
/// [on-connect]: crate::HttpServiceBuilder::on_connect_ext
|
/// [on-connect]: crate::HttpServiceBuilder::on_connect_ext
|
||||||
pub fn take_conn_data(&mut self) -> Option<Rc<Extensions>> {
|
pub fn take_conn_data(&mut self) -> Option<Rc<Extensions>> {
|
||||||
self.conn_data.take()
|
self.conn_data.take()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the request data container, leaving an empty one in it's place.
|
/// Returns the request-local data/extensions container, leaving an empty one in its place.
|
||||||
pub fn take_req_data(&mut self) -> Extensions {
|
pub fn take_req_data(&mut self) -> Extensions {
|
||||||
mem::take(self.req_data.get_mut())
|
mem::take(self.extensions.get_mut())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
//! HTTP response builder.
|
//! HTTP response builder.
|
||||||
|
|
||||||
use std::{
|
use std::{cell::RefCell, fmt, str};
|
||||||
cell::{Ref, RefMut},
|
|
||||||
fmt, str,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{EitherBody, MessageBody},
|
body::{EitherBody, MessageBody},
|
||||||
@ -202,20 +199,6 @@ impl ResponseBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Responses extensions
|
|
||||||
#[inline]
|
|
||||||
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
|
||||||
let head = self.head.as_ref().expect("cannot reuse response builder");
|
|
||||||
head.extensions.borrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mutable reference to a the response's extensions
|
|
||||||
#[inline]
|
|
||||||
pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
|
|
||||||
let head = self.head.as_ref().expect("cannot reuse response builder");
|
|
||||||
head.extensions.borrow_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate response with a wrapped body.
|
/// Generate response with a wrapped body.
|
||||||
///
|
///
|
||||||
/// This `ResponseBuilder` will be left in a useless state.
|
/// This `ResponseBuilder` will be left in a useless state.
|
||||||
@ -238,7 +221,12 @@ impl ResponseBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let head = self.head.take().expect("cannot reuse response builder");
|
let head = self.head.take().expect("cannot reuse response builder");
|
||||||
Ok(Response { head, body })
|
|
||||||
|
Ok(Response {
|
||||||
|
head,
|
||||||
|
body,
|
||||||
|
extensions: RefCell::new(Extensions::new()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate response with an empty body.
|
/// Generate response with an empty body.
|
||||||
|
@ -1,26 +1,20 @@
|
|||||||
//! Response head type and caching pool.
|
//! Response head type and caching pool.
|
||||||
|
|
||||||
use std::{
|
use std::{cell::RefCell, ops};
|
||||||
cell::{Ref, RefCell, RefMut},
|
|
||||||
ops,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{header::HeaderMap, message::Flags, ConnectionType, StatusCode, Version};
|
||||||
header::HeaderMap, message::Flags, ConnectionType, Extensions, StatusCode, Version,
|
|
||||||
};
|
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create();
|
static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ResponseHead {
|
pub struct ResponseHead {
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
pub status: StatusCode,
|
pub status: StatusCode,
|
||||||
pub headers: HeaderMap,
|
pub headers: HeaderMap,
|
||||||
pub reason: Option<&'static str>,
|
pub reason: Option<&'static str>,
|
||||||
pub(crate) extensions: RefCell<Extensions>,
|
pub(crate) flags: Flags,
|
||||||
flags: Flags,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseHead {
|
impl ResponseHead {
|
||||||
@ -33,36 +27,35 @@ impl ResponseHead {
|
|||||||
headers: HeaderMap::with_capacity(12),
|
headers: HeaderMap::with_capacity(12),
|
||||||
reason: None,
|
reason: None,
|
||||||
flags: Flags::empty(),
|
flags: Flags::empty(),
|
||||||
extensions: RefCell::new(Extensions::new()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Read the message headers.
|
/// Read the message headers.
|
||||||
|
#[inline]
|
||||||
pub fn headers(&self) -> &HeaderMap {
|
pub fn headers(&self) -> &HeaderMap {
|
||||||
&self.headers
|
&self.headers
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Mutable reference to the message headers.
|
/// Mutable reference to the message headers.
|
||||||
|
#[inline]
|
||||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||||
&mut self.headers
|
&mut self.headers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Message extensions
|
/// Sets the flag that controls wether to send headers formatted as Camel-Case.
|
||||||
|
///
|
||||||
|
/// Only applicable to HTTP/1.x responses; HTTP/2 header names are always lowercase.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
pub fn set_camel_case_headers(&mut self, camel_case: bool) {
|
||||||
self.extensions.borrow()
|
if camel_case {
|
||||||
|
self.flags.insert(Flags::CAMEL_CASE);
|
||||||
|
} else {
|
||||||
|
self.flags.remove(Flags::CAMEL_CASE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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
|
/// Set connection type of the message
|
||||||
|
#[inline]
|
||||||
pub fn set_connection_type(&mut self, ctype: ConnectionType) {
|
pub fn set_connection_type(&mut self, ctype: ConnectionType) {
|
||||||
match ctype {
|
match ctype {
|
||||||
ConnectionType::Close => self.flags.insert(Flags::CLOSE),
|
ConnectionType::Close => self.flags.insert(Flags::CLOSE),
|
||||||
@ -121,14 +114,14 @@ impl ResponseHead {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Get response body chunking state
|
/// Get response body chunking state
|
||||||
|
#[inline]
|
||||||
pub fn chunked(&self) -> bool {
|
pub fn chunked(&self) -> bool {
|
||||||
!self.flags.contains(Flags::NO_CHUNKING)
|
!self.flags.contains(Flags::NO_CHUNKING)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Set no chunking for payload
|
/// Set no chunking for payload
|
||||||
|
#[inline]
|
||||||
pub fn no_chunking(&mut self, val: bool) {
|
pub fn no_chunking(&mut self, val: bool) {
|
||||||
if val {
|
if val {
|
||||||
self.flags.insert(Flags::NO_CHUNKING);
|
self.flags.insert(Flags::NO_CHUNKING);
|
||||||
@ -171,7 +164,7 @@ impl Drop for BoxedResponseHead {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request's objects pool
|
/// Response head object pool.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub struct BoxedResponsePool(#[allow(clippy::vec_box)] RefCell<Vec<Box<ResponseHead>>>);
|
pub struct BoxedResponsePool(#[allow(clippy::vec_box)] RefCell<Vec<Box<ResponseHead>>>);
|
||||||
|
|
||||||
@ -180,7 +173,7 @@ impl BoxedResponsePool {
|
|||||||
BoxedResponsePool(RefCell::new(Vec::with_capacity(128)))
|
BoxedResponsePool(RefCell::new(Vec::with_capacity(128)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get message from the pool
|
/// Get message from the pool.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_message(&self, status: StatusCode) -> BoxedResponseHead {
|
fn get_message(&self, status: StatusCode) -> BoxedResponseHead {
|
||||||
if let Some(mut head) = self.0.borrow_mut().pop() {
|
if let Some(mut head) = self.0.borrow_mut().pop() {
|
||||||
@ -196,13 +189,67 @@ impl BoxedResponsePool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Release request instance
|
/// Release request instance.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn release(&self, mut msg: Box<ResponseHead>) {
|
fn release(&self, msg: Box<ResponseHead>) {
|
||||||
let pool = &mut self.0.borrow_mut();
|
let pool = &mut self.0.borrow_mut();
|
||||||
|
|
||||||
if pool.len() < 128 {
|
if pool.len() < 128 {
|
||||||
msg.extensions.get_mut().clear();
|
|
||||||
pool.push(msg);
|
pool.push(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{
|
||||||
|
io::{Read as _, Write as _},
|
||||||
|
net,
|
||||||
|
};
|
||||||
|
|
||||||
|
use memchr::memmem;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
header::{HeaderName, HeaderValue},
|
||||||
|
Error, HttpService, Request, Response,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn camel_case_headers() {
|
||||||
|
let mut srv = actix_http_test::test_server(|| {
|
||||||
|
HttpService::new(|req: Request| async move {
|
||||||
|
let mut res = Response::ok();
|
||||||
|
|
||||||
|
if req.path().contains("camel") {
|
||||||
|
res.head_mut().set_camel_case_headers(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.headers_mut().insert(
|
||||||
|
HeaderName::from_static("foo-bar"),
|
||||||
|
HeaderValue::from_static("baz"),
|
||||||
|
);
|
||||||
|
Ok::<_, Error>(res)
|
||||||
|
})
|
||||||
|
.tcp()
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||||
|
let _ = stream.write_all(b"GET /camel HTTP/1.1\r\nConnection: Close\r\n\r\n");
|
||||||
|
let mut data = vec![0; 1024];
|
||||||
|
let _ = stream.read(&mut data);
|
||||||
|
assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n");
|
||||||
|
assert!(memmem::find(&data, b"Foo-Bar").is_some());
|
||||||
|
assert!(!memmem::find(&data, b"foo-bar").is_some());
|
||||||
|
|
||||||
|
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||||
|
let _ = stream.write_all(b"GET /lower HTTP/1.1\r\nConnection: Close\r\n\r\n");
|
||||||
|
let mut data = vec![0; 1024];
|
||||||
|
let _ = stream.read(&mut data);
|
||||||
|
assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n");
|
||||||
|
assert!(!memmem::find(&data, b"Foo-Bar").is_some());
|
||||||
|
assert!(memmem::find(&data, b"foo-bar").is_some());
|
||||||
|
|
||||||
|
srv.stop().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! HTTP response.
|
//! HTTP response.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Ref, RefMut},
|
cell::{Ref, RefCell, RefMut},
|
||||||
fmt, str,
|
fmt, str,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ use bytes::{Bytes, BytesMut};
|
|||||||
use bytestring::ByteString;
|
use bytestring::ByteString;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BoxBody, MessageBody},
|
body::{BoxBody, EitherBody, MessageBody},
|
||||||
header::{self, HeaderMap, TryIntoHeaderValue},
|
header::{self, HeaderMap, TryIntoHeaderValue},
|
||||||
responses::BoxedResponseHead,
|
responses::BoxedResponseHead,
|
||||||
Error, Extensions, ResponseBuilder, ResponseHead, StatusCode,
|
Error, Extensions, ResponseBuilder, ResponseHead, StatusCode,
|
||||||
@ -19,6 +19,7 @@ use crate::{
|
|||||||
pub struct Response<B> {
|
pub struct Response<B> {
|
||||||
pub(crate) head: BoxedResponseHead,
|
pub(crate) head: BoxedResponseHead,
|
||||||
pub(crate) body: B,
|
pub(crate) body: B,
|
||||||
|
pub(crate) extensions: RefCell<Extensions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response<BoxBody> {
|
impl Response<BoxBody> {
|
||||||
@ -28,6 +29,7 @@ impl Response<BoxBody> {
|
|||||||
Response {
|
Response {
|
||||||
head: BoxedResponseHead::new(status),
|
head: BoxedResponseHead::new(status),
|
||||||
body: BoxBody::new(()),
|
body: BoxBody::new(()),
|
||||||
|
extensions: RefCell::new(Extensions::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +76,7 @@ impl<B> Response<B> {
|
|||||||
Response {
|
Response {
|
||||||
head: BoxedResponseHead::new(status),
|
head: BoxedResponseHead::new(status),
|
||||||
body,
|
body,
|
||||||
|
extensions: RefCell::new(Extensions::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,20 +123,21 @@ impl<B> Response<B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if keep-alive is enabled.
|
/// Returns true if keep-alive is enabled.
|
||||||
|
#[inline]
|
||||||
pub fn keep_alive(&self) -> bool {
|
pub fn keep_alive(&self) -> bool {
|
||||||
self.head.keep_alive()
|
self.head.keep_alive()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the extensions of this response.
|
/// Returns a reference to the request-local data/extensions container.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
||||||
self.head.extensions.borrow()
|
self.extensions.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable reference to the extensions of this response.
|
/// Returns a mutable reference to the request-local data/extensions container.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
|
pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
|
||||||
self.head.extensions.borrow_mut()
|
self.extensions.borrow_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the body of this response.
|
/// Returns a reference to the body of this response.
|
||||||
@ -143,24 +147,29 @@ impl<B> Response<B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sets new body.
|
/// Sets new body.
|
||||||
|
#[inline]
|
||||||
pub fn set_body<B2>(self, body: B2) -> Response<B2> {
|
pub fn set_body<B2>(self, body: B2) -> Response<B2> {
|
||||||
Response {
|
Response {
|
||||||
head: self.head,
|
head: self.head,
|
||||||
body,
|
body,
|
||||||
|
extensions: self.extensions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drops body and returns new response.
|
/// Drops body and returns new response.
|
||||||
|
#[inline]
|
||||||
pub fn drop_body(self) -> Response<()> {
|
pub fn drop_body(self) -> Response<()> {
|
||||||
self.set_body(())
|
self.set_body(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets new body, returning new response and previous body value.
|
/// Sets new body, returning new response and previous body value.
|
||||||
|
#[inline]
|
||||||
pub(crate) fn replace_body<B2>(self, body: B2) -> (Response<B2>, B) {
|
pub(crate) fn replace_body<B2>(self, body: B2) -> (Response<B2>, B) {
|
||||||
(
|
(
|
||||||
Response {
|
Response {
|
||||||
head: self.head,
|
head: self.head,
|
||||||
body,
|
body,
|
||||||
|
extensions: self.extensions,
|
||||||
},
|
},
|
||||||
self.body,
|
self.body,
|
||||||
)
|
)
|
||||||
@ -171,11 +180,15 @@ impl<B> Response<B> {
|
|||||||
/// # Implementation Notes
|
/// # Implementation Notes
|
||||||
/// Due to internal performance optimizations, the first element of the returned tuple is a
|
/// Due to internal performance optimizations, the first element of the returned tuple is a
|
||||||
/// `Response` as well but only contains the head of the response this was called on.
|
/// `Response` as well but only contains the head of the response this was called on.
|
||||||
|
#[inline]
|
||||||
pub fn into_parts(self) -> (Response<()>, B) {
|
pub fn into_parts(self) -> (Response<()>, B) {
|
||||||
self.replace_body(())
|
self.replace_body(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns new response with mapped body.
|
/// Map the current body type to another using a closure. Returns a new response.
|
||||||
|
///
|
||||||
|
/// Closure receives the response head and the current body type.
|
||||||
|
#[inline]
|
||||||
pub fn map_body<F, B2>(mut self, f: F) -> Response<B2>
|
pub fn map_body<F, B2>(mut self, f: F) -> Response<B2>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut ResponseHead, B) -> B2,
|
F: FnOnce(&mut ResponseHead, B) -> B2,
|
||||||
@ -185,6 +198,7 @@ impl<B> Response<B> {
|
|||||||
Response {
|
Response {
|
||||||
head: self.head,
|
head: self.head,
|
||||||
body,
|
body,
|
||||||
|
extensions: self.extensions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,6 +211,7 @@ impl<B> Response<B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns body, consuming this response.
|
/// Returns body, consuming this response.
|
||||||
|
#[inline]
|
||||||
pub fn into_body(self) -> B {
|
pub fn into_body(self) -> B {
|
||||||
self.body
|
self.body
|
||||||
}
|
}
|
||||||
@ -239,9 +254,9 @@ impl<I: Into<Response<BoxBody>>, E: Into<Error>> From<Result<I, E>> for Response
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ResponseBuilder> for Response<BoxBody> {
|
impl From<ResponseBuilder> for Response<EitherBody<()>> {
|
||||||
fn from(mut builder: ResponseBuilder) -> Self {
|
fn from(mut builder: ResponseBuilder) -> Self {
|
||||||
builder.finish().map_into_boxed_body()
|
builder.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,13 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.0-rc.2 - 2022-01-21
|
||||||
|
- Add `Path::as_str`. [#2590]
|
||||||
|
- Deprecate `Path::path`. [#2590]
|
||||||
|
|
||||||
|
[#2590]: https://github.com/actix/actix-web/pull/2590
|
||||||
|
|
||||||
|
|
||||||
## 0.5.0-rc.1 - 2022-01-14
|
## 0.5.0-rc.1 - 2022-01-14
|
||||||
- `Resource` trait now have an associated type, `Path`, instead of the generic parameter. [#2568]
|
- `Resource` trait now have an associated type, `Path`, instead of the generic parameter. [#2568]
|
||||||
- `Resource` is now implemented for `&mut Path<_>` and `RefMut<Path<_>>`. [#2568]
|
- `Resource` is now implemented for `&mut Path<_>` and `RefMut<Path<_>>`. [#2568]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-router"
|
name = "actix-router"
|
||||||
version = "0.5.0-rc.1"
|
version = "0.5.0-rc.2"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
||||||
|
@ -37,19 +37,39 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get reference to inner path instance.
|
/// Returns reference to inner path instance.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_ref(&self) -> &T {
|
pub fn get_ref(&self) -> &T {
|
||||||
&self.path
|
&self.path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get mutable reference to inner path instance.
|
/// Returns mutable reference to inner path instance.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_mut(&mut self) -> &mut T {
|
pub fn get_mut(&mut self) -> &mut T {
|
||||||
&mut self.path
|
&mut self.path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Path.
|
/// Returns full path as a string.
|
||||||
|
#[inline]
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
profile_method!(as_str);
|
||||||
|
self.path.path()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns unprocessed part of the path.
|
||||||
|
///
|
||||||
|
/// Returns empty string if no more is to be processed.
|
||||||
|
#[inline]
|
||||||
|
pub fn unprocessed(&self) -> &str {
|
||||||
|
profile_method!(unprocessed);
|
||||||
|
// clamp skip to path length
|
||||||
|
let skip = (self.skip as usize).min(self.as_str().len());
|
||||||
|
&self.path.path()[skip..]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns unprocessed part of the path.
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[deprecated(since = "0.6.0", note = "Use `.as_str()` or `.unprocessed()`.")]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn path(&self) -> &str {
|
pub fn path(&self) -> &str {
|
||||||
profile_method!(path);
|
profile_method!(path);
|
||||||
@ -66,6 +86,8 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
/// Set new path.
|
/// Set new path.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set(&mut self, path: T) {
|
pub fn set(&mut self, path: T) {
|
||||||
|
profile_method!(set);
|
||||||
|
|
||||||
self.skip = 0;
|
self.skip = 0;
|
||||||
self.path = path;
|
self.path = path;
|
||||||
self.segments.clear();
|
self.segments.clear();
|
||||||
@ -74,6 +96,8 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
/// Reset state.
|
/// Reset state.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
|
profile_method!(reset);
|
||||||
|
|
||||||
self.skip = 0;
|
self.skip = 0;
|
||||||
self.segments.clear();
|
self.segments.clear();
|
||||||
}
|
}
|
||||||
@ -81,6 +105,7 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
/// Skip first `n` chars in path.
|
/// Skip first `n` chars in path.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn skip(&mut self, n: u16) {
|
pub fn skip(&mut self, n: u16) {
|
||||||
|
profile_method!(skip);
|
||||||
self.skip += n;
|
self.skip += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,6 +127,8 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
name: impl Into<Cow<'static, str>>,
|
name: impl Into<Cow<'static, str>>,
|
||||||
value: impl Into<Cow<'static, str>>,
|
value: impl Into<Cow<'static, str>>,
|
||||||
) {
|
) {
|
||||||
|
profile_method!(add_static);
|
||||||
|
|
||||||
self.segments
|
self.segments
|
||||||
.push((name.into(), PathItem::Static(value.into())));
|
.push((name.into(), PathItem::Static(value.into())));
|
||||||
}
|
}
|
||||||
@ -136,11 +163,6 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get unprocessed part of the path
|
|
||||||
pub fn unprocessed(&self) -> &str {
|
|
||||||
&self.path.path()[(self.skip as usize)..]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get matched parameter by name.
|
/// Get matched parameter by name.
|
||||||
///
|
///
|
||||||
/// If keyed parameter is not available empty string is used as default value.
|
/// If keyed parameter is not available empty string is used as default value.
|
||||||
|
@ -692,7 +692,7 @@ impl ResourceDef {
|
|||||||
|
|
||||||
let mut segments = <[PathItem; MAX_DYNAMIC_SEGMENTS]>::default();
|
let mut segments = <[PathItem; MAX_DYNAMIC_SEGMENTS]>::default();
|
||||||
let path = resource.resource_path();
|
let path = resource.resource_path();
|
||||||
let path_str = path.path();
|
let path_str = path.unprocessed();
|
||||||
|
|
||||||
let (matched_len, matched_vars) = match &self.pat_type {
|
let (matched_len, matched_vars) = match &self.pat_type {
|
||||||
PatternType::Static(pattern) => {
|
PatternType::Static(pattern) => {
|
||||||
@ -710,7 +710,7 @@ impl ResourceDef {
|
|||||||
let captures = {
|
let captures = {
|
||||||
profile_section!(pattern_dynamic_regex_exec);
|
profile_section!(pattern_dynamic_regex_exec);
|
||||||
|
|
||||||
match re.captures(path.path()) {
|
match re.captures(path.unprocessed()) {
|
||||||
Some(captures) => captures,
|
Some(captures) => captures,
|
||||||
_ => return false,
|
_ => return false,
|
||||||
}
|
}
|
||||||
@ -738,7 +738,7 @@ impl ResourceDef {
|
|||||||
PatternType::DynamicSet(re, params) => {
|
PatternType::DynamicSet(re, params) => {
|
||||||
profile_section!(pattern_dynamic_set);
|
profile_section!(pattern_dynamic_set);
|
||||||
|
|
||||||
let path = path.path();
|
let path = path.unprocessed();
|
||||||
let (pattern, names) = match re.matches(path).into_iter().next() {
|
let (pattern, names) = match re.matches(path).into_iter().next() {
|
||||||
Some(idx) => ¶ms[idx],
|
Some(idx) => ¶ms[idx],
|
||||||
_ => return false,
|
_ => return false,
|
||||||
|
@ -256,6 +256,7 @@ mod tests {
|
|||||||
router.path("/name/{val}", 11);
|
router.path("/name/{val}", 11);
|
||||||
let mut router = router.finish();
|
let mut router = router.finish();
|
||||||
|
|
||||||
|
// test skip beyond path length
|
||||||
let mut path = Path::new("/name");
|
let mut path = Path::new("/name");
|
||||||
path.skip(6);
|
path.skip(6);
|
||||||
assert!(router.recognize_mut(&mut path).is_none());
|
assert!(router.recognize_mut(&mut path).is_none());
|
||||||
|
@ -121,7 +121,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn valid_utf8_multibyte() {
|
fn valid_utf8_multi_byte() {
|
||||||
let test = ('\u{FF00}'..='\u{FFFF}').collect::<String>();
|
let test = ('\u{FF00}'..='\u{FFFF}').collect::<String>();
|
||||||
let encoded = percent_encode(test.as_bytes());
|
let encoded = percent_encode(test.as_bytes());
|
||||||
let path = match_url("/a/{id}/b", format!("/a/{}/b", &encoded));
|
let path = match_url("/a/{id}/b", format!("/a/{}/b", &encoded));
|
||||||
@ -135,6 +135,6 @@ mod tests {
|
|||||||
let path = Path::new(Url::new(uri));
|
let path = Path::new(Url::new(uri));
|
||||||
|
|
||||||
// We should always get a valid utf8 string
|
// We should always get a valid utf8 string
|
||||||
assert!(String::from_utf8(path.path().as_bytes().to_owned()).is_ok());
|
assert!(String::from_utf8(path.as_str().as_bytes().to_owned()).is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,13 +39,18 @@
|
|||||||
//! ```
|
//! ```
|
||||||
//! # use actix_web::HttpResponse;
|
//! # use actix_web::HttpResponse;
|
||||||
//! # use actix_web_codegen::route;
|
//! # use actix_web_codegen::route;
|
||||||
//! #[route("/test", method="GET", method="HEAD")]
|
//! #[route("/test", method = "GET", method = "HEAD")]
|
||||||
//! async fn get_and_head_handler() -> HttpResponse {
|
//! async fn get_and_head_handler() -> HttpResponse {
|
||||||
//! HttpResponse::Ok().finish()
|
//! HttpResponse::Ok().finish()
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! [actix-web attributes docs]: https://docs.rs/actix-web/*/actix_web/#attributes
|
//! # Multiple Path Handlers
|
||||||
|
//! There are no macros to generate multi-path handlers. Let us know in [this issue].
|
||||||
|
//!
|
||||||
|
//! [this issue]: https://github.com/actix/actix-web/issues/1709
|
||||||
|
//!
|
||||||
|
//! [actix-web attributes docs]: https://docs.rs/actix-web/latest/actix_web/#attributes
|
||||||
//! [GET]: macro@get
|
//! [GET]: macro@get
|
||||||
//! [POST]: macro@post
|
//! [POST]: macro@post
|
||||||
//! [PUT]: macro@put
|
//! [PUT]: macro@put
|
||||||
@ -73,22 +78,23 @@ mod route;
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Attributes
|
/// # Attributes
|
||||||
/// - `"path"` - Raw literal string with path for which to register handler.
|
/// - `"path"`: Raw literal string with path for which to register handler.
|
||||||
/// - `name="resource_name"` - Specifies resource name for the handler. If not set, the function name of handler is used.
|
/// - `name = "resource_name"`: Specifies resource name for the handler. If not set, the function
|
||||||
/// - `method="HTTP_METHOD"` - Registers HTTP method to provide guard for. Upper-case string, "GET", "POST" for example.
|
/// name of handler is used.
|
||||||
/// - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard`
|
/// - `method = "HTTP_METHOD"`: Registers HTTP method to provide guard for. Upper-case string,
|
||||||
/// - `wrap="Middleware"` - Registers a resource middleware.
|
/// "GET", "POST" for example.
|
||||||
|
/// - `guard = "function_name"`: Registers function as guard using `actix_web::guard::fn_guard`.
|
||||||
|
/// - `wrap = "Middleware"`: Registers a resource middleware.
|
||||||
///
|
///
|
||||||
/// # Notes
|
/// # Notes
|
||||||
/// Function name can be specified as any expression that is going to be accessible to the generate
|
/// Function name can be specified as any expression that is going to be accessible to the generate
|
||||||
/// code, e.g `my_guard` or `my_module::my_guard`.
|
/// code, e.g `my_guard` or `my_module::my_guard`.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Examples
|
||||||
///
|
|
||||||
/// ```
|
/// ```
|
||||||
/// # use actix_web::HttpResponse;
|
/// # use actix_web::HttpResponse;
|
||||||
/// # use actix_web_codegen::route;
|
/// # use actix_web_codegen::route;
|
||||||
/// #[route("/test", method="GET", method="HEAD")]
|
/// #[route("/test", method = "GET", method = "HEAD")]
|
||||||
/// async fn example() -> HttpResponse {
|
/// async fn example() -> HttpResponse {
|
||||||
/// HttpResponse::Ok().finish()
|
/// HttpResponse::Ok().finish()
|
||||||
/// }
|
/// }
|
||||||
@ -98,69 +104,54 @@ pub fn route(args: TokenStream, input: TokenStream) -> TokenStream {
|
|||||||
route::with_method(None, args, input)
|
route::with_method(None, args, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! doc_comment {
|
|
||||||
($x:expr; $($tt:tt)*) => {
|
|
||||||
#[doc = $x]
|
|
||||||
$($tt)*
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! method_macro {
|
macro_rules! method_macro {
|
||||||
(
|
($variant:ident, $method:ident) => {
|
||||||
$($variant:ident, $method:ident,)+
|
#[doc = concat!("Creates route handler with `actix_web::guard::", stringify!($variant), "`.")]
|
||||||
) => {
|
///
|
||||||
$(doc_comment! {
|
/// # Syntax
|
||||||
concat!("
|
/// ```plain
|
||||||
Creates route handler with `actix_web::guard::", stringify!($variant), "`.
|
#[doc = concat!("#[", stringify!($method), r#"("path"[, attributes])]"#)]
|
||||||
|
/// ```
|
||||||
# Syntax
|
///
|
||||||
```plain
|
/// # Attributes
|
||||||
#[", stringify!($method), r#"("path"[, attributes])]
|
/// - `"path"`: Raw literal string with path for which to register handler.
|
||||||
```
|
/// - `name = "resource_name"`: Specifies resource name for the handler. If not set, the function
|
||||||
|
/// name of handler is used.
|
||||||
# Attributes
|
/// - `guard = "function_name"`: Registers function as guard using `actix_web::guard::fn_guard`.
|
||||||
- `"path"` - Raw literal string with path for which to register handler.
|
/// - `wrap = "Middleware"`: Registers a resource middleware.
|
||||||
- `name="resource_name"` - Specifies resource name for the handler. If not set, the function name of handler is used.
|
///
|
||||||
- `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard`.
|
/// # Notes
|
||||||
- `wrap="Middleware"` - Registers a resource middleware.
|
/// Function name can be specified as any expression that is going to be accessible to the
|
||||||
|
/// generate code, e.g `my_guard` or `my_module::my_guard`.
|
||||||
# Notes
|
///
|
||||||
Function name can be specified as any expression that is going to be accessible to the generate
|
/// # Examples
|
||||||
code, e.g `my_guard` or `my_module::my_guard`.
|
/// ```
|
||||||
|
/// # use actix_web::HttpResponse;
|
||||||
# Example
|
#[doc = concat!("# use actix_web_codegen::", stringify!($method), ";")]
|
||||||
|
#[doc = concat!("#[", stringify!($method), r#"("/")]"#)]
|
||||||
```
|
/// async fn example() -> HttpResponse {
|
||||||
# use actix_web::HttpResponse;
|
/// HttpResponse::Ok().finish()
|
||||||
# use actix_web_codegen::"#, stringify!($method), ";
|
/// }
|
||||||
#[", stringify!($method), r#"("/")]
|
/// ```
|
||||||
async fn example() -> HttpResponse {
|
#[proc_macro_attribute]
|
||||||
HttpResponse::Ok().finish()
|
pub fn $method(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
route::with_method(Some(route::MethodType::$variant), args, input)
|
||||||
}
|
}
|
||||||
```
|
|
||||||
"#);
|
|
||||||
#[proc_macro_attribute]
|
|
||||||
pub fn $method(args: TokenStream, input: TokenStream) -> TokenStream {
|
|
||||||
route::with_method(Some(route::MethodType::$variant), args, input)
|
|
||||||
}
|
|
||||||
})+
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
method_macro! {
|
method_macro!(Get, get);
|
||||||
Get, get,
|
method_macro!(Post, post);
|
||||||
Post, post,
|
method_macro!(Put, put);
|
||||||
Put, put,
|
method_macro!(Delete, delete);
|
||||||
Delete, delete,
|
method_macro!(Head, head);
|
||||||
Head, head,
|
method_macro!(Connect, connect);
|
||||||
Connect, connect,
|
method_macro!(Options, options);
|
||||||
Options, options,
|
method_macro!(Trace, trace);
|
||||||
Trace, trace,
|
method_macro!(Patch, patch);
|
||||||
Patch, patch,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Marks async main function as the actix system entry-point.
|
|
||||||
|
|
||||||
|
/// Marks async main function as the Actix Web system entry-point.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// #[actix_web::main]
|
/// #[actix_web::main]
|
||||||
|
@ -302,13 +302,13 @@ impl ToTokens for Route {
|
|||||||
if methods.len() > 1 {
|
if methods.len() > 1 {
|
||||||
quote! {
|
quote! {
|
||||||
.guard(
|
.guard(
|
||||||
actix_web::guard::Any(actix_web::guard::#first())
|
::actix_web::guard::Any(::actix_web::guard::#first())
|
||||||
#(.or(actix_web::guard::#others()))*
|
#(.or(::actix_web::guard::#others()))*
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
.guard(actix_web::guard::#first())
|
.guard(::actix_web::guard::#first())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -318,17 +318,17 @@ impl ToTokens for Route {
|
|||||||
#[allow(non_camel_case_types, missing_docs)]
|
#[allow(non_camel_case_types, missing_docs)]
|
||||||
pub struct #name;
|
pub struct #name;
|
||||||
|
|
||||||
impl actix_web::dev::HttpServiceFactory for #name {
|
impl ::actix_web::dev::HttpServiceFactory for #name {
|
||||||
fn register(self, __config: &mut actix_web::dev::AppService) {
|
fn register(self, __config: &mut actix_web::dev::AppService) {
|
||||||
#ast
|
#ast
|
||||||
let __resource = actix_web::Resource::new(#path)
|
let __resource = ::actix_web::Resource::new(#path)
|
||||||
.name(#resource_name)
|
.name(#resource_name)
|
||||||
#method_guards
|
#method_guards
|
||||||
#(.guard(actix_web::guard::fn_guard(#guards)))*
|
#(.guard(::actix_web::guard::fn_guard(#guards)))*
|
||||||
#(.wrap(#wrappers))*
|
#(.wrap(#wrappers))*
|
||||||
.#resource_type(#name);
|
.#resource_type(#name);
|
||||||
|
|
||||||
actix_web::dev::HttpServiceFactory::register(__resource, __config)
|
::actix_web::dev::HttpServiceFactory::register(__resource, __config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -95,13 +95,13 @@ trust-dns-resolver = { version = "0.20.0", optional = true }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-http = { version = "3.0.0-beta.18", features = ["openssl"] }
|
actix-http = { version = "3.0.0-beta.18", features = ["openssl"] }
|
||||||
actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] }
|
actix-http-test = { version = "3.0.0-beta.11", features = ["openssl"] }
|
||||||
actix-server = "2.0.0-rc.2"
|
actix-server = "2"
|
||||||
actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] }
|
actix-test = { version = "0.1.0-beta.11", features = ["openssl", "rustls"] }
|
||||||
actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] }
|
actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] }
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
actix-web = { version = "4.0.0-beta.20", features = ["openssl"] }
|
actix-web = { version = "4.0.0-beta.20", features = ["openssl"] }
|
||||||
|
|
||||||
brotli2 = "0.3.2"
|
brotli = "3.3.3"
|
||||||
const-str = "0.3"
|
const-str = "0.3"
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
flate2 = "1.0.13"
|
flate2 = "1.0.13"
|
||||||
@ -109,6 +109,7 @@ futures-util = { version = "0.3.7", default-features = false }
|
|||||||
static_assertions = "1.1"
|
static_assertions = "1.1"
|
||||||
rcgen = "0.8"
|
rcgen = "0.8"
|
||||||
rustls-pemfile = "0.2"
|
rustls-pemfile = "0.2"
|
||||||
|
tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] }
|
||||||
zstd = "0.9"
|
zstd = "0.9"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
|
|
||||||
#[actix_web::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn StdError>> {
|
async fn main() -> Result<(), Box<dyn StdError>> {
|
||||||
std::env::set_var("RUST_LOG", "client=trace,awc=trace,actix_http=trace");
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
|
// construct request builder
|
||||||
let client = awc::Client::new();
|
let client = awc::Client::new();
|
||||||
|
|
||||||
// Create request builder, configure request and send
|
// configure request
|
||||||
let request = client
|
let request = client
|
||||||
.get("https://www.rust-lang.org/")
|
.get("https://www.rust-lang.org/")
|
||||||
.append_header(("User-Agent", "Actix-web"));
|
.append_header(("User-Agent", "Actix-web"));
|
||||||
@ -16,7 +16,7 @@ async fn main() -> Result<(), Box<dyn StdError>> {
|
|||||||
|
|
||||||
let mut response = request.send().await?;
|
let mut response = request.send().await?;
|
||||||
|
|
||||||
// server http response
|
// server response head
|
||||||
println!("Response: {:?}", response);
|
println!("Response: {:?}", response);
|
||||||
|
|
||||||
// read response body
|
// read response body
|
||||||
|
@ -207,7 +207,7 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maximum supported HTTP major version.
|
/// Sets maximum supported HTTP major version.
|
||||||
///
|
///
|
||||||
/// Supported versions are HTTP/1.1 and HTTP/2.
|
/// Supported versions are HTTP/1.1 and HTTP/2.
|
||||||
pub fn max_http_version(mut self, val: http::Version) -> Self {
|
pub fn max_http_version(mut self, val: http::Version) -> Self {
|
||||||
@ -222,8 +222,8 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates the initial window size (in octets) for
|
/// Sets the initial window size (in octets) for HTTP/2 stream-level flow control for
|
||||||
/// HTTP2 stream-level flow control for received data.
|
/// received data.
|
||||||
///
|
///
|
||||||
/// The default value is 65,535 and is good for APIs, but not for big objects.
|
/// The default value is 65,535 and is good for APIs, but not for big objects.
|
||||||
pub fn initial_window_size(mut self, size: u32) -> Self {
|
pub fn initial_window_size(mut self, size: u32) -> Self {
|
||||||
@ -231,8 +231,8 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates the initial window size (in octets) for
|
/// Sets the initial window size (in octets) for HTTP/2 connection-level flow control for
|
||||||
/// HTTP2 connection-level flow control for received data.
|
/// received data.
|
||||||
///
|
///
|
||||||
/// The default value is 65,535 and is good for APIs, but not for big objects.
|
/// The default value is 65,535 and is good for APIs, but not for big objects.
|
||||||
pub fn initial_connection_window_size(mut self, size: u32) -> Self {
|
pub fn initial_connection_window_size(mut self, size: u32) -> Self {
|
||||||
@ -243,6 +243,7 @@ where
|
|||||||
/// Set total number of simultaneous connections per type of scheme.
|
/// Set total number of simultaneous connections per type of scheme.
|
||||||
///
|
///
|
||||||
/// If limit is 0, the connector has no limit.
|
/// If limit is 0, the connector has no limit.
|
||||||
|
///
|
||||||
/// The default limit size is 100.
|
/// The default limit size is 100.
|
||||||
pub fn limit(mut self, limit: usize) -> Self {
|
pub fn limit(mut self, limit: usize) -> Self {
|
||||||
self.config.limit = limit;
|
self.config.limit = limit;
|
||||||
|
@ -67,12 +67,13 @@ impl Default for Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
/// Create new client instance with default settings.
|
/// Constructs new client instance with default settings.
|
||||||
pub fn new() -> Client {
|
pub fn new() -> Client {
|
||||||
Client::default()
|
Client::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create `Client` builder.
|
/// Constructs new `Client` builder.
|
||||||
|
///
|
||||||
/// This function is equivalent of `ClientBuilder::new()`.
|
/// This function is equivalent of `ClientBuilder::new()`.
|
||||||
pub fn builder() -> ClientBuilder<
|
pub fn builder() -> ClientBuilder<
|
||||||
impl Service<
|
impl Service<
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cell::{Ref, RefMut},
|
cell::{Ref, RefCell, RefMut},
|
||||||
fmt, mem,
|
fmt, mem,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
@ -28,6 +28,8 @@ pin_project! {
|
|||||||
#[pin]
|
#[pin]
|
||||||
pub(crate) payload: Payload<S>,
|
pub(crate) payload: Payload<S>,
|
||||||
pub(crate) timeout: ResponseTimeout,
|
pub(crate) timeout: ResponseTimeout,
|
||||||
|
pub(crate) extensions: RefCell<Extensions>,
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,6 +40,7 @@ impl<S> ClientResponse<S> {
|
|||||||
head,
|
head,
|
||||||
payload,
|
payload,
|
||||||
timeout: ResponseTimeout::default(),
|
timeout: ResponseTimeout::default(),
|
||||||
|
extensions: RefCell::new(Extensions::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +67,9 @@ impl<S> ClientResponse<S> {
|
|||||||
&self.head().headers
|
&self.head().headers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a body and return previous body value
|
/// Map the current body type to another using a closure. Returns a new response.
|
||||||
|
///
|
||||||
|
/// Closure receives the response head and the current body type.
|
||||||
pub fn map_body<F, U>(mut self, f: F) -> ClientResponse<U>
|
pub fn map_body<F, U>(mut self, f: F) -> ClientResponse<U>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut ResponseHead, Payload<S>) -> Payload<U>,
|
F: FnOnce(&mut ResponseHead, Payload<S>) -> Payload<U>,
|
||||||
@ -75,6 +80,7 @@ impl<S> ClientResponse<S> {
|
|||||||
payload,
|
payload,
|
||||||
head: self.head,
|
head: self.head,
|
||||||
timeout: self.timeout,
|
timeout: self.timeout,
|
||||||
|
extensions: self.extensions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,6 +107,7 @@ impl<S> ClientResponse<S> {
|
|||||||
payload: self.payload,
|
payload: self.payload,
|
||||||
head: self.head,
|
head: self.head,
|
||||||
timeout,
|
timeout,
|
||||||
|
extensions: self.extensions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +160,6 @@ where
|
|||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// `Future` implementation returns error if:
|
/// `Future` implementation returns error if:
|
||||||
/// - content type is not `application/json`
|
|
||||||
/// - content length is greater than [limit](JsonBody::limit) (default: 2 MiB)
|
/// - content length is greater than [limit](JsonBody::limit) (default: 2 MiB)
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
@ -224,11 +230,11 @@ impl<S> HttpMessage for ClientResponse<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn extensions(&self) -> Ref<'_, Extensions> {
|
fn extensions(&self) -> Ref<'_, Extensions> {
|
||||||
self.head.extensions()
|
self.extensions.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
||||||
self.head.extensions_mut()
|
self.extensions.borrow_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! Type definitions required to use [`awc::Client`](super::Client) as a WebSocket client.
|
//! Type definitions required to use [`awc::Client`](super::Client) as a WebSocket client.
|
||||||
//!
|
//!
|
||||||
//! # Example
|
//! # Examples
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use awc::{Client, ws};
|
//! use awc::{Client, ws};
|
||||||
|
@ -41,16 +41,22 @@ pub mod deflate {
|
|||||||
|
|
||||||
pub mod brotli {
|
pub mod brotli {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ::brotli2::{read::BrotliDecoder, write::BrotliEncoder};
|
use ::brotli::{reader::Decompressor as BrotliDecoder, CompressorWriter as BrotliEncoder};
|
||||||
|
|
||||||
pub fn encode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
|
pub fn encode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
|
||||||
let mut encoder = BrotliEncoder::new(Vec::new(), 3);
|
let mut encoder = BrotliEncoder::new(
|
||||||
|
Vec::new(),
|
||||||
|
8 * 1024, // 32 KiB buffer
|
||||||
|
3, // BROTLI_PARAM_QUALITY
|
||||||
|
22, // BROTLI_PARAM_LGWIN
|
||||||
|
);
|
||||||
encoder.write_all(bytes.as_ref()).unwrap();
|
encoder.write_all(bytes.as_ref()).unwrap();
|
||||||
encoder.finish().unwrap()
|
encoder.flush().unwrap();
|
||||||
|
encoder.into_inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
|
pub fn decode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
|
||||||
let mut decoder = BrotliDecoder::new(bytes.as_ref());
|
let mut decoder = BrotliDecoder::new(bytes.as_ref(), 8_096);
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
decoder.read_to_end(&mut buf).unwrap();
|
decoder.read_to_end(&mut buf).unwrap();
|
||||||
buf
|
buf
|
||||||
|
@ -24,7 +24,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
App::new()
|
App::new()
|
||||||
.wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2")))
|
.wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2")))
|
||||||
.wrap(middleware::Compress::default())
|
.wrap(middleware::Compress::default())
|
||||||
.wrap(middleware::Logger::default())
|
.wrap(middleware::Logger::default().log_target("http_log"))
|
||||||
.service(index)
|
.service(index)
|
||||||
.service(no_params)
|
.service(no_params)
|
||||||
.service(
|
.service(
|
||||||
|
@ -31,7 +31,7 @@ fi
|
|||||||
|
|
||||||
# get current version
|
# get current version
|
||||||
PACKAGE_NAME="$(sed -nE 's/^name ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST" | head -n 1)"
|
PACKAGE_NAME="$(sed -nE 's/^name ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST" | head -n 1)"
|
||||||
CURRENT_VERSION="$(sed -nE 's/^version ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST")"
|
CURRENT_VERSION="$(sed -nE 's/^version ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST" | head -n 1)"
|
||||||
|
|
||||||
CHANGE_CHUNK_FILE="$(mktemp)"
|
CHANGE_CHUNK_FILE="$(mktemp)"
|
||||||
echo saving changelog to $CHANGE_CHUNK_FILE
|
echo saving changelog to $CHANGE_CHUNK_FILE
|
||||||
|
@ -201,27 +201,29 @@ where
|
|||||||
actix_service::forward_ready!(service);
|
actix_service::forward_ready!(service);
|
||||||
|
|
||||||
fn call(&self, mut req: Request) -> Self::Future {
|
fn call(&self, mut req: Request) -> Self::Future {
|
||||||
let req_data = Rc::new(RefCell::new(req.take_req_data()));
|
let extensions = Rc::new(RefCell::new(req.take_req_data()));
|
||||||
let conn_data = req.take_conn_data();
|
let conn_data = req.take_conn_data();
|
||||||
let (head, payload) = req.into_parts();
|
let (head, payload) = req.into_parts();
|
||||||
|
|
||||||
let req = if let Some(mut req) = self.app_state.pool().pop() {
|
let req = match self.app_state.pool().pop() {
|
||||||
let inner = Rc::get_mut(&mut req.inner).unwrap();
|
Some(mut req) => {
|
||||||
inner.path.get_mut().update(&head.uri);
|
let inner = Rc::get_mut(&mut req.inner).unwrap();
|
||||||
inner.path.reset();
|
inner.path.get_mut().update(&head.uri);
|
||||||
inner.head = head;
|
inner.path.reset();
|
||||||
inner.conn_data = conn_data;
|
inner.head = head;
|
||||||
inner.req_data = req_data;
|
inner.conn_data = conn_data;
|
||||||
req
|
inner.extensions = extensions;
|
||||||
} else {
|
req
|
||||||
HttpRequest::new(
|
}
|
||||||
|
|
||||||
|
None => HttpRequest::new(
|
||||||
Path::new(Url::new(head.uri.clone())),
|
Path::new(Url::new(head.uri.clone())),
|
||||||
head,
|
head,
|
||||||
self.app_state.clone(),
|
Rc::clone(&self.app_state),
|
||||||
self.app_data.clone(),
|
Rc::clone(&self.app_data),
|
||||||
conn_data,
|
conn_data,
|
||||||
req_data,
|
extensions,
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.service.call(ServiceRequest::new(req, payload))
|
self.service.call(ServiceRequest::new(req, payload))
|
||||||
|
10
src/guard.rs
10
src/guard.rs
@ -54,7 +54,7 @@ use std::{
|
|||||||
|
|
||||||
use actix_http::{header, uri::Uri, Extensions, Method as HttpMethod, RequestHead};
|
use actix_http::{header, uri::Uri, Extensions, Method as HttpMethod, RequestHead};
|
||||||
|
|
||||||
use crate::{http::header::Header, service::ServiceRequest};
|
use crate::{http::header::Header, service::ServiceRequest, HttpMessage as _};
|
||||||
|
|
||||||
/// Provides access to request parts that are useful during routing.
|
/// Provides access to request parts that are useful during routing.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -69,16 +69,16 @@ impl<'a> GuardContext<'a> {
|
|||||||
self.req.head()
|
self.req.head()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns reference to the request-local data container.
|
/// Returns reference to the request-local data/extensions container.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn req_data(&self) -> Ref<'a, Extensions> {
|
pub fn req_data(&self) -> Ref<'a, Extensions> {
|
||||||
self.req.req_data()
|
self.req.extensions()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns mutable reference to the request-local data container.
|
/// Returns mutable reference to the request-local data/extensions container.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn req_data_mut(&self) -> RefMut<'a, Extensions> {
|
pub fn req_data_mut(&self) -> RefMut<'a, Extensions> {
|
||||||
self.req.req_data_mut()
|
self.req.extensions_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts a typed header from the request.
|
/// Extracts a typed header from the request.
|
||||||
|
@ -16,7 +16,7 @@ crate::http::header::common_header! {
|
|||||||
/// # Example Values
|
/// # Example Values
|
||||||
/// * `Tue, 15 Nov 1994 08:12:31 GMT`
|
/// * `Tue, 15 Nov 1994 08:12:31 GMT`
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use std::time::SystemTime;
|
/// use std::time::SystemTime;
|
||||||
|
@ -19,7 +19,7 @@ crate::http::header::common_header! {
|
|||||||
/// # Example Values
|
/// # Example Values
|
||||||
/// * `Thu, 01 Dec 1994 16:00:00 GMT`
|
/// * `Thu, 01 Dec 1994 16:00:00 GMT`
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use std::time::{SystemTime, Duration};
|
/// use std::time::{SystemTime, Duration};
|
||||||
|
@ -18,7 +18,7 @@ crate::http::header::common_header! {
|
|||||||
/// # Example Values
|
/// # Example Values
|
||||||
/// * `Sat, 29 Oct 1994 19:43:31 GMT`
|
/// * `Sat, 29 Oct 1994 19:43:31 GMT`
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use std::time::{SystemTime, Duration};
|
/// use std::time::{SystemTime, Duration};
|
||||||
|
@ -18,7 +18,7 @@ crate::http::header::common_header! {
|
|||||||
/// # Example Values
|
/// # Example Values
|
||||||
/// * `Sat, 29 Oct 1994 19:43:31 GMT`
|
/// * `Sat, 29 Oct 1994 19:43:31 GMT`
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use std::time::{SystemTime, Duration};
|
/// use std::time::{SystemTime, Duration};
|
||||||
|
@ -17,7 +17,7 @@ crate::http::header::common_header! {
|
|||||||
/// # Example Values
|
/// # Example Values
|
||||||
/// * `Sat, 29 Oct 1994 19:43:31 GMT`
|
/// * `Sat, 29 Oct 1994 19:43:31 GMT`
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use std::time::{SystemTime, Duration};
|
/// use std::time::{SystemTime, Duration};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! For middleware documentation, see [`Logger`].
|
//! For middleware documentation, see [`Logger`].
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
env,
|
env,
|
||||||
@ -87,6 +88,7 @@ struct Inner {
|
|||||||
format: Format,
|
format: Format,
|
||||||
exclude: HashSet<String>,
|
exclude: HashSet<String>,
|
||||||
exclude_regex: RegexSet,
|
exclude_regex: RegexSet,
|
||||||
|
log_target: Cow<'static, str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Logger {
|
impl Logger {
|
||||||
@ -96,6 +98,7 @@ impl Logger {
|
|||||||
format: Format::new(format),
|
format: Format::new(format),
|
||||||
exclude: HashSet::new(),
|
exclude: HashSet::new(),
|
||||||
exclude_regex: RegexSet::empty(),
|
exclude_regex: RegexSet::empty(),
|
||||||
|
log_target: Cow::Borrowed(module_path!()),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,13 +121,31 @@ impl Logger {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the logging target to `target`.
|
||||||
|
///
|
||||||
|
/// By default, the log target is `module_path!()` of the log call location. In our case, that
|
||||||
|
/// would be `actix_web::middleware::logger`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// Using `.log_target("http_log")` would have this effect on request logs:
|
||||||
|
/// ```diff
|
||||||
|
/// - [2015-10-21T07:28:00Z INFO actix_web::middleware::logger] 127.0.0.1 "GET / HTTP/1.1" 200 88 "-" "dmc/1.0" 0.001985
|
||||||
|
/// + [2015-10-21T07:28:00Z INFO http_log] 127.0.0.1 "GET / HTTP/1.1" 200 88 "-" "dmc/1.0" 0.001985
|
||||||
|
/// ^^^^^^^^
|
||||||
|
/// ```
|
||||||
|
pub fn log_target(mut self, target: impl Into<Cow<'static, str>>) -> Self {
|
||||||
|
let inner = Rc::get_mut(&mut self.0).unwrap();
|
||||||
|
inner.log_target = target.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Register a function that receives a ServiceRequest and returns a String for use in the
|
/// Register a function that receives a ServiceRequest and returns a String for use in the
|
||||||
/// log line. The label passed as the first argument should match a replacement substring in
|
/// log line. The label passed as the first argument should match a replacement substring in
|
||||||
/// the logger format like `%{label}xi`.
|
/// the logger format like `%{label}xi`.
|
||||||
///
|
///
|
||||||
/// It is convention to print "-" to indicate no output instead of an empty string.
|
/// It is convention to print "-" to indicate no output instead of an empty string.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// # use actix_web::http::{header::HeaderValue};
|
/// # use actix_web::http::{header::HeaderValue};
|
||||||
/// # use actix_web::middleware::Logger;
|
/// # use actix_web::middleware::Logger;
|
||||||
@ -171,6 +192,7 @@ impl Default for Logger {
|
|||||||
format: Format::default(),
|
format: Format::default(),
|
||||||
exclude: HashSet::new(),
|
exclude: HashSet::new(),
|
||||||
exclude_regex: RegexSet::empty(),
|
exclude_regex: RegexSet::empty(),
|
||||||
|
log_target: Cow::Borrowed(module_path!()),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,13 +244,15 @@ where
|
|||||||
actix_service::forward_ready!(service);
|
actix_service::forward_ready!(service);
|
||||||
|
|
||||||
fn call(&self, req: ServiceRequest) -> Self::Future {
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||||
if self.inner.exclude.contains(req.path())
|
let excluded = self.inner.exclude.contains(req.path())
|
||||||
|| self.inner.exclude_regex.is_match(req.path())
|
|| self.inner.exclude_regex.is_match(req.path());
|
||||||
{
|
|
||||||
|
if excluded {
|
||||||
LoggerResponse {
|
LoggerResponse {
|
||||||
fut: self.service.call(req),
|
fut: self.service.call(req),
|
||||||
format: None,
|
format: None,
|
||||||
time: OffsetDateTime::now_utc(),
|
time: OffsetDateTime::now_utc(),
|
||||||
|
log_target: Cow::Borrowed(""),
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -238,10 +262,12 @@ where
|
|||||||
for unit in &mut format.0 {
|
for unit in &mut format.0 {
|
||||||
unit.render_request(now, &req);
|
unit.render_request(now, &req);
|
||||||
}
|
}
|
||||||
|
|
||||||
LoggerResponse {
|
LoggerResponse {
|
||||||
fut: self.service.call(req),
|
fut: self.service.call(req),
|
||||||
format: Some(format),
|
format: Some(format),
|
||||||
time: now,
|
time: now,
|
||||||
|
log_target: self.inner.log_target.clone(),
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -258,6 +284,7 @@ pin_project! {
|
|||||||
fut: S::Future,
|
fut: S::Future,
|
||||||
time: OffsetDateTime,
|
time: OffsetDateTime,
|
||||||
format: Option<Format>,
|
format: Option<Format>,
|
||||||
|
log_target: Cow<'static, str>,
|
||||||
_phantom: PhantomData<B>,
|
_phantom: PhantomData<B>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -289,12 +316,14 @@ where
|
|||||||
|
|
||||||
let time = *this.time;
|
let time = *this.time;
|
||||||
let format = this.format.take();
|
let format = this.format.take();
|
||||||
|
let log_target = this.log_target.clone();
|
||||||
|
|
||||||
Poll::Ready(Ok(res.map_body(move |_, body| StreamLog {
|
Poll::Ready(Ok(res.map_body(move |_, body| StreamLog {
|
||||||
body,
|
body,
|
||||||
time,
|
time,
|
||||||
format,
|
format,
|
||||||
size: 0,
|
size: 0,
|
||||||
|
log_target,
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -306,7 +335,9 @@ pin_project! {
|
|||||||
format: Option<Format>,
|
format: Option<Format>,
|
||||||
size: usize,
|
size: usize,
|
||||||
time: OffsetDateTime,
|
time: OffsetDateTime,
|
||||||
|
log_target: Cow<'static, str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B> PinnedDrop for StreamLog<B> {
|
impl<B> PinnedDrop for StreamLog<B> {
|
||||||
fn drop(this: Pin<&mut Self>) {
|
fn drop(this: Pin<&mut Self>) {
|
||||||
if let Some(ref format) = this.format {
|
if let Some(ref format) = this.format {
|
||||||
@ -316,7 +347,11 @@ pin_project! {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
log::info!("{}", FormatDisplay(&render));
|
|
||||||
|
log::info!(
|
||||||
|
target: this.log_target.as_ref(),
|
||||||
|
"{}", FormatDisplay(&render)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -700,7 +735,6 @@ mod tests {
|
|||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
let s = format!("{}", FormatDisplay(&render));
|
let s = format!("{}", FormatDisplay(&render));
|
||||||
println!("{}", s);
|
|
||||||
assert!(s.contains("/test/route/yeah"));
|
assert!(s.contains("/test/route/yeah"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -794,7 +828,6 @@ mod tests {
|
|||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
let s = format!("{}", FormatDisplay(&render));
|
let s = format!("{}", FormatDisplay(&render));
|
||||||
println!("{}", s);
|
|
||||||
assert!(s.contains("192.0.2.60"));
|
assert!(s.contains("192.0.2.60"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,10 +5,7 @@ use std::{
|
|||||||
str,
|
str,
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{Message, RequestHead};
|
||||||
header::HeaderMap, Extensions, HttpMessage, Message, Method, Payload, RequestHead, Uri,
|
|
||||||
Version,
|
|
||||||
};
|
|
||||||
use actix_router::{Path, Url};
|
use actix_router::{Path, Url};
|
||||||
use actix_utils::future::{ok, Ready};
|
use actix_utils::future::{ok, Ready};
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
@ -16,8 +13,14 @@ use cookie::{Cookie, ParseError as CookieParseError};
|
|||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app_service::AppInitServiceState, config::AppConfig, error::UrlGenerationError,
|
app_service::AppInitServiceState,
|
||||||
info::ConnectionInfo, rmap::ResourceMap, Error, FromRequest,
|
config::AppConfig,
|
||||||
|
dev::{Extensions, Payload},
|
||||||
|
error::UrlGenerationError,
|
||||||
|
http::{header::HeaderMap, Method, Uri, Version},
|
||||||
|
info::ConnectionInfo,
|
||||||
|
rmap::ResourceMap,
|
||||||
|
Error, FromRequest, HttpMessage,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
@ -38,7 +41,7 @@ pub(crate) struct HttpRequestInner {
|
|||||||
pub(crate) path: Path<Url>,
|
pub(crate) path: Path<Url>,
|
||||||
pub(crate) app_data: SmallVec<[Rc<Extensions>; 4]>,
|
pub(crate) app_data: SmallVec<[Rc<Extensions>; 4]>,
|
||||||
pub(crate) conn_data: Option<Rc<Extensions>>,
|
pub(crate) conn_data: Option<Rc<Extensions>>,
|
||||||
pub(crate) req_data: Rc<RefCell<Extensions>>,
|
pub(crate) extensions: Rc<RefCell<Extensions>>,
|
||||||
app_state: Rc<AppInitServiceState>,
|
app_state: Rc<AppInitServiceState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +53,7 @@ impl HttpRequest {
|
|||||||
app_state: Rc<AppInitServiceState>,
|
app_state: Rc<AppInitServiceState>,
|
||||||
app_data: Rc<Extensions>,
|
app_data: Rc<Extensions>,
|
||||||
conn_data: Option<Rc<Extensions>>,
|
conn_data: Option<Rc<Extensions>>,
|
||||||
req_data: Rc<RefCell<Extensions>>,
|
extensions: Rc<RefCell<Extensions>>,
|
||||||
) -> HttpRequest {
|
) -> HttpRequest {
|
||||||
let mut data = SmallVec::<[Rc<Extensions>; 4]>::new();
|
let mut data = SmallVec::<[Rc<Extensions>; 4]>::new();
|
||||||
data.push(app_data);
|
data.push(app_data);
|
||||||
@ -62,7 +65,7 @@ impl HttpRequest {
|
|||||||
app_state,
|
app_state,
|
||||||
app_data: data,
|
app_data: data,
|
||||||
conn_data,
|
conn_data,
|
||||||
req_data,
|
extensions,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,6 +138,10 @@ impl HttpRequest {
|
|||||||
&self.inner.path
|
&self.inner.path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the URL parameters container.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if this `HttpRequest` has been cloned.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn match_info_mut(&mut self) -> &mut Path<Url> {
|
pub(crate) fn match_info_mut(&mut self) -> &mut Path<Url> {
|
||||||
&mut Rc::get_mut(&mut self.inner).unwrap().path
|
&mut Rc::get_mut(&mut self.inner).unwrap().path
|
||||||
@ -159,14 +166,6 @@ impl HttpRequest {
|
|||||||
self.resource_map().match_name(self.path())
|
self.resource_map().match_name(self.path())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn req_data(&self) -> Ref<'_, Extensions> {
|
|
||||||
self.inner.req_data.borrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn req_data_mut(&self) -> RefMut<'_, Extensions> {
|
|
||||||
self.inner.req_data.borrow_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference a piece of connection data set in an [on-connect] callback.
|
/// Returns a reference a piece of connection data set in an [on-connect] callback.
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
@ -213,7 +212,7 @@ impl HttpRequest {
|
|||||||
self.resource_map().url_for(self, name, elements)
|
self.resource_map().url_for(self, name, elements)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate url for named resource
|
/// Generate URL for named resource
|
||||||
///
|
///
|
||||||
/// This method is similar to `HttpRequest::url_for()` but it can be used
|
/// This method is similar to `HttpRequest::url_for()` but it can be used
|
||||||
/// for urls that do not contain variable parts.
|
/// for urls that do not contain variable parts.
|
||||||
@ -356,12 +355,12 @@ impl HttpMessage for HttpRequest {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn extensions(&self) -> Ref<'_, Extensions> {
|
fn extensions(&self) -> Ref<'_, Extensions> {
|
||||||
self.req_data()
|
self.inner.extensions.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
||||||
self.req_data_mut()
|
self.inner.extensions.borrow_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -382,7 +381,10 @@ impl Drop for HttpRequest {
|
|||||||
|
|
||||||
// Inner is borrowed mut here and; get req data mutably to reduce borrow check. Also
|
// Inner is borrowed mut here and; get req data mutably to reduce borrow check. Also
|
||||||
// we know the req_data Rc will not have any cloned at this point to unwrap is okay.
|
// we know the req_data Rc will not have any cloned at this point to unwrap is okay.
|
||||||
Rc::get_mut(&mut inner.req_data).unwrap().get_mut().clear();
|
Rc::get_mut(&mut inner.extensions)
|
||||||
|
.unwrap()
|
||||||
|
.get_mut()
|
||||||
|
.clear();
|
||||||
|
|
||||||
// a re-borrow of pool is necessary here.
|
// a re-borrow of pool is necessary here.
|
||||||
let req = Rc::clone(&self.inner);
|
let req = Rc::clone(&self.inner);
|
||||||
@ -506,10 +508,12 @@ mod tests {
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::dev::{ResourceDef, ResourceMap};
|
use crate::{
|
||||||
use crate::http::{header, StatusCode};
|
dev::{ResourceDef, ResourceMap},
|
||||||
use crate::test::{call_service, init_service, read_body, TestRequest};
|
http::{header, StatusCode},
|
||||||
use crate::{web, App, HttpResponse};
|
test::{self, call_service, init_service, read_body, TestRequest},
|
||||||
|
web, App, HttpResponse,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_debug() {
|
fn test_debug() {
|
||||||
@ -863,4 +867,47 @@ mod tests {
|
|||||||
let res = call_service(&srv, req).await;
|
let res = call_service(&srv, req).await;
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn url_for_closest_named_resource() {
|
||||||
|
// we mount the route named 'nested' on 2 different scopes, 'a' and 'b'
|
||||||
|
let srv = test::init_service(
|
||||||
|
App::new()
|
||||||
|
.service(
|
||||||
|
web::scope("/foo")
|
||||||
|
.service(web::resource("/nested").name("nested").route(web::get().to(
|
||||||
|
|req: HttpRequest| {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.body(format!("{}", req.url_for_static("nested").unwrap()))
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
.service(web::scope("/baz").service(web::resource("deep")))
|
||||||
|
.service(web::resource("{foo_param}")),
|
||||||
|
)
|
||||||
|
.service(web::scope("/bar").service(
|
||||||
|
web::resource("/nested").name("nested").route(web::get().to(
|
||||||
|
|req: HttpRequest| {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.body(format!("{}", req.url_for_static("nested").unwrap()))
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let foo_resp =
|
||||||
|
test::call_service(&srv, TestRequest::with_uri("/foo/nested").to_request()).await;
|
||||||
|
assert_eq!(foo_resp.status(), StatusCode::OK);
|
||||||
|
let body = read_body(foo_resp).await;
|
||||||
|
// `body` equals http://localhost:8080/bar/nested
|
||||||
|
// because nested from /bar overrides /foo's
|
||||||
|
// to do this any other way would require something like a custom tree search
|
||||||
|
assert_eq!(body, "http://localhost:8080/bar/nested");
|
||||||
|
|
||||||
|
let bar_resp =
|
||||||
|
test::call_service(&srv, TestRequest::with_uri("/bar/nested").to_request()).await;
|
||||||
|
assert_eq!(bar_resp.status(), StatusCode::OK);
|
||||||
|
let body = read_body(bar_resp).await;
|
||||||
|
assert_eq!(body, "http://localhost:8080/bar/nested");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,10 @@ use std::{any::type_name, ops::Deref};
|
|||||||
|
|
||||||
use actix_utils::future::{err, ok, Ready};
|
use actix_utils::future::{err, ok, Ready};
|
||||||
|
|
||||||
use crate::{dev::Payload, error::ErrorInternalServerError, Error, FromRequest, HttpRequest};
|
use crate::{
|
||||||
|
dev::Payload, error::ErrorInternalServerError, Error, FromRequest, HttpMessage as _,
|
||||||
|
HttpRequest,
|
||||||
|
};
|
||||||
|
|
||||||
/// Request-local data extractor.
|
/// Request-local data extractor.
|
||||||
///
|
///
|
||||||
@ -17,13 +20,13 @@ use crate::{dev::Payload, error::ErrorInternalServerError, Error, FromRequest, H
|
|||||||
/// # Mutating Request Data
|
/// # Mutating Request Data
|
||||||
/// Note that since extractors must output owned data, only types that `impl Clone` can use this
|
/// Note that since extractors must output owned data, only types that `impl Clone` can use this
|
||||||
/// extractor. A clone is taken of the required request data and can, therefore, not be directly
|
/// extractor. A clone is taken of the required request data and can, therefore, not be directly
|
||||||
/// mutated in-place. To mutate request data, continue to use [`HttpRequest::req_data_mut`] or
|
/// mutated in-place. To mutate request data, continue to use [`HttpRequest::extensions_mut`] or
|
||||||
/// re-insert the cloned data back into the extensions map. A `DerefMut` impl is intentionally not
|
/// re-insert the cloned data back into the extensions map. A `DerefMut` impl is intentionally not
|
||||||
/// provided to make this potential foot-gun more obvious.
|
/// provided to make this potential foot-gun more obvious.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Examples
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// # use actix_web::{web, HttpResponse, HttpRequest, Responder};
|
/// # use actix_web::{web, HttpResponse, HttpRequest, Responder, HttpMessage as _};
|
||||||
///
|
///
|
||||||
/// #[derive(Debug, Clone, PartialEq)]
|
/// #[derive(Debug, Clone, PartialEq)]
|
||||||
/// struct FlagFromMiddleware(String);
|
/// struct FlagFromMiddleware(String);
|
||||||
@ -35,7 +38,7 @@ use crate::{dev::Payload, error::ErrorInternalServerError, Error, FromRequest, H
|
|||||||
/// ) -> impl Responder {
|
/// ) -> impl Responder {
|
||||||
/// // use an option extractor if middleware is not guaranteed to add this type of req data
|
/// // use an option extractor if middleware is not guaranteed to add this type of req data
|
||||||
/// if let Some(flag) = opt_flag {
|
/// if let Some(flag) = opt_flag {
|
||||||
/// assert_eq!(&flag.into_inner(), req.req_data().get::<FlagFromMiddleware>().unwrap());
|
/// assert_eq!(&flag.into_inner(), req.extensions().get::<FlagFromMiddleware>().unwrap());
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// HttpResponse::Ok()
|
/// HttpResponse::Ok()
|
||||||
@ -67,7 +70,7 @@ impl<T: Clone + 'static> FromRequest for ReqData<T> {
|
|||||||
type Future = Ready<Result<Self, Error>>;
|
type Future = Ready<Result<Self, Error>>;
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
if let Some(st) = req.req_data().get::<T>() {
|
if let Some(st) = req.extensions().get::<T>() {
|
||||||
ok(ReqData(st.clone()))
|
ok(ReqData(st.clone()))
|
||||||
} else {
|
} else {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
|
@ -6,23 +6,17 @@ use std::{
|
|||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{error::HttpError, Response, ResponseHead};
|
||||||
body::{BodyStream, BoxBody, MessageBody},
|
|
||||||
error::HttpError,
|
|
||||||
header::{self, HeaderName, TryIntoHeaderPair, TryIntoHeaderValue},
|
|
||||||
ConnectionType, Extensions, Response, ResponseHead, StatusCode,
|
|
||||||
};
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
#[cfg(feature = "cookies")]
|
|
||||||
use actix_http::header::HeaderValue;
|
|
||||||
#[cfg(feature = "cookies")]
|
|
||||||
use cookie::{Cookie, CookieJar};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
body::{BodyStream, BoxBody, MessageBody},
|
||||||
|
dev::Extensions,
|
||||||
error::{Error, JsonPayloadError},
|
error::{Error, JsonPayloadError},
|
||||||
|
http::header::{self, HeaderName, TryIntoHeaderPair, TryIntoHeaderValue},
|
||||||
|
http::{ConnectionType, StatusCode},
|
||||||
BoxError, HttpRequest, HttpResponse, Responder,
|
BoxError, HttpRequest, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -31,9 +25,7 @@ use crate::{
|
|||||||
/// This type can be used to construct an instance of `Response` through a builder-like pattern.
|
/// This type can be used to construct an instance of `Response` through a builder-like pattern.
|
||||||
pub struct HttpResponseBuilder {
|
pub struct HttpResponseBuilder {
|
||||||
res: Option<Response<BoxBody>>,
|
res: Option<Response<BoxBody>>,
|
||||||
err: Option<HttpError>,
|
error: Option<HttpError>,
|
||||||
#[cfg(feature = "cookies")]
|
|
||||||
cookies: Option<CookieJar>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpResponseBuilder {
|
impl HttpResponseBuilder {
|
||||||
@ -42,9 +34,7 @@ impl HttpResponseBuilder {
|
|||||||
pub fn new(status: StatusCode) -> Self {
|
pub fn new(status: StatusCode) -> Self {
|
||||||
Self {
|
Self {
|
||||||
res: Some(Response::with_body(status, BoxBody::new(()))),
|
res: Some(Response::with_body(status, BoxBody::new(()))),
|
||||||
err: None,
|
error: None,
|
||||||
#[cfg(feature = "cookies")]
|
|
||||||
cookies: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +63,7 @@ impl HttpResponseBuilder {
|
|||||||
Ok((key, value)) => {
|
Ok((key, value)) => {
|
||||||
parts.headers.insert(key, value);
|
parts.headers.insert(key, value);
|
||||||
}
|
}
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.error = Some(e.into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +85,7 @@ impl HttpResponseBuilder {
|
|||||||
if let Some(parts) = self.inner() {
|
if let Some(parts) = self.inner() {
|
||||||
match header.try_into_pair() {
|
match header.try_into_pair() {
|
||||||
Ok((key, value)) => parts.headers.append(key, value),
|
Ok((key, value)) => parts.headers.append(key, value),
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.error = Some(e.into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,14 +104,14 @@ impl HttpResponseBuilder {
|
|||||||
K::Error: Into<HttpError>,
|
K::Error: Into<HttpError>,
|
||||||
V: TryIntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
{
|
{
|
||||||
if self.err.is_some() {
|
if self.error.is_some() {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
match (key.try_into(), value.try_into_value()) {
|
match (key.try_into(), value.try_into_value()) {
|
||||||
(Ok(name), Ok(value)) => return self.insert_header((name, value)),
|
(Ok(name), Ok(value)) => return self.insert_header((name, value)),
|
||||||
(Err(err), _) => self.err = Some(err.into()),
|
(Err(err), _) => self.error = Some(err.into()),
|
||||||
(_, Err(err)) => self.err = Some(err.into()),
|
(_, Err(err)) => self.error = Some(err.into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
@ -139,14 +129,14 @@ impl HttpResponseBuilder {
|
|||||||
K::Error: Into<HttpError>,
|
K::Error: Into<HttpError>,
|
||||||
V: TryIntoHeaderValue,
|
V: TryIntoHeaderValue,
|
||||||
{
|
{
|
||||||
if self.err.is_some() {
|
if self.error.is_some() {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
match (key.try_into(), value.try_into_value()) {
|
match (key.try_into(), value.try_into_value()) {
|
||||||
(Ok(name), Ok(value)) => return self.append_header((name, value)),
|
(Ok(name), Ok(value)) => return self.append_header((name, value)),
|
||||||
(Err(err), _) => self.err = Some(err.into()),
|
(Err(err), _) => self.error = Some(err.into()),
|
||||||
(_, Err(err)) => self.err = Some(err.into()),
|
(_, Err(err)) => self.error = Some(err.into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
@ -219,18 +209,23 @@ impl HttpResponseBuilder {
|
|||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
parts.headers.insert(header::CONTENT_TYPE, value);
|
parts.headers.insert(header::CONTENT_TYPE, value);
|
||||||
}
|
}
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.error = Some(e.into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a cookie.
|
/// Add a cookie to the response.
|
||||||
///
|
///
|
||||||
|
/// To send a "removal" cookie, call [`.make_removal()`](cookie::Cookie::make_removal) on the
|
||||||
|
/// given cookie. See [`HttpResponse::add_removal_cookie()`] to learn more.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// Send a new cookie:
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::{HttpResponse, cookie::Cookie};
|
/// use actix_web::{HttpResponse, cookie::Cookie};
|
||||||
///
|
///
|
||||||
/// HttpResponse::Ok()
|
/// let res = HttpResponse::Ok()
|
||||||
/// .cookie(
|
/// .cookie(
|
||||||
/// Cookie::build("name", "value")
|
/// Cookie::build("name", "value")
|
||||||
/// .domain("www.rust-lang.org")
|
/// .domain("www.rust-lang.org")
|
||||||
@ -241,48 +236,34 @@ impl HttpResponseBuilder {
|
|||||||
/// )
|
/// )
|
||||||
/// .finish();
|
/// .finish();
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "cookies")]
|
|
||||||
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
|
|
||||||
if self.cookies.is_none() {
|
|
||||||
let mut jar = CookieJar::new();
|
|
||||||
jar.add(cookie.into_owned());
|
|
||||||
self.cookies = Some(jar)
|
|
||||||
} else {
|
|
||||||
self.cookies.as_mut().unwrap().add(cookie.into_owned());
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove cookie.
|
|
||||||
///
|
|
||||||
/// A `Set-Cookie` header is added that will delete a cookie with the same name from the client.
|
|
||||||
///
|
///
|
||||||
|
/// Send a removal cookie:
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::{HttpRequest, HttpResponse, Responder};
|
/// use actix_web::{HttpResponse, cookie::Cookie};
|
||||||
///
|
///
|
||||||
/// async fn handler(req: HttpRequest) -> impl Responder {
|
/// // the name, domain and path match the cookie created in the previous example
|
||||||
/// let mut builder = HttpResponse::Ok();
|
/// let mut cookie = Cookie::build("name", "value-does-not-matter")
|
||||||
|
/// .domain("www.rust-lang.org")
|
||||||
|
/// .path("/")
|
||||||
|
/// .finish();
|
||||||
|
/// cookie.make_removal();
|
||||||
///
|
///
|
||||||
/// if let Some(ref cookie) = req.cookie("name") {
|
/// let res = HttpResponse::Ok()
|
||||||
/// builder.del_cookie(cookie);
|
/// .cookie(cookie)
|
||||||
/// }
|
/// .finish();
|
||||||
///
|
|
||||||
/// builder.finish()
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
pub fn del_cookie(&mut self, cookie: &Cookie<'_>) -> &mut Self {
|
pub fn cookie(&mut self, cookie: cookie::Cookie<'_>) -> &mut Self {
|
||||||
if self.cookies.is_none() {
|
match cookie.to_string().try_into_value() {
|
||||||
self.cookies = Some(CookieJar::new())
|
Ok(hdr_val) => self.append_header((header::SET_COOKIE, hdr_val)),
|
||||||
|
Err(err) => {
|
||||||
|
self.error = Some(err.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let jar = self.cookies.as_mut().unwrap();
|
|
||||||
let cookie = cookie.clone().into_owned();
|
|
||||||
jar.add_original(cookie.clone());
|
|
||||||
jar.remove(cookie);
|
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Responses extensions
|
/// Returns a reference to the response-local data/extensions container.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
||||||
self.res
|
self.res
|
||||||
@ -291,7 +272,8 @@ impl HttpResponseBuilder {
|
|||||||
.extensions()
|
.extensions()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutable reference to a the response's extensions
|
/// Returns a mutable reference to the response-local data/extensions container.
|
||||||
|
#[inline]
|
||||||
pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
|
pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
|
||||||
self.res
|
self.res
|
||||||
.as_mut()
|
.as_mut()
|
||||||
@ -301,6 +283,9 @@ impl HttpResponseBuilder {
|
|||||||
|
|
||||||
/// Set a body and build the `HttpResponse`.
|
/// Set a body and build the `HttpResponse`.
|
||||||
///
|
///
|
||||||
|
/// Unlike [`message_body`](Self::message_body), errors are converted into error
|
||||||
|
/// responses immediately.
|
||||||
|
///
|
||||||
/// `HttpResponseBuilder` can not be used after this call.
|
/// `HttpResponseBuilder` can not be used after this call.
|
||||||
pub fn body<B>(&mut self, body: B) -> HttpResponse<BoxBody>
|
pub fn body<B>(&mut self, body: B) -> HttpResponse<BoxBody>
|
||||||
where
|
where
|
||||||
@ -316,7 +301,7 @@ impl HttpResponseBuilder {
|
|||||||
///
|
///
|
||||||
/// `HttpResponseBuilder` can not be used after this call.
|
/// `HttpResponseBuilder` can not be used after this call.
|
||||||
pub fn message_body<B>(&mut self, body: B) -> Result<HttpResponse<B>, Error> {
|
pub fn message_body<B>(&mut self, body: B) -> Result<HttpResponse<B>, Error> {
|
||||||
if let Some(err) = self.err.take() {
|
if let Some(err) = self.error.take() {
|
||||||
return Err(err.into());
|
return Err(err.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,20 +311,7 @@ impl HttpResponseBuilder {
|
|||||||
.expect("cannot reuse response builder")
|
.expect("cannot reuse response builder")
|
||||||
.set_body(body);
|
.set_body(body);
|
||||||
|
|
||||||
#[allow(unused_mut)] // mut is only unused when cookies are disabled
|
Ok(HttpResponse::from(res))
|
||||||
let mut res = HttpResponse::from(res);
|
|
||||||
|
|
||||||
#[cfg(feature = "cookies")]
|
|
||||||
if let Some(ref jar) = self.cookies {
|
|
||||||
for cookie in jar.delta() {
|
|
||||||
match HeaderValue::from_str(&cookie.to_string()) {
|
|
||||||
Ok(val) => res.headers_mut().append(header::SET_COOKIE, val),
|
|
||||||
Err(err) => return Err(err.into()),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a streaming body and build the `HttpResponse`.
|
/// Set a streaming body and build the `HttpResponse`.
|
||||||
@ -388,15 +360,12 @@ impl HttpResponseBuilder {
|
|||||||
pub fn take(&mut self) -> Self {
|
pub fn take(&mut self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
res: self.res.take(),
|
res: self.res.take(),
|
||||||
err: self.err.take(),
|
error: self.error.take(),
|
||||||
#[cfg(feature = "cookies")]
|
|
||||||
cookies: self.cookies.take(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn inner(&mut self) -> Option<&mut ResponseHead> {
|
fn inner(&mut self) -> Option<&mut ResponseHead> {
|
||||||
if self.err.is_some() {
|
if self.error.is_some() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -435,10 +404,9 @@ impl Responder for HttpResponseBuilder {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use actix_http::body;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
body,
|
||||||
http::{
|
http::{
|
||||||
header::{self, HeaderValue, CONTENT_TYPE},
|
header::{self, HeaderValue, CONTENT_TYPE},
|
||||||
StatusCode,
|
StatusCode,
|
||||||
|
@ -27,7 +27,7 @@ use crate::{error::Error, HttpRequest, HttpResponseBuilder, Responder};
|
|||||||
/// An outgoing response.
|
/// An outgoing response.
|
||||||
pub struct HttpResponse<B = BoxBody> {
|
pub struct HttpResponse<B = BoxBody> {
|
||||||
res: Response<B>,
|
res: Response<B>,
|
||||||
pub(crate) error: Option<Error>,
|
error: Option<Error>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpResponse<BoxBody> {
|
impl HttpResponse<BoxBody> {
|
||||||
@ -116,18 +116,54 @@ impl<B> HttpResponse<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a cookie to this response
|
/// Add a cookie to this response.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns an error if the cookie results in a malformed `Set-Cookie` header.
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> {
|
pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> {
|
||||||
HeaderValue::from_str(&cookie.to_string())
|
HeaderValue::from_str(&cookie.to_string())
|
||||||
.map(|c| {
|
.map(|cookie| self.headers_mut().append(header::SET_COOKIE, cookie))
|
||||||
self.headers_mut().append(header::SET_COOKIE, c);
|
.map_err(Into::into)
|
||||||
})
|
|
||||||
.map_err(|e| e.into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove all cookies with the given name from this response. Returns
|
/// Add a "removal" cookie to the response that matches attributes of given cookie.
|
||||||
/// the number of cookies removed.
|
///
|
||||||
|
/// This will cause browsers/clients to remove stored cookies with this name.
|
||||||
|
///
|
||||||
|
/// The `Set-Cookie` header added to the response will have:
|
||||||
|
/// - name matching given cookie;
|
||||||
|
/// - domain matching given cookie;
|
||||||
|
/// - path matching given cookie;
|
||||||
|
/// - an empty value;
|
||||||
|
/// - a max-age of `0`;
|
||||||
|
/// - an expiration date far in the past.
|
||||||
|
///
|
||||||
|
/// If the cookie you're trying to remove has an explicit path or domain set, those attributes
|
||||||
|
/// will need to be included in the cookie passed in here.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns an error if the given name results in a malformed `Set-Cookie` header.
|
||||||
|
#[cfg(feature = "cookies")]
|
||||||
|
pub fn add_removal_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> {
|
||||||
|
let mut removal_cookie = cookie.to_owned();
|
||||||
|
removal_cookie.make_removal();
|
||||||
|
|
||||||
|
HeaderValue::from_str(&removal_cookie.to_string())
|
||||||
|
.map(|cookie| self.headers_mut().append(header::SET_COOKIE, cookie))
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove all cookies with the given name from this response.
|
||||||
|
///
|
||||||
|
/// Returns the number of cookies removed.
|
||||||
|
///
|
||||||
|
/// This method can _not_ cause a browser/client to delete any of its stored cookies. Its only
|
||||||
|
/// purpose is to delete cookies that were added to this response using [`add_cookie`]
|
||||||
|
/// and [`add_removal_cookie`]. Use [`add_removal_cookie`] to send a "removal" cookie.
|
||||||
|
///
|
||||||
|
/// [`add_cookie`]: Self::add_cookie
|
||||||
|
/// [`add_removal_cookie`]: Self::add_removal_cookie
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
pub fn del_cookie(&mut self, name: &str) -> usize {
|
pub fn del_cookie(&mut self, name: &str) -> usize {
|
||||||
let headers = self.headers_mut();
|
let headers = self.headers_mut();
|
||||||
@ -140,6 +176,7 @@ impl<B> HttpResponse<B> {
|
|||||||
headers.remove(header::SET_COOKIE);
|
headers.remove(header::SET_COOKIE);
|
||||||
|
|
||||||
let mut count: usize = 0;
|
let mut count: usize = 0;
|
||||||
|
|
||||||
for v in vals {
|
for v in vals {
|
||||||
if let Ok(s) = v.to_str() {
|
if let Ok(s) = v.to_str() {
|
||||||
if let Ok(c) = Cookie::parse_encoded(s) {
|
if let Ok(c) = Cookie::parse_encoded(s) {
|
||||||
@ -168,34 +205,37 @@ impl<B> HttpResponse<B> {
|
|||||||
self.res.keep_alive()
|
self.res.keep_alive()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Responses extensions
|
/// Returns reference to the response-local data/extensions container.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
||||||
self.res.extensions()
|
self.res.extensions()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutable reference to a the response's extensions
|
/// Returns reference to the response-local data/extensions container.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
|
pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
|
||||||
self.res.extensions_mut()
|
self.res.extensions_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get body of this response
|
/// Returns a reference to this response's body.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn body(&self) -> &B {
|
pub fn body(&self) -> &B {
|
||||||
self.res.body()
|
self.res.body()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a body
|
/// Sets new body.
|
||||||
pub fn set_body<B2>(self, body: B2) -> HttpResponse<B2> {
|
pub fn set_body<B2>(self, body: B2) -> HttpResponse<B2> {
|
||||||
HttpResponse {
|
HttpResponse {
|
||||||
res: self.res.set_body(body),
|
res: self.res.set_body(body),
|
||||||
error: None,
|
error: self.error,
|
||||||
// error: self.error, ??
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Split response and body
|
/// Returns split head and body.
|
||||||
|
///
|
||||||
|
/// # Implementation Notes
|
||||||
|
/// Due to internal performance optimizations, the first element of the returned tuple is an
|
||||||
|
/// `HttpResponse` as well but only contains the head of the response this was called on.
|
||||||
pub fn into_parts(self) -> (HttpResponse<()>, B) {
|
pub fn into_parts(self) -> (HttpResponse<()>, B) {
|
||||||
let (head, body) = self.res.into_parts();
|
let (head, body) = self.res.into_parts();
|
||||||
|
|
||||||
@ -208,7 +248,7 @@ impl<B> HttpResponse<B> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drop request's body
|
/// Drops body and returns new response.
|
||||||
pub fn drop_body(self) -> HttpResponse<()> {
|
pub fn drop_body(self) -> HttpResponse<()> {
|
||||||
HttpResponse {
|
HttpResponse {
|
||||||
res: self.res.drop_body(),
|
res: self.res.drop_body(),
|
||||||
@ -216,7 +256,9 @@ impl<B> HttpResponse<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a body and return previous body value
|
/// Map the current body type to another using a closure. Returns a new response.
|
||||||
|
///
|
||||||
|
/// Closure receives the response head and the current body type.
|
||||||
pub fn map_body<F, B2>(self, f: F) -> HttpResponse<B2>
|
pub fn map_body<F, B2>(self, f: F) -> HttpResponse<B2>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut ResponseHead, B) -> B2,
|
F: FnOnce(&mut ResponseHead, B) -> B2,
|
||||||
@ -365,3 +407,23 @@ mod tests {
|
|||||||
assert!(dbg.contains("HttpResponse"));
|
assert!(dbg.contains("HttpResponse"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[cfg(feature = "cookies")]
|
||||||
|
mod cookie_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn removal_cookies() {
|
||||||
|
let mut res = HttpResponse::Ok().finish();
|
||||||
|
let cookie = Cookie::new("foo", "");
|
||||||
|
res.add_removal_cookie(&cookie).unwrap();
|
||||||
|
let set_cookie_hdr = res.headers().get(header::SET_COOKIE).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
&set_cookie_hdr.as_bytes()[..25],
|
||||||
|
&b"foo=; Max-Age=0; Expires="[..],
|
||||||
|
"unexpected set-cookie value: {:?}",
|
||||||
|
set_cookie_hdr.to_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
83
src/rmap.rs
83
src/rmap.rs
@ -1,6 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
|
fmt::Write as _,
|
||||||
rc::{Rc, Weak},
|
rc::{Rc, Weak},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -10,12 +11,14 @@ use url::Url;
|
|||||||
|
|
||||||
use crate::{error::UrlGenerationError, request::HttpRequest};
|
use crate::{error::UrlGenerationError, request::HttpRequest};
|
||||||
|
|
||||||
|
const AVG_PATH_LEN: usize = 24;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ResourceMap {
|
pub struct ResourceMap {
|
||||||
pattern: ResourceDef,
|
pattern: ResourceDef,
|
||||||
|
|
||||||
/// Named resources within the tree or, for external resources,
|
/// Named resources within the tree or, for external resources, it points to isolated nodes
|
||||||
/// it points to isolated nodes outside the tree.
|
/// outside the tree.
|
||||||
named: AHashMap<String, Rc<ResourceMap>>,
|
named: AHashMap<String, Rc<ResourceMap>>,
|
||||||
|
|
||||||
parent: RefCell<Weak<ResourceMap>>,
|
parent: RefCell<Weak<ResourceMap>>,
|
||||||
@ -35,6 +38,35 @@ impl ResourceMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format resource map as tree structure (unfinished).
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn tree(&self) -> String {
|
||||||
|
let mut buf = String::new();
|
||||||
|
self._tree(&mut buf, 0);
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn _tree(&self, buf: &mut String, level: usize) {
|
||||||
|
if let Some(children) = &self.nodes {
|
||||||
|
for child in children {
|
||||||
|
writeln!(
|
||||||
|
buf,
|
||||||
|
"{}{} {}",
|
||||||
|
"--".repeat(level),
|
||||||
|
child.pattern.pattern().unwrap(),
|
||||||
|
child
|
||||||
|
.pattern
|
||||||
|
.name()
|
||||||
|
.map(|name| format!("({})", name))
|
||||||
|
.unwrap_or_else(|| "".to_owned())
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
ResourceMap::_tree(child, buf, level + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds a (possibly nested) resource.
|
/// Adds a (possibly nested) resource.
|
||||||
///
|
///
|
||||||
/// To add a non-prefix pattern, `nested` must be `None`.
|
/// To add a non-prefix pattern, `nested` must be `None`.
|
||||||
@ -44,7 +76,11 @@ impl ResourceMap {
|
|||||||
pattern.set_id(self.nodes.as_ref().unwrap().len() as u16);
|
pattern.set_id(self.nodes.as_ref().unwrap().len() as u16);
|
||||||
|
|
||||||
if let Some(new_node) = nested {
|
if let Some(new_node) = nested {
|
||||||
assert_eq!(&new_node.pattern, pattern, "`patern` and `nested` mismatch");
|
debug_assert_eq!(
|
||||||
|
&new_node.pattern, pattern,
|
||||||
|
"`pattern` and `nested` mismatch"
|
||||||
|
);
|
||||||
|
// parents absorb references to the named resources of children
|
||||||
self.named.extend(new_node.named.clone().into_iter());
|
self.named.extend(new_node.named.clone().into_iter());
|
||||||
self.nodes.as_mut().unwrap().push(new_node);
|
self.nodes.as_mut().unwrap().push(new_node);
|
||||||
} else {
|
} else {
|
||||||
@ -64,7 +100,7 @@ impl ResourceMap {
|
|||||||
None => false,
|
None => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Don't add external resources to the tree
|
// don't add external resources to the tree
|
||||||
if !is_external {
|
if !is_external {
|
||||||
self.nodes.as_mut().unwrap().push(new_node);
|
self.nodes.as_mut().unwrap().push(new_node);
|
||||||
}
|
}
|
||||||
@ -78,7 +114,7 @@ impl ResourceMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate url for named resource
|
/// Generate URL for named resource.
|
||||||
///
|
///
|
||||||
/// Check [`HttpRequest::url_for`] for detailed information.
|
/// Check [`HttpRequest::url_for`] for detailed information.
|
||||||
pub fn url_for<U, I>(
|
pub fn url_for<U, I>(
|
||||||
@ -97,7 +133,7 @@ impl ResourceMap {
|
|||||||
.named
|
.named
|
||||||
.get(name)
|
.get(name)
|
||||||
.ok_or(UrlGenerationError::ResourceNotFound)?
|
.ok_or(UrlGenerationError::ResourceNotFound)?
|
||||||
.root_rmap_fn(String::with_capacity(24), |mut acc, node| {
|
.root_rmap_fn(String::with_capacity(AVG_PATH_LEN), |mut acc, node| {
|
||||||
node.pattern
|
node.pattern
|
||||||
.resource_path_from_iter(&mut acc, &mut elements)
|
.resource_path_from_iter(&mut acc, &mut elements)
|
||||||
.then(|| acc)
|
.then(|| acc)
|
||||||
@ -128,6 +164,7 @@ impl ResourceMap {
|
|||||||
Ok(url)
|
Ok(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if there is a resource that would match `path`.
|
||||||
pub fn has_resource(&self, path: &str) -> bool {
|
pub fn has_resource(&self, path: &str) -> bool {
|
||||||
self.find_matching_node(path).is_some()
|
self.find_matching_node(path).is_some()
|
||||||
}
|
}
|
||||||
@ -142,9 +179,10 @@ impl ResourceMap {
|
|||||||
/// is possible.
|
/// is possible.
|
||||||
pub fn match_pattern(&self, path: &str) -> Option<String> {
|
pub fn match_pattern(&self, path: &str) -> Option<String> {
|
||||||
self.find_matching_node(path)?.root_rmap_fn(
|
self.find_matching_node(path)?.root_rmap_fn(
|
||||||
String::with_capacity(24),
|
String::with_capacity(AVG_PATH_LEN),
|
||||||
|mut acc, node| {
|
|mut acc, node| {
|
||||||
acc.push_str(node.pattern.pattern()?);
|
let pattern = node.pattern.pattern()?;
|
||||||
|
acc.push_str(pattern);
|
||||||
Some(acc)
|
Some(acc)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -490,4 +528,33 @@ mod tests {
|
|||||||
"https://duck.com/abcd"
|
"https://duck.com/abcd"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn url_for_override_within_map() {
|
||||||
|
let mut root = ResourceMap::new(ResourceDef::prefix(""));
|
||||||
|
|
||||||
|
let mut foo_rdef = ResourceDef::prefix("/foo");
|
||||||
|
let mut foo_map = ResourceMap::new(foo_rdef.clone());
|
||||||
|
let mut nested_rdef = ResourceDef::new("/nested");
|
||||||
|
nested_rdef.set_name("nested");
|
||||||
|
foo_map.add(&mut nested_rdef, None);
|
||||||
|
root.add(&mut foo_rdef, Some(Rc::new(foo_map)));
|
||||||
|
|
||||||
|
let mut foo_rdef = ResourceDef::prefix("/bar");
|
||||||
|
let mut foo_map = ResourceMap::new(foo_rdef.clone());
|
||||||
|
let mut nested_rdef = ResourceDef::new("/nested");
|
||||||
|
nested_rdef.set_name("nested");
|
||||||
|
foo_map.add(&mut nested_rdef, None);
|
||||||
|
root.add(&mut foo_rdef, Some(Rc::new(foo_map)));
|
||||||
|
|
||||||
|
let rmap = Rc::new(root);
|
||||||
|
ResourceMap::finish(&rmap);
|
||||||
|
|
||||||
|
let req = crate::test::TestRequest::default().to_http_request();
|
||||||
|
|
||||||
|
let url = rmap.url_for(&req, "nested", &[""; 0]).unwrap().to_string();
|
||||||
|
assert_eq!(url, "http://localhost:8080/bar/nested");
|
||||||
|
|
||||||
|
assert!(rmap.url_for(&req, "missing", &["u123"]).is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
105
src/service.rs
105
src/service.rs
@ -97,12 +97,18 @@ impl ServiceRequest {
|
|||||||
|
|
||||||
/// Construct request from parts.
|
/// Construct request from parts.
|
||||||
pub fn from_parts(req: HttpRequest, payload: Payload) -> Self {
|
pub fn from_parts(req: HttpRequest, payload: Payload) -> Self {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
if Rc::strong_count(&req.inner) > 1 {
|
||||||
|
log::warn!("Cloning an `HttpRequest` might cause panics.");
|
||||||
|
}
|
||||||
|
|
||||||
Self { req, payload }
|
Self { req, payload }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct request from request.
|
/// Construct request from request.
|
||||||
///
|
///
|
||||||
/// The returned `ServiceRequest` would have no payload.
|
/// The returned `ServiceRequest` would have no payload.
|
||||||
|
#[inline]
|
||||||
pub fn from_request(req: HttpRequest) -> Self {
|
pub fn from_request(req: HttpRequest) -> Self {
|
||||||
ServiceRequest {
|
ServiceRequest {
|
||||||
req,
|
req,
|
||||||
@ -197,9 +203,9 @@ impl ServiceRequest {
|
|||||||
self.req.connection_info()
|
self.req.connection_info()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to the Path parameters.
|
/// Returns a reference to the Path parameters.
|
||||||
///
|
///
|
||||||
/// Params is a container for url parameters.
|
/// Params is a container for URL parameters.
|
||||||
/// A variable segment is specified in the form `{identifier}`,
|
/// A variable segment is specified in the form `{identifier}`,
|
||||||
/// where the identifier can be used later in a request handler to
|
/// where the identifier can be used later in a request handler to
|
||||||
/// access the matched value for that segment.
|
/// access the matched value for that segment.
|
||||||
@ -208,6 +214,12 @@ impl ServiceRequest {
|
|||||||
self.req.match_info()
|
self.req.match_info()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the Path parameters.
|
||||||
|
#[inline]
|
||||||
|
pub fn match_info_mut(&mut self) -> &mut Path<Url> {
|
||||||
|
self.req.match_info_mut()
|
||||||
|
}
|
||||||
|
|
||||||
/// Counterpart to [`HttpRequest::match_name`].
|
/// Counterpart to [`HttpRequest::match_name`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn match_name(&self) -> Option<&str> {
|
pub fn match_name(&self) -> Option<&str> {
|
||||||
@ -220,12 +232,6 @@ impl ServiceRequest {
|
|||||||
self.req.match_pattern()
|
self.req.match_pattern()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to the Path parameters.
|
|
||||||
#[inline]
|
|
||||||
pub fn match_info_mut(&mut self) -> &mut Path<Url> {
|
|
||||||
self.req.match_info_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a reference to a `ResourceMap` of current application.
|
/// Get a reference to a `ResourceMap` of current application.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn resource_map(&self) -> &ResourceMap {
|
pub fn resource_map(&self) -> &ResourceMap {
|
||||||
@ -256,18 +262,6 @@ impl ServiceRequest {
|
|||||||
self.req.conn_data()
|
self.req.conn_data()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Counterpart to [`HttpRequest::req_data`].
|
|
||||||
#[inline]
|
|
||||||
pub fn req_data(&self) -> Ref<'_, Extensions> {
|
|
||||||
self.req.req_data()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Counterpart to [`HttpRequest::req_data_mut`].
|
|
||||||
#[inline]
|
|
||||||
pub fn req_data_mut(&self) -> RefMut<'_, Extensions> {
|
|
||||||
self.req.req_data_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
|
pub fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
|
||||||
@ -320,18 +314,15 @@ impl HttpMessage for ServiceRequest {
|
|||||||
type Stream = BoxedPayloadStream;
|
type Stream = BoxedPayloadStream;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Returns Request's headers.
|
|
||||||
fn headers(&self) -> &HeaderMap {
|
fn headers(&self) -> &HeaderMap {
|
||||||
&self.head().headers
|
&self.head().headers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request extensions
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn extensions(&self) -> Ref<'_, Extensions> {
|
fn extensions(&self) -> Ref<'_, Extensions> {
|
||||||
self.req.extensions()
|
self.req.extensions()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutable reference to a the request's extensions
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
||||||
self.req.extensions_mut()
|
self.req.extensions_mut()
|
||||||
@ -398,32 +389,32 @@ impl<B> ServiceResponse<B> {
|
|||||||
ServiceResponse::new(self.request, response)
|
ServiceResponse::new(self.request, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get reference to original request
|
/// Returns reference to original request.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn request(&self) -> &HttpRequest {
|
pub fn request(&self) -> &HttpRequest {
|
||||||
&self.request
|
&self.request
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get reference to response
|
/// Returns reference to response.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn response(&self) -> &HttpResponse<B> {
|
pub fn response(&self) -> &HttpResponse<B> {
|
||||||
&self.response
|
&self.response
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get mutable reference to response
|
/// Returns mutable reference to response.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn response_mut(&mut self) -> &mut HttpResponse<B> {
|
pub fn response_mut(&mut self) -> &mut HttpResponse<B> {
|
||||||
&mut self.response
|
&mut self.response
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the response status code
|
/// Returns response status code.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn status(&self) -> StatusCode {
|
pub fn status(&self) -> StatusCode {
|
||||||
self.response.status()
|
self.response.status()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Returns response's headers.
|
/// Returns response's headers.
|
||||||
|
#[inline]
|
||||||
pub fn headers(&self) -> &HeaderMap {
|
pub fn headers(&self) -> &HeaderMap {
|
||||||
self.response.headers()
|
self.response.headers()
|
||||||
}
|
}
|
||||||
@ -440,13 +431,9 @@ impl<B> ServiceResponse<B> {
|
|||||||
(self.request, self.response)
|
(self.request, self.response)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract response body
|
/// Map the current body type to another using a closure. Returns a new response.
|
||||||
#[inline]
|
///
|
||||||
pub fn into_body(self) -> B {
|
/// Closure receives the response head and the current body type.
|
||||||
self.response.into_body()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a new body
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn map_body<F, B2>(self, f: F) -> ServiceResponse<B2>
|
pub fn map_body<F, B2>(self, f: F) -> ServiceResponse<B2>
|
||||||
where
|
where
|
||||||
@ -477,6 +464,12 @@ impl<B> ServiceResponse<B> {
|
|||||||
{
|
{
|
||||||
self.map_body(|_, body| body.boxed())
|
self.map_body(|_, body| body.boxed())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes the response and returns its body.
|
||||||
|
#[inline]
|
||||||
|
pub fn into_body(self) -> B {
|
||||||
|
self.response.into_body()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B> From<ServiceResponse<B>> for HttpResponse<B> {
|
impl<B> From<ServiceResponse<B>> for HttpResponse<B> {
|
||||||
@ -546,14 +539,12 @@ impl WebService {
|
|||||||
/// Ok(req.into_response(HttpResponse::Ok().finish()))
|
/// Ok(req.into_response(HttpResponse::Ok().finish()))
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// let app = App::new()
|
||||||
/// let app = App::new()
|
/// .service(
|
||||||
/// .service(
|
/// web::service("/app")
|
||||||
/// web::service("/app")
|
/// .guard(guard::Header("content-type", "text/plain"))
|
||||||
/// .guard(guard::Header("content-type", "text/plain"))
|
/// .finish(index)
|
||||||
/// .finish(index)
|
/// );
|
||||||
/// );
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
|
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
|
||||||
self.guards.push(Box::new(guard));
|
self.guards.push(Box::new(guard));
|
||||||
@ -677,7 +668,7 @@ service_tuple! { A B C D E F G H I J K L }
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::test::{init_service, TestRequest};
|
use crate::test::{self, init_service, TestRequest};
|
||||||
use crate::{guard, http, web, App, HttpResponse};
|
use crate::{guard, http, web, App, HttpResponse};
|
||||||
use actix_service::Service;
|
use actix_service::Service;
|
||||||
use actix_utils::future::ok;
|
use actix_utils::future::ok;
|
||||||
@ -824,4 +815,30 @@ mod tests {
|
|||||||
let resp = srv.call(req).await.unwrap();
|
let resp = srv.call(req).await.unwrap();
|
||||||
assert_eq!(resp.status(), http::StatusCode::OK);
|
assert_eq!(resp.status(), http::StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
#[should_panic(expected = "called `Option::unwrap()` on a `None` value")]
|
||||||
|
async fn cloning_request_panics() {
|
||||||
|
async fn index(_name: web::Path<(String,)>) -> &'static str {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
let app = test::init_service(
|
||||||
|
App::new()
|
||||||
|
.wrap_fn(|req, svc| {
|
||||||
|
let (req, pl) = req.into_parts();
|
||||||
|
let _req2 = req.clone();
|
||||||
|
let req = ServiceRequest::from_parts(req, pl);
|
||||||
|
svc.call(req)
|
||||||
|
})
|
||||||
|
.route("/", web::get().to(|| async { "" }))
|
||||||
|
.service(
|
||||||
|
web::resource("/resource1/{name}/index.html").route(web::get().to(index)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = test::TestRequest::default().to_request();
|
||||||
|
let _res = test::call_service(&app, req).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
cookie::{Cookie, CookieBuilder},
|
cookie::Cookie,
|
||||||
http::{header, StatusCode},
|
http::{header, StatusCode},
|
||||||
middleware::{Compress, NormalizePath, TrailingSlash},
|
middleware::{Compress, NormalizePath, TrailingSlash},
|
||||||
web, App, Error, HttpResponse,
|
web, App, Error, HttpResponse,
|
||||||
@ -773,7 +773,7 @@ async fn test_server_cookies() {
|
|||||||
App::new().default_service(web::to(|| {
|
App::new().default_service(web::to(|| {
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.cookie(
|
.cookie(
|
||||||
CookieBuilder::new("first", "first_value")
|
Cookie::build("first", "first_value")
|
||||||
.http_only(true)
|
.http_only(true)
|
||||||
.finish(),
|
.finish(),
|
||||||
)
|
)
|
||||||
@ -787,13 +787,13 @@ async fn test_server_cookies() {
|
|||||||
let res = req.send().await.unwrap();
|
let res = req.send().await.unwrap();
|
||||||
assert!(res.status().is_success());
|
assert!(res.status().is_success());
|
||||||
|
|
||||||
let first_cookie = CookieBuilder::new("first", "first_value")
|
let first_cookie = Cookie::build("first", "first_value")
|
||||||
.http_only(true)
|
.http_only(true)
|
||||||
.finish();
|
.finish();
|
||||||
let second_cookie = Cookie::new("second", "second_value");
|
let second_cookie = Cookie::new("second", "first_value");
|
||||||
|
|
||||||
let cookies = res.cookies().expect("To have cookies");
|
let cookies = res.cookies().expect("To have cookies");
|
||||||
assert_eq!(cookies.len(), 2);
|
assert_eq!(cookies.len(), 3);
|
||||||
if cookies[0] == first_cookie {
|
if cookies[0] == first_cookie {
|
||||||
assert_eq!(cookies[1], second_cookie);
|
assert_eq!(cookies[1], second_cookie);
|
||||||
} else {
|
} else {
|
||||||
@ -809,7 +809,7 @@ async fn test_server_cookies() {
|
|||||||
.get_all(http::header::SET_COOKIE)
|
.get_all(http::header::SET_COOKIE)
|
||||||
.map(|header| header.to_str().expect("To str").to_string())
|
.map(|header| header.to_str().expect("To str").to_string())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
assert_eq!(cookies.len(), 2);
|
assert_eq!(cookies.len(), 3);
|
||||||
if cookies[0] == first_cookie {
|
if cookies[0] == first_cookie {
|
||||||
assert_eq!(cookies[1], second_cookie);
|
assert_eq!(cookies[1], second_cookie);
|
||||||
} else {
|
} else {
|
||||||
|
@ -41,16 +41,22 @@ pub mod deflate {
|
|||||||
|
|
||||||
pub mod brotli {
|
pub mod brotli {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ::brotli2::{read::BrotliDecoder, write::BrotliEncoder};
|
use ::brotli::{reader::Decompressor as BrotliDecoder, CompressorWriter as BrotliEncoder};
|
||||||
|
|
||||||
pub fn encode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
|
pub fn encode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
|
||||||
let mut encoder = BrotliEncoder::new(Vec::new(), 3);
|
let mut encoder = BrotliEncoder::new(
|
||||||
|
Vec::new(),
|
||||||
|
8 * 1024, // 32 KiB buffer
|
||||||
|
3, // BROTLI_PARAM_QUALITY
|
||||||
|
22, // BROTLI_PARAM_LGWIN
|
||||||
|
);
|
||||||
encoder.write_all(bytes.as_ref()).unwrap();
|
encoder.write_all(bytes.as_ref()).unwrap();
|
||||||
encoder.finish().unwrap()
|
encoder.flush().unwrap();
|
||||||
|
encoder.into_inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
|
pub fn decode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
|
||||||
let mut decoder = BrotliDecoder::new(bytes.as_ref());
|
let mut decoder = BrotliDecoder::new(bytes.as_ref(), 8_096);
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
decoder.read_to_end(&mut buf).unwrap();
|
decoder.read_to_end(&mut buf).unwrap();
|
||||||
buf
|
buf
|
||||||
|
Reference in New Issue
Block a user