mirror of
https://github.com/fafhrd91/actix-web
synced 2025-08-07 23:34:26 +02:00
Compare commits
57 Commits
v1.0.0-alp
...
http-v0.1.
Author | SHA1 | Date | |
---|---|---|---|
|
5f6a1a8249 | ||
|
5d531989e7 | ||
|
3532602299 | ||
|
48bee55087 | ||
|
d00c9bb844 | ||
|
895e409d57 | ||
|
f0789aad05 | ||
|
7e480ab2f7 | ||
|
891f857547 | ||
|
01b1350dcc | ||
|
5e4e95fb0a | ||
|
9f421b81b8 | ||
|
6decfdda1f | ||
|
fc9b14a933 | ||
|
7292d0b696 | ||
|
a3844c1bfd | ||
|
791f22bbc8 | ||
|
1e7f97a111 | ||
|
bc40f5ae40 | ||
|
3504a8fc0a | ||
|
bfe0df5ab0 | ||
|
ed94df189f | ||
|
aa255298ef | ||
|
da86b6e062 | ||
|
75e340137d | ||
|
e659e09e29 | ||
|
163ca89cf4 | ||
|
85b598a614 | ||
|
b64851c5ec | ||
|
cc8420377e | ||
|
5740f1e63a | ||
|
c943e95812 | ||
|
4c0ebd55d3 | ||
|
e7ec77aa81 | ||
|
ddfd7523f7 | ||
|
2986077a28 | ||
|
3744957804 | ||
|
420d3064c5 | ||
|
eb4f6b74fb | ||
|
a116c4c2c7 | ||
|
7f674febb1 | ||
|
14252f5ef2 | ||
|
7a28b32f6d | ||
|
09cdf1e302 | ||
|
1eebd47072 | ||
|
002c41a7ca | ||
|
ab4fda6084 | ||
|
f9078d41cd | ||
|
4cc2b38059 | ||
|
d7040dc303 | ||
|
6bc1a0c76b | ||
|
5bd5651faa | ||
|
32ac159ba2 | ||
|
ee33f52736 | ||
|
4f30fa9d46 | ||
|
043f6e77ae | ||
|
48518df883 |
@@ -42,7 +42,7 @@ script:
|
||||
after_success:
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
||||
cargo doc --all-features &&
|
||||
cargo doc --no-deps --all-features &&
|
||||
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
|
||||
git clone https://github.com/davisp/ghp-import.git &&
|
||||
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
|
||||
|
52
CHANGES.md
52
CHANGES.md
@@ -1,5 +1,57 @@
|
||||
# Changes
|
||||
|
||||
### Added
|
||||
|
||||
* Add helper functions for reading response body `test::read_body()`
|
||||
|
||||
* Added support for `remainder match` (i.e "/path/{tail}*")
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* `.to_async()` handler can return `Responder` type #792
|
||||
|
||||
|
||||
## [1.0.0-beta.1] - 2019-04-20
|
||||
|
||||
### Added
|
||||
|
||||
* Add helper functions for reading test response body,
|
||||
`test::read_response()` and test::read_response_json()`
|
||||
|
||||
* Add `.peer_addr()` #744
|
||||
|
||||
* Add `NormalizePath` middleware
|
||||
|
||||
### Changed
|
||||
|
||||
* Rename `RouterConfig` to `ServiceConfig`
|
||||
|
||||
* Rename `test::call_success` to `test::call_service`
|
||||
|
||||
* Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts.
|
||||
|
||||
* `CookieIdentityPolicy::max_age()` accepts value in seconds
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed `TestRequest::app_data()`
|
||||
|
||||
|
||||
## [1.0.0-alpha.6] - 2019-04-14
|
||||
|
||||
### Changed
|
||||
|
||||
* Allow to use any service as default service.
|
||||
|
||||
* Remove generic type for request payload, always use default.
|
||||
|
||||
* Removed `Decompress` middleware. Bytes, String, Json, Form extractors
|
||||
automatically decompress payload.
|
||||
|
||||
* Make extractor config type explicit. Add `FromRequest::Config` associated type.
|
||||
|
||||
|
||||
## [1.0.0-alpha.5] - 2019-04-12
|
||||
|
||||
### Added
|
||||
|
29
Cargo.toml
29
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "1.0.0-alpha.5"
|
||||
version = "1.0.0-beta.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||
readme = "README.md"
|
||||
@@ -15,6 +15,9 @@ license = "MIT/Apache-2.0"
|
||||
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"]
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "actix/actix-web", branch = "master" }
|
||||
codecov = { repository = "actix/actix-web", branch = "master", service = "github" }
|
||||
@@ -37,9 +40,6 @@ members = [
|
||||
"test-server",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"]
|
||||
|
||||
[features]
|
||||
default = ["brotli", "flate2-zlib", "secure-cookies", "client"]
|
||||
|
||||
@@ -68,20 +68,20 @@ rust-tls = ["rustls", "actix-server/rust-tls"]
|
||||
actix-codec = "0.1.2"
|
||||
actix-service = "0.3.6"
|
||||
actix-utils = "0.3.4"
|
||||
actix-router = "0.1.2"
|
||||
actix-router = "0.1.3"
|
||||
actix-rt = "0.2.2"
|
||||
actix-web-codegen = "0.1.0-alpha.1"
|
||||
actix-http = { version = "0.1.0-alpha.5", features=["fail"] }
|
||||
actix-server = "0.4.2"
|
||||
actix-server-config = "0.1.0"
|
||||
actix-web-codegen = "0.1.0-beta.1"
|
||||
actix-http = { version = "0.1.1", features=["fail"] }
|
||||
actix-server = "0.4.3"
|
||||
actix-server-config = "0.1.1"
|
||||
actix-threadpool = "0.1.0"
|
||||
awc = { version = "0.1.0-alpha.5", optional = true }
|
||||
awc = { version = "0.1.1", optional = true }
|
||||
|
||||
bytes = "0.4"
|
||||
derive_more = "0.14"
|
||||
encoding = "0.2"
|
||||
futures = "0.1"
|
||||
hashbrown = "0.1.8"
|
||||
hashbrown = "0.2.2"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
net2 = "0.2.33"
|
||||
@@ -90,7 +90,7 @@ regex = "1.0"
|
||||
serde = { version = "1.0", features=["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_urlencoded = "0.5.3"
|
||||
time = "0.1"
|
||||
time = "0.1.42"
|
||||
url = { version="1.7", features=["query_encoding"] }
|
||||
|
||||
# ssl support
|
||||
@@ -98,8 +98,9 @@ openssl = { version="0.10", optional = true }
|
||||
rustls = { version = "^0.15", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-http = { version = "0.1.0-alpha.5", features=["ssl", "brotli", "flate2-zlib"] }
|
||||
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
|
||||
actix-http = { version = "0.1.1", features=["ssl", "brotli", "flate2-zlib"] }
|
||||
actix-http-test = { version = "0.1.0", features=["ssl"] }
|
||||
actix-files = { version = "0.1.0-beta.1" }
|
||||
rand = "0.6"
|
||||
env_logger = "0.6"
|
||||
serde_derive = "1.0"
|
||||
|
52
MIGRATION.md
52
MIGRATION.md
@@ -1,7 +1,7 @@
|
||||
## 1.0
|
||||
|
||||
* Resource registration. 1.0 version uses generalized resource
|
||||
registration via `.service()` method.
|
||||
registration via `.service()` method.
|
||||
|
||||
instead of
|
||||
|
||||
@@ -44,9 +44,41 @@ registration via `.service()` method.
|
||||
);
|
||||
```
|
||||
|
||||
* `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`.
|
||||
|
||||
instead of
|
||||
|
||||
```rust
|
||||
App.new().resource("/welcome", |r| r.with(welcome))
|
||||
```
|
||||
|
||||
use `.to()` or `.to_async()` methods
|
||||
|
||||
```rust
|
||||
App.new().service(web::resource("/welcome").to(welcome))
|
||||
```
|
||||
|
||||
* Passing arguments to handler with extractors, multiple arguments are allowed
|
||||
|
||||
instead of
|
||||
|
||||
```rust
|
||||
fn welcome((body, req): (Bytes, HttpRequest)) -> ... {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
use multiple arguments
|
||||
|
||||
```rust
|
||||
fn welcome(body: Bytes, req: HttpRequest) -> ... {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
* `.f()`, `.a()` and `.h()` handler registration methods have been removed.
|
||||
Use `.to()` for handlers and `.to_async()` for async handlers. Handler function
|
||||
must use extractors.
|
||||
Use `.to()` for handlers and `.to_async()` for async handlers. Handler function
|
||||
must use extractors.
|
||||
|
||||
instead of
|
||||
|
||||
@@ -61,8 +93,8 @@ must use extractors.
|
||||
```
|
||||
|
||||
* `State` is now `Data`. You register Data during the App initialization process
|
||||
and then access it from handlers either using a Data extractor or using
|
||||
HttpRequest's api.
|
||||
and then access it from handlers either using a Data extractor or using
|
||||
HttpRequest's api.
|
||||
|
||||
instead of
|
||||
|
||||
@@ -96,7 +128,7 @@ HttpRequest's api.
|
||||
```
|
||||
|
||||
|
||||
* AsyncResponder is removed.
|
||||
* AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type.
|
||||
|
||||
instead of
|
||||
|
||||
@@ -150,6 +182,8 @@ HttpRequest's api.
|
||||
}
|
||||
```
|
||||
|
||||
* `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type
|
||||
|
||||
* StaticFiles and NamedFile has been move to separate create.
|
||||
|
||||
instead of `use actix_web::fs::StaticFile`
|
||||
@@ -166,10 +200,10 @@ HttpRequest's api.
|
||||
|
||||
use `use actix_multipart::Multipart`
|
||||
|
||||
* Request/response compression/decompression is not enabled by default.
|
||||
To enable use `App::enable_encoding()` method.
|
||||
* Response compression is not enabled by default.
|
||||
To enable, use `Compress` middleware, `App::new().wrap(Compress::default())`.
|
||||
|
||||
* `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type
|
||||
* Session middleware moved to actix-session crate
|
||||
|
||||
* Actors support have been moved to `actix-web-actors` crate
|
||||
|
||||
|
@@ -18,8 +18,8 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
||||
## Documentation & community resources
|
||||
|
||||
* [User Guide](https://actix.rs/docs/)
|
||||
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
|
||||
* [API Documentation (0.7 Release)](https://docs.rs/actix-web/0.7.19/actix_web/)
|
||||
* [API Documentation (1.0)](https://docs.rs/actix-web/)
|
||||
* [API Documentation (0.7)](https://docs.rs/actix-web/0.7.19/actix_web/)
|
||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
||||
* Minimum supported Rust version: 1.32 or later
|
||||
@@ -36,8 +36,7 @@ fn index(info: web::Path<(u32, String)>) -> impl Responder {
|
||||
fn main() -> std::io::Result<()> {
|
||||
HttpServer::new(
|
||||
|| App::new().service(
|
||||
web::resource("/{id}/{name}/index.html")
|
||||
.route(web::get().to(index))))
|
||||
web::resource("/{id}/{name}/index.html").to(index)))
|
||||
.bind("127.0.0.1:8080")?
|
||||
.run()
|
||||
}
|
||||
|
@@ -1,15 +1,21 @@
|
||||
# Changes
|
||||
|
||||
## [0.1.0-beta.1] - 2019-04-20
|
||||
|
||||
* Update actix-web to beta.1
|
||||
|
||||
## [0.1.0-alpha.6] - 2019-04-14
|
||||
|
||||
* Update actix-web to alpha6
|
||||
|
||||
## [0.1.0-alpha.4] - 2019-04-08
|
||||
|
||||
* Update actix-web to alpha4
|
||||
|
||||
|
||||
## [0.1.0-alpha.2] - 2019-04-02
|
||||
|
||||
* Add default handler support
|
||||
|
||||
|
||||
## [0.1.0-alpha.1] - 2019-03-28
|
||||
|
||||
* Initial impl
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-files"
|
||||
version = "0.1.0-alpha.4"
|
||||
version = "0.1.0-beta.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Static files support for actix web."
|
||||
readme = "README.md"
|
||||
@@ -18,7 +18,7 @@ name = "actix_files"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-web = "1.0.0-alpha.5"
|
||||
actix-web = "1.0.0-beta.1"
|
||||
actix-service = "0.3.4"
|
||||
bitflags = "1"
|
||||
bytes = "0.4"
|
||||
@@ -31,4 +31,4 @@ percent-encoding = "1.0"
|
||||
v_htmlescape = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-web = { version = "1.0.0-alpha.5", features=["ssl"] }
|
||||
actix-web = { version = "1.0.0-beta.1", features=["ssl"] }
|
||||
|
@@ -10,7 +10,7 @@ use std::{cmp, io};
|
||||
use actix_service::boxed::{self, BoxedNewService, BoxedService};
|
||||
use actix_service::{IntoNewService, NewService, Service};
|
||||
use actix_web::dev::{
|
||||
HttpServiceFactory, Payload, ResourceDef, ServiceConfig, ServiceRequest,
|
||||
AppService, HttpServiceFactory, Payload, ResourceDef, ServiceRequest,
|
||||
ServiceResponse,
|
||||
};
|
||||
use actix_web::error::{BlockingError, Error, ErrorInternalServerError};
|
||||
@@ -32,9 +32,8 @@ use self::error::{FilesError, UriSegmentError};
|
||||
pub use crate::named::NamedFile;
|
||||
pub use crate::range::HttpRange;
|
||||
|
||||
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>;
|
||||
type HttpNewService<P> =
|
||||
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
|
||||
type HttpService = BoxedService<ServiceRequest, ServiceResponse, Error>;
|
||||
type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||
|
||||
/// Return the MIME type associated with a filename extension (case-insensitive).
|
||||
/// If `ext` is empty or no associated type for the extension was found, returns
|
||||
@@ -225,18 +224,18 @@ type MimeOverride = Fn(&mime::Name) -> DispositionType;
|
||||
/// .service(fs::Files::new("/static", "."));
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Files<S> {
|
||||
pub struct Files {
|
||||
path: String,
|
||||
directory: PathBuf,
|
||||
index: Option<String>,
|
||||
show_index: bool,
|
||||
default: Rc<RefCell<Option<Rc<HttpNewService<S>>>>>,
|
||||
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
|
||||
renderer: Rc<DirectoryRenderer>,
|
||||
mime_override: Option<Rc<MimeOverride>>,
|
||||
file_flags: named::Flags,
|
||||
}
|
||||
|
||||
impl<S> Clone for Files<S> {
|
||||
impl Clone for Files {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
directory: self.directory.clone(),
|
||||
@@ -251,13 +250,13 @@ impl<S> Clone for Files<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> Files<S> {
|
||||
impl Files {
|
||||
/// Create new `Files` instance for specified base directory.
|
||||
///
|
||||
/// `File` uses `ThreadPool` for blocking filesystem operations.
|
||||
/// By default pool with 5x threads of available cpus is used.
|
||||
/// Pool size can be changed by setting ACTIX_CPU_POOL environment variable.
|
||||
pub fn new<T: Into<PathBuf>>(path: &str, dir: T) -> Files<S> {
|
||||
pub fn new<T: Into<PathBuf>>(path: &str, dir: T) -> Files {
|
||||
let dir = dir.into().canonicalize().unwrap_or_else(|_| PathBuf::new());
|
||||
if !dir.is_dir() {
|
||||
log::error!("Specified path is not a directory");
|
||||
@@ -335,7 +334,7 @@ impl<S: 'static> Files<S> {
|
||||
where
|
||||
F: IntoNewService<U>,
|
||||
U: NewService<
|
||||
Request = ServiceRequest<S>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
> + 'static,
|
||||
@@ -349,11 +348,8 @@ impl<S: 'static> Files<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> HttpServiceFactory<P> for Files<P>
|
||||
where
|
||||
P: 'static,
|
||||
{
|
||||
fn register(self, config: &mut ServiceConfig<P>) {
|
||||
impl HttpServiceFactory for Files {
|
||||
fn register(self, config: &mut AppService) {
|
||||
if self.default.borrow().is_none() {
|
||||
*self.default.borrow_mut() = Some(config.default_service());
|
||||
}
|
||||
@@ -366,11 +362,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: 'static> NewService for Files<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
impl NewService for Files {
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Service = FilesService<P>;
|
||||
type Service = FilesService;
|
||||
type InitError = ();
|
||||
type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>;
|
||||
|
||||
@@ -401,37 +397,36 @@ impl<P: 'static> NewService for Files<P> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FilesService<P> {
|
||||
pub struct FilesService {
|
||||
directory: PathBuf,
|
||||
index: Option<String>,
|
||||
show_index: bool,
|
||||
default: Option<HttpService<P>>,
|
||||
default: Option<HttpService>,
|
||||
renderer: Rc<DirectoryRenderer>,
|
||||
mime_override: Option<Rc<MimeOverride>>,
|
||||
file_flags: named::Flags,
|
||||
}
|
||||
|
||||
impl<P> FilesService<P> {
|
||||
impl FilesService {
|
||||
fn handle_err(
|
||||
&mut self,
|
||||
e: io::Error,
|
||||
req: HttpRequest,
|
||||
payload: Payload<P>,
|
||||
req: ServiceRequest,
|
||||
) -> Either<
|
||||
FutureResult<ServiceResponse, Error>,
|
||||
Box<Future<Item = ServiceResponse, Error = Error>>,
|
||||
> {
|
||||
log::debug!("Files: Failed to handle {}: {}", req.path(), e);
|
||||
if let Some(ref mut default) = self.default {
|
||||
default.call(ServiceRequest::from_parts(req, payload))
|
||||
default.call(req)
|
||||
} else {
|
||||
Either::A(ok(ServiceResponse::from_err(e, req.clone())))
|
||||
Either::A(ok(req.error_response(e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> Service for FilesService<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
impl Service for FilesService {
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Future = Either<
|
||||
@@ -443,18 +438,18 @@ impl<P> Service for FilesService<P> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||
let (req, pl) = req.into_parts();
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
// let (req, pl) = req.into_parts();
|
||||
|
||||
let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) {
|
||||
Ok(item) => item,
|
||||
Err(e) => return Either::A(ok(ServiceResponse::from_err(e, req.clone()))),
|
||||
Err(e) => return Either::A(ok(req.error_response(e))),
|
||||
};
|
||||
|
||||
// full filepath
|
||||
let path = match self.directory.join(&real_path.0).canonicalize() {
|
||||
Ok(path) => path,
|
||||
Err(e) => return self.handle_err(e, req, pl),
|
||||
Err(e) => return self.handle_err(e, req),
|
||||
};
|
||||
|
||||
if path.is_dir() {
|
||||
@@ -470,24 +465,26 @@ impl<P> Service for FilesService<P> {
|
||||
}
|
||||
|
||||
named_file.flags = self.file_flags;
|
||||
let (req, _) = req.into_parts();
|
||||
Either::A(ok(match named_file.respond_to(&req) {
|
||||
Ok(item) => ServiceResponse::new(req.clone(), item),
|
||||
Err(e) => ServiceResponse::from_err(e, req.clone()),
|
||||
Ok(item) => ServiceResponse::new(req, item),
|
||||
Err(e) => ServiceResponse::from_err(e, req),
|
||||
}))
|
||||
}
|
||||
Err(e) => return self.handle_err(e, req, pl),
|
||||
Err(e) => return self.handle_err(e, req),
|
||||
}
|
||||
} else if self.show_index {
|
||||
let dir = Directory::new(self.directory.clone(), path);
|
||||
let (req, _) = req.into_parts();
|
||||
let x = (self.renderer)(&dir, &req);
|
||||
match x {
|
||||
Ok(resp) => Either::A(ok(resp)),
|
||||
Err(e) => return self.handle_err(e, req, pl),
|
||||
Err(e) => return Either::A(ok(ServiceResponse::from_err(e, req))),
|
||||
}
|
||||
} else {
|
||||
Either::A(ok(ServiceResponse::from_err(
|
||||
FilesError::IsDirectory,
|
||||
req.clone(),
|
||||
req.into_parts().0,
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
@@ -500,16 +497,15 @@ impl<P> Service for FilesService<P> {
|
||||
}
|
||||
|
||||
named_file.flags = self.file_flags;
|
||||
let (req, _) = req.into_parts();
|
||||
match named_file.respond_to(&req) {
|
||||
Ok(item) => {
|
||||
Either::A(ok(ServiceResponse::new(req.clone(), item)))
|
||||
}
|
||||
Err(e) => {
|
||||
Either::A(ok(ServiceResponse::from_err(e, req.clone())))
|
||||
Err(e) => Either::A(ok(ServiceResponse::from_err(e, req))),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => self.handle_err(e, req, pl),
|
||||
Err(e) => self.handle_err(e, req),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -547,11 +543,12 @@ impl PathBufWrp {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> FromRequest<P> for PathBufWrp {
|
||||
impl FromRequest for PathBufWrp {
|
||||
type Error = UriSegmentError;
|
||||
type Future = Result<Self, Self::Error>;
|
||||
type Config = ();
|
||||
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
PathBufWrp::get_pathbuf(req.match_info().path())
|
||||
}
|
||||
}
|
||||
@@ -570,6 +567,7 @@ mod tests {
|
||||
self, ContentDisposition, DispositionParam, DispositionType,
|
||||
};
|
||||
use actix_web::http::{Method, StatusCode};
|
||||
use actix_web::middleware::Compress;
|
||||
use actix_web::test::{self, TestRequest};
|
||||
use actix_web::App;
|
||||
|
||||
@@ -777,7 +775,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let request = TestRequest::get().uri("/").to_request();
|
||||
let response = test::call_success(&mut srv, request);
|
||||
let response = test::call_service(&mut srv, request);
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let content_disposition = response
|
||||
@@ -801,7 +799,7 @@ mod tests {
|
||||
.uri("/t%65st/Cargo.toml")
|
||||
.header(header::RANGE, "bytes=10-20")
|
||||
.to_request();
|
||||
let response = test::call_success(&mut srv, request);
|
||||
let response = test::call_service(&mut srv, request);
|
||||
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
|
||||
|
||||
// Invalid range header
|
||||
@@ -809,7 +807,7 @@ mod tests {
|
||||
.uri("/t%65st/Cargo.toml")
|
||||
.header(header::RANGE, "bytes=1-0")
|
||||
.to_request();
|
||||
let response = test::call_success(&mut srv, request);
|
||||
let response = test::call_service(&mut srv, request);
|
||||
|
||||
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
|
||||
}
|
||||
@@ -826,7 +824,7 @@ mod tests {
|
||||
.header(header::RANGE, "bytes=10-20")
|
||||
.to_request();
|
||||
|
||||
let response = test::call_success(&mut srv, request);
|
||||
let response = test::call_service(&mut srv, request);
|
||||
let contentrange = response
|
||||
.headers()
|
||||
.get(header::CONTENT_RANGE)
|
||||
@@ -841,7 +839,7 @@ mod tests {
|
||||
.uri("/t%65st/tests/test.binary")
|
||||
.header(header::RANGE, "bytes=10-5")
|
||||
.to_request();
|
||||
let response = test::call_success(&mut srv, request);
|
||||
let response = test::call_service(&mut srv, request);
|
||||
|
||||
let contentrange = response
|
||||
.headers()
|
||||
@@ -864,7 +862,7 @@ mod tests {
|
||||
.uri("/t%65st/tests/test.binary")
|
||||
.header(header::RANGE, "bytes=10-20")
|
||||
.to_request();
|
||||
let response = test::call_success(&mut srv, request);
|
||||
let response = test::call_service(&mut srv, request);
|
||||
|
||||
let contentlength = response
|
||||
.headers()
|
||||
@@ -880,7 +878,7 @@ mod tests {
|
||||
.uri("/t%65st/tests/test.binary")
|
||||
.header(header::RANGE, "bytes=10-8")
|
||||
.to_request();
|
||||
let response = test::call_success(&mut srv, request);
|
||||
let response = test::call_service(&mut srv, request);
|
||||
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
|
||||
|
||||
// Without range header
|
||||
@@ -888,7 +886,7 @@ mod tests {
|
||||
.uri("/t%65st/tests/test.binary")
|
||||
// .no_default_headers()
|
||||
.to_request();
|
||||
let response = test::call_success(&mut srv, request);
|
||||
let response = test::call_service(&mut srv, request);
|
||||
|
||||
let contentlength = response
|
||||
.headers()
|
||||
@@ -903,7 +901,7 @@ mod tests {
|
||||
let request = TestRequest::get()
|
||||
.uri("/t%65st/tests/test.binary")
|
||||
.to_request();
|
||||
let mut response = test::call_success(&mut srv, request);
|
||||
let mut response = test::call_service(&mut srv, request);
|
||||
|
||||
// with enabled compression
|
||||
// {
|
||||
@@ -934,7 +932,7 @@ mod tests {
|
||||
let request = TestRequest::get()
|
||||
.uri("/tests/test%20space.binary")
|
||||
.to_request();
|
||||
let mut response = test::call_success(&mut srv, request);
|
||||
let mut response = test::call_service(&mut srv, request);
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let bytes =
|
||||
@@ -965,7 +963,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_named_file_content_encoding() {
|
||||
let mut srv = test::init_service(App::new().enable_encoding().service(
|
||||
let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
|
||||
web::resource("/").to(|| {
|
||||
NamedFile::open("Cargo.toml")
|
||||
.unwrap()
|
||||
@@ -977,14 +975,14 @@ mod tests {
|
||||
.uri("/")
|
||||
.header(header::ACCEPT_ENCODING, "gzip")
|
||||
.to_request();
|
||||
let res = test::call_success(&mut srv, request);
|
||||
let res = test::call_service(&mut srv, request);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert!(!res.headers().contains_key(header::CONTENT_ENCODING));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_named_file_content_encoding_gzip() {
|
||||
let mut srv = test::init_service(App::new().enable_encoding().service(
|
||||
let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
|
||||
web::resource("/").to(|| {
|
||||
NamedFile::open("Cargo.toml")
|
||||
.unwrap()
|
||||
@@ -996,7 +994,7 @@ mod tests {
|
||||
.uri("/")
|
||||
.header(header::ACCEPT_ENCODING, "gzip")
|
||||
.to_request();
|
||||
let res = test::call_success(&mut srv, request);
|
||||
let res = test::call_service(&mut srv, request);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers()
|
||||
@@ -1023,20 +1021,20 @@ mod tests {
|
||||
);
|
||||
let req = TestRequest::with_uri("/missing").to_request();
|
||||
|
||||
let resp = test::call_success(&mut srv, req);
|
||||
let resp = test::call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let mut srv = test::init_service(App::new().service(Files::new("/", ".")));
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = test::call_success(&mut srv, req);
|
||||
let resp = test::call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let mut srv = test::init_service(
|
||||
App::new().service(Files::new("/", ".").show_files_listing()),
|
||||
);
|
||||
let req = TestRequest::with_uri("/tests").to_request();
|
||||
let mut resp = test::call_success(&mut srv, req);
|
||||
let mut resp = test::call_service(&mut srv, req);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
"text/html; charset=utf-8"
|
||||
@@ -1053,15 +1051,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_static_files_bad_directory() {
|
||||
let _st: Files<()> = Files::new("/", "missing");
|
||||
let _st: Files<()> = Files::new("/", "Cargo.toml");
|
||||
let _st: Files = Files::new("/", "missing");
|
||||
let _st: Files = Files::new("/", "Cargo.toml");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_handler_file_missing() {
|
||||
let mut st = test::block_on(
|
||||
Files::new("/", ".")
|
||||
.default_handler(|req: ServiceRequest<_>| {
|
||||
.default_handler(|req: ServiceRequest| {
|
||||
Ok(req.into_response(HttpResponse::Ok().body("default content")))
|
||||
})
|
||||
.new_service(&()),
|
||||
@@ -1069,7 +1067,7 @@ mod tests {
|
||||
.unwrap();
|
||||
let req = TestRequest::with_uri("/missing").to_srv_request();
|
||||
|
||||
let mut resp = test::call_success(&mut st, req);
|
||||
let mut resp = test::call_service(&mut st, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let bytes =
|
||||
test::block_on(resp.take_body().fold(BytesMut::new(), |mut b, c| {
|
||||
|
@@ -15,7 +15,7 @@ use actix_web::http::header::{
|
||||
self, ContentDisposition, DispositionParam, DispositionType,
|
||||
};
|
||||
use actix_web::http::{ContentEncoding, Method, StatusCode};
|
||||
use actix_web::middleware::encoding::BodyEncoding;
|
||||
use actix_web::middleware::BodyEncoding;
|
||||
use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder};
|
||||
|
||||
use crate::range::HttpRange;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-framed"
|
||||
version = "0.1.0-alpha.1"
|
||||
version = "0.1.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix framed app server"
|
||||
readme = "README.md"
|
||||
@@ -25,13 +25,13 @@ actix-service = "0.3.6"
|
||||
actix-utils = "0.3.4"
|
||||
actix-router = "0.1.2"
|
||||
actix-rt = "0.2.2"
|
||||
actix-http = "0.1.0-alpha.5"
|
||||
actix-http = "0.1.0"
|
||||
|
||||
bytes = "0.4"
|
||||
futures = "0.1.25"
|
||||
log = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-server = { version = "0.4.1", features=["ssl"] }
|
||||
actix-server = { version = "0.4.3", features=["ssl"] }
|
||||
actix-connect = { version = "0.1.4", features=["ssl"] }
|
||||
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
|
||||
actix-http-test = { version = "0.1.0", features=["ssl"] }
|
||||
|
@@ -1,5 +1,10 @@
|
||||
# Changes
|
||||
|
||||
## [0.1.0] - 2019-04-16
|
||||
|
||||
* Update tests
|
||||
|
||||
|
||||
## [0.1.0-alpha.1] - 2019-04-12
|
||||
|
||||
* Initial release
|
||||
|
@@ -1,5 +1,5 @@
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_http::{body, ws, Error, HttpService, Response};
|
||||
use actix_http::{body, http::StatusCode, ws, Error, HttpService, Response};
|
||||
use actix_http_test::TestServer;
|
||||
use actix_service::{IntoNewService, NewService};
|
||||
use actix_utils::framed::FramedTransport;
|
||||
@@ -99,6 +99,11 @@ fn test_service() {
|
||||
)
|
||||
});
|
||||
|
||||
// non ws request
|
||||
let res = srv.block_on(srv.get("/index.html").send()).unwrap();
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
// not found
|
||||
assert!(srv.ws_at("/test").is_err());
|
||||
|
||||
// client service
|
||||
|
@@ -1,5 +1,36 @@
|
||||
# Changes
|
||||
|
||||
## [0.1.2] - 2019-04-23
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix BorrowMutError panic in client connector #793
|
||||
|
||||
|
||||
## [0.1.1] - 2019-04-19
|
||||
|
||||
### Changes
|
||||
|
||||
* Cookie::max_age() accepts value in seconds
|
||||
|
||||
* Cookie::max_age_time() accepts value in time::Duration
|
||||
|
||||
* Allow to specify server address for client connector
|
||||
|
||||
|
||||
## [0.1.0] - 2019-04-16
|
||||
|
||||
### Added
|
||||
|
||||
* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr`
|
||||
|
||||
### Changed
|
||||
|
||||
* `actix_http::encoding` always available
|
||||
|
||||
* use trust-dns-resolver 0.11.0
|
||||
|
||||
|
||||
## [0.1.0-alpha.5] - 2019-04-12
|
||||
|
||||
### Added
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-http"
|
||||
version = "0.1.0-alpha.5"
|
||||
version = "0.1.2"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix http primitives"
|
||||
readme = "README.md"
|
||||
@@ -46,9 +46,9 @@ secure-cookies = ["ring"]
|
||||
[dependencies]
|
||||
actix-service = "0.3.6"
|
||||
actix-codec = "0.1.2"
|
||||
actix-connect = "0.1.4"
|
||||
actix-connect = "0.1.5"
|
||||
actix-utils = "0.3.5"
|
||||
actix-server-config = "0.1.0"
|
||||
actix-server-config = "0.1.1"
|
||||
actix-threadpool = "0.1.0"
|
||||
|
||||
base64 = "0.10"
|
||||
@@ -60,7 +60,7 @@ derive_more = "0.14"
|
||||
either = "1.5.2"
|
||||
encoding = "0.2"
|
||||
futures = "0.1"
|
||||
hashbrown = "0.2.0"
|
||||
hashbrown = "0.2.2"
|
||||
h2 = "0.1.16"
|
||||
http = "0.1.17"
|
||||
httparse = "1.3"
|
||||
@@ -76,12 +76,12 @@ serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
sha1 = "0.6"
|
||||
slab = "0.4"
|
||||
serde_urlencoded = "0.5.3"
|
||||
time = "0.1"
|
||||
serde_urlencoded = "0.5.5"
|
||||
time = "0.1.42"
|
||||
tokio-tcp = "0.1.3"
|
||||
tokio-timer = "0.2"
|
||||
tokio-timer = "0.2.8"
|
||||
tokio-current-thread = "0.1"
|
||||
trust-dns-resolver = { version="0.11.0-alpha.3", default-features = false }
|
||||
trust-dns-resolver = { version="0.11.0", default-features = false }
|
||||
|
||||
# for secure cookie
|
||||
ring = { version = "0.14.6", optional = true }
|
||||
@@ -93,12 +93,13 @@ flate2 = { version="1.0.7", optional = true, default-features = false }
|
||||
# optional deps
|
||||
failure = { version = "0.1.5", optional = true }
|
||||
openssl = { version="0.10", optional = true }
|
||||
chrono = "0.4.6"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "0.2.2"
|
||||
actix-server = { version = "0.4.1", features=["ssl"] }
|
||||
actix-server = { version = "0.4.3", features=["ssl"] }
|
||||
actix-connect = { version = "0.1.4", features=["ssl"] }
|
||||
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
|
||||
actix-http-test = { version = "0.1.0", features=["ssl"] }
|
||||
env_logger = "0.6"
|
||||
serde_derive = "1.0"
|
||||
openssl = { version="0.10" }
|
||||
|
@@ -14,6 +14,7 @@ use tokio_tcp::TcpStream;
|
||||
use super::connection::Connection;
|
||||
use super::error::ConnectError;
|
||||
use super::pool::{ConnectionPool, Protocol};
|
||||
use super::Connect;
|
||||
|
||||
#[cfg(feature = "ssl")]
|
||||
use openssl::ssl::SslConnector;
|
||||
@@ -57,8 +58,7 @@ impl Connector<(), ()> {
|
||||
let ssl = {
|
||||
#[cfg(feature = "ssl")]
|
||||
{
|
||||
use log::error;
|
||||
use openssl::ssl::{SslConnector, SslMethod};
|
||||
use openssl::ssl::SslMethod;
|
||||
|
||||
let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||
let _ = ssl
|
||||
@@ -178,13 +178,15 @@ where
|
||||
/// its combinator chain.
|
||||
pub fn finish(
|
||||
self,
|
||||
) -> impl Service<Request = Uri, Response = impl Connection, Error = ConnectError> + Clone
|
||||
{
|
||||
) -> impl Service<Request = Connect, Response = impl Connection, Error = ConnectError>
|
||||
+ Clone {
|
||||
#[cfg(not(feature = "ssl"))]
|
||||
{
|
||||
let connector = TimeoutService::new(
|
||||
self.timeout,
|
||||
apply_fn(self.connector, |msg: Uri, srv| srv.call(msg.into()))
|
||||
apply_fn(self.connector, |msg: Connect, srv| {
|
||||
srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr))
|
||||
})
|
||||
.map_err(ConnectError::from)
|
||||
.map(|stream| (stream.into_parts().0, Protocol::Http1)),
|
||||
)
|
||||
@@ -210,7 +212,9 @@ where
|
||||
|
||||
let ssl_service = TimeoutService::new(
|
||||
self.timeout,
|
||||
apply_fn(self.connector.clone(), |msg: Uri, srv| srv.call(msg.into()))
|
||||
apply_fn(self.connector.clone(), |msg: Connect, srv| {
|
||||
srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr))
|
||||
})
|
||||
.map_err(ConnectError::from)
|
||||
.and_then(
|
||||
OpensslConnector::service(self.ssl)
|
||||
@@ -238,7 +242,9 @@ where
|
||||
|
||||
let tcp_service = TimeoutService::new(
|
||||
self.timeout,
|
||||
apply_fn(self.connector.clone(), |msg: Uri, srv| srv.call(msg.into()))
|
||||
apply_fn(self.connector.clone(), |msg: Connect, srv| {
|
||||
srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr))
|
||||
})
|
||||
.map_err(ConnectError::from)
|
||||
.map(|stream| (stream.into_parts().0, Protocol::Http1)),
|
||||
)
|
||||
@@ -265,15 +271,6 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "0.1.0-alpha4", note = "please use `.finish()` method")]
|
||||
pub fn service(
|
||||
self,
|
||||
) -> impl Service<Request = Uri, Response = impl Connection, Error = ConnectError> + Clone
|
||||
{
|
||||
self.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssl"))]
|
||||
@@ -287,7 +284,7 @@ mod connect_impl {
|
||||
pub(crate) struct InnerConnector<T, Io>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite + 'static,
|
||||
T: Service<Request = Uri, Response = (Io, Protocol), Error = ConnectError>,
|
||||
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>,
|
||||
{
|
||||
pub(crate) tcp_pool: ConnectionPool<T, Io>,
|
||||
}
|
||||
@@ -295,7 +292,7 @@ mod connect_impl {
|
||||
impl<T, Io> Clone for InnerConnector<T, Io>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite + 'static,
|
||||
T: Service<Request = Uri, Response = (Io, Protocol), Error = ConnectError>
|
||||
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
|
||||
+ Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
@@ -308,9 +305,9 @@ mod connect_impl {
|
||||
impl<T, Io> Service for InnerConnector<T, Io>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite + 'static,
|
||||
T: Service<Request = Uri, Response = (Io, Protocol), Error = ConnectError>,
|
||||
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>,
|
||||
{
|
||||
type Request = Uri;
|
||||
type Request = Connect;
|
||||
type Response = IoConnection<Io>;
|
||||
type Error = ConnectError;
|
||||
type Future = Either<
|
||||
@@ -322,8 +319,8 @@ mod connect_impl {
|
||||
self.tcp_pool.poll_ready()
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Uri) -> Self::Future {
|
||||
match req.scheme_str() {
|
||||
fn call(&mut self, req: Connect) -> Self::Future {
|
||||
match req.uri.scheme_str() {
|
||||
Some("https") | Some("wss") => {
|
||||
Either::B(err(ConnectError::SslIsNotSupported))
|
||||
}
|
||||
@@ -347,8 +344,8 @@ mod connect_impl {
|
||||
where
|
||||
Io1: AsyncRead + AsyncWrite + 'static,
|
||||
Io2: AsyncRead + AsyncWrite + 'static,
|
||||
T1: Service<Request = Uri, Response = (Io1, Protocol), Error = ConnectError>,
|
||||
T2: Service<Request = Uri, Response = (Io2, Protocol), Error = ConnectError>,
|
||||
T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>,
|
||||
T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>,
|
||||
{
|
||||
pub(crate) tcp_pool: ConnectionPool<T1, Io1>,
|
||||
pub(crate) ssl_pool: ConnectionPool<T2, Io2>,
|
||||
@@ -358,9 +355,9 @@ mod connect_impl {
|
||||
where
|
||||
Io1: AsyncRead + AsyncWrite + 'static,
|
||||
Io2: AsyncRead + AsyncWrite + 'static,
|
||||
T1: Service<Request = Uri, Response = (Io1, Protocol), Error = ConnectError>
|
||||
T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
|
||||
+ Clone,
|
||||
T2: Service<Request = Uri, Response = (Io2, Protocol), Error = ConnectError>
|
||||
T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
|
||||
+ Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
@@ -375,10 +372,10 @@ mod connect_impl {
|
||||
where
|
||||
Io1: AsyncRead + AsyncWrite + 'static,
|
||||
Io2: AsyncRead + AsyncWrite + 'static,
|
||||
T1: Service<Request = Uri, Response = (Io1, Protocol), Error = ConnectError>,
|
||||
T2: Service<Request = Uri, Response = (Io2, Protocol), Error = ConnectError>,
|
||||
T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>,
|
||||
T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>,
|
||||
{
|
||||
type Request = Uri;
|
||||
type Request = Connect;
|
||||
type Response = EitherConnection<Io1, Io2>;
|
||||
type Error = ConnectError;
|
||||
type Future = Either<
|
||||
@@ -393,8 +390,8 @@ mod connect_impl {
|
||||
self.tcp_pool.poll_ready()
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Uri) -> Self::Future {
|
||||
match req.scheme_str() {
|
||||
fn call(&mut self, req: Connect) -> Self::Future {
|
||||
match req.uri.scheme_str() {
|
||||
Some("https") | Some("wss") => {
|
||||
Either::B(Either::B(InnerConnectorResponseB {
|
||||
fut: self.ssl_pool.call(req),
|
||||
@@ -412,7 +409,7 @@ mod connect_impl {
|
||||
pub(crate) struct InnerConnectorResponseA<T, Io1, Io2>
|
||||
where
|
||||
Io1: AsyncRead + AsyncWrite + 'static,
|
||||
T: Service<Request = Uri, Response = (Io1, Protocol), Error = ConnectError>,
|
||||
T: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>,
|
||||
{
|
||||
fut: <ConnectionPool<T, Io1> as Service>::Future,
|
||||
_t: PhantomData<Io2>,
|
||||
@@ -420,7 +417,7 @@ mod connect_impl {
|
||||
|
||||
impl<T, Io1, Io2> Future for InnerConnectorResponseA<T, Io1, Io2>
|
||||
where
|
||||
T: Service<Request = Uri, Response = (Io1, Protocol), Error = ConnectError>,
|
||||
T: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>,
|
||||
Io1: AsyncRead + AsyncWrite + 'static,
|
||||
Io2: AsyncRead + AsyncWrite + 'static,
|
||||
{
|
||||
@@ -438,7 +435,7 @@ mod connect_impl {
|
||||
pub(crate) struct InnerConnectorResponseB<T, Io1, Io2>
|
||||
where
|
||||
Io2: AsyncRead + AsyncWrite + 'static,
|
||||
T: Service<Request = Uri, Response = (Io2, Protocol), Error = ConnectError>,
|
||||
T: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>,
|
||||
{
|
||||
fut: <ConnectionPool<T, Io2> as Service>::Future,
|
||||
_t: PhantomData<Io1>,
|
||||
@@ -446,7 +443,7 @@ mod connect_impl {
|
||||
|
||||
impl<T, Io1, Io2> Future for InnerConnectorResponseB<T, Io1, Io2>
|
||||
where
|
||||
T: Service<Request = Uri, Response = (Io2, Protocol), Error = ConnectError>,
|
||||
T: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>,
|
||||
Io1: AsyncRead + AsyncWrite + 'static,
|
||||
Io2: AsyncRead + AsyncWrite + 'static,
|
||||
{
|
||||
|
@@ -1,4 +1,6 @@
|
||||
//! Http client api
|
||||
use http::Uri;
|
||||
|
||||
mod connection;
|
||||
mod connector;
|
||||
mod error;
|
||||
@@ -10,3 +12,9 @@ pub use self::connection::Connection;
|
||||
pub use self::connector::Connector;
|
||||
pub use self::error::{ConnectError, InvalidUrl, SendRequestError};
|
||||
pub use self::pool::Protocol;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Connect {
|
||||
pub uri: Uri,
|
||||
pub addr: Option<std::net::SocketAddr>,
|
||||
}
|
||||
|
@@ -13,13 +13,14 @@ use futures::unsync::oneshot;
|
||||
use futures::{Async, Future, Poll};
|
||||
use h2::client::{handshake, Handshake};
|
||||
use hashbrown::HashMap;
|
||||
use http::uri::{Authority, Uri};
|
||||
use http::uri::Authority;
|
||||
use indexmap::IndexSet;
|
||||
use slab::Slab;
|
||||
use tokio_timer::{sleep, Delay};
|
||||
|
||||
use super::connection::{ConnectionType, IoConnection};
|
||||
use super::error::ConnectError;
|
||||
use super::Connect;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
/// Protocol version
|
||||
@@ -48,7 +49,7 @@ pub(crate) struct ConnectionPool<T, Io: AsyncRead + AsyncWrite + 'static>(
|
||||
impl<T, Io> ConnectionPool<T, Io>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite + 'static,
|
||||
T: Service<Request = Uri, Response = (Io, Protocol), Error = ConnectError>,
|
||||
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
connector: T,
|
||||
@@ -87,9 +88,9 @@ where
|
||||
impl<T, Io> Service for ConnectionPool<T, Io>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite + 'static,
|
||||
T: Service<Request = Uri, Response = (Io, Protocol), Error = ConnectError>,
|
||||
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>,
|
||||
{
|
||||
type Request = Uri;
|
||||
type Request = Connect;
|
||||
type Response = IoConnection<Io>;
|
||||
type Error = ConnectError;
|
||||
type Future = Either<
|
||||
@@ -101,8 +102,8 @@ where
|
||||
self.0.poll_ready()
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Uri) -> Self::Future {
|
||||
let key = if let Some(authority) = req.authority_part() {
|
||||
fn call(&mut self, req: Connect) -> Self::Future {
|
||||
let key = if let Some(authority) = req.uri.authority_part() {
|
||||
authority.clone().into()
|
||||
} else {
|
||||
return Either::A(err(ConnectError::Unresolverd));
|
||||
@@ -112,13 +113,23 @@ where
|
||||
match self.1.as_ref().borrow_mut().acquire(&key) {
|
||||
Acquire::Acquired(io, created) => {
|
||||
// use existing connection
|
||||
Either::A(ok(IoConnection::new(
|
||||
return Either::A(ok(IoConnection::new(
|
||||
io,
|
||||
created,
|
||||
Some(Acquired(key, Some(self.1.clone()))),
|
||||
)))
|
||||
)));
|
||||
}
|
||||
Acquire::NotAvailable => {
|
||||
Acquire::Available => {
|
||||
// open new connection
|
||||
return Either::B(Either::B(OpenConnection::new(
|
||||
key,
|
||||
self.1.clone(),
|
||||
self.0.call(req),
|
||||
)));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// connection is not available, wait
|
||||
let (rx, token) = self.1.as_ref().borrow_mut().wait_for(req);
|
||||
Either::B(Either::A(WaitForConnection {
|
||||
@@ -128,16 +139,6 @@ where
|
||||
inner: Some(self.1.clone()),
|
||||
}))
|
||||
}
|
||||
Acquire::Available => {
|
||||
// open new connection
|
||||
Either::B(Either::B(OpenConnection::new(
|
||||
key,
|
||||
self.1.clone(),
|
||||
self.0.call(req),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
@@ -292,7 +293,10 @@ pub(crate) struct Inner<Io> {
|
||||
limit: usize,
|
||||
acquired: usize,
|
||||
available: HashMap<Key, VecDeque<AvailableConnection<Io>>>,
|
||||
waiters: Slab<(Uri, oneshot::Sender<Result<IoConnection<Io>, ConnectError>>)>,
|
||||
waiters: Slab<(
|
||||
Connect,
|
||||
oneshot::Sender<Result<IoConnection<Io>, ConnectError>>,
|
||||
)>,
|
||||
waiters_queue: IndexSet<(Key, usize)>,
|
||||
task: AtomicTask,
|
||||
}
|
||||
@@ -331,14 +335,14 @@ where
|
||||
/// connection is not available, wait
|
||||
fn wait_for(
|
||||
&mut self,
|
||||
connect: Uri,
|
||||
connect: Connect,
|
||||
) -> (
|
||||
oneshot::Receiver<Result<IoConnection<Io>, ConnectError>>,
|
||||
usize,
|
||||
) {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let key: Key = connect.authority_part().unwrap().clone().into();
|
||||
let key: Key = connect.uri.authority_part().unwrap().clone().into();
|
||||
let entry = self.waiters.vacant_entry();
|
||||
let token = entry.key();
|
||||
entry.insert((connect, tx));
|
||||
|
@@ -1,6 +1,7 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use time::{Duration, Tm};
|
||||
use chrono::Duration;
|
||||
use time::Tm;
|
||||
|
||||
use super::{Cookie, SameSite};
|
||||
|
||||
@@ -16,7 +17,6 @@ use super::{Cookie, SameSite};
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_http::cookie::Cookie;
|
||||
/// use time::Duration;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let cookie: Cookie = Cookie::build("name", "value")
|
||||
@@ -24,7 +24,7 @@ use super::{Cookie, SameSite};
|
||||
/// .path("/")
|
||||
/// .secure(true)
|
||||
/// .http_only(true)
|
||||
/// .max_age(Duration::days(1))
|
||||
/// .max_age(84600)
|
||||
/// .finish();
|
||||
/// # }
|
||||
/// ```
|
||||
@@ -79,6 +79,26 @@ impl CookieBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `max_age` field in seconds in the cookie being built.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_http::cookie::Cookie;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let c = Cookie::build("foo", "bar")
|
||||
/// .max_age(1800)
|
||||
/// .finish();
|
||||
///
|
||||
/// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60)));
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn max_age(self, seconds: i64) -> CookieBuilder {
|
||||
self.max_age_time(Duration::seconds(seconds))
|
||||
}
|
||||
|
||||
/// Sets the `max_age` field in the cookie being built.
|
||||
///
|
||||
/// # Example
|
||||
@@ -88,14 +108,14 @@ impl CookieBuilder {
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let c = Cookie::build("foo", "bar")
|
||||
/// .max_age(time::Duration::minutes(30))
|
||||
/// .max_age_time(time::Duration::minutes(30))
|
||||
/// .finish();
|
||||
///
|
||||
/// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60)));
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn max_age(mut self, value: Duration) -> CookieBuilder {
|
||||
pub fn max_age_time(mut self, value: Duration) -> CookieBuilder {
|
||||
self.cookie.set_max_age(value);
|
||||
self
|
||||
}
|
||||
@@ -200,7 +220,7 @@ impl CookieBuilder {
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_http::cookie::Cookie;
|
||||
/// use time::Duration;
|
||||
/// use chrono::Duration;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let c = Cookie::build("foo", "bar")
|
||||
|
@@ -1,7 +1,7 @@
|
||||
use std::collections::HashSet;
|
||||
use std::mem::replace;
|
||||
|
||||
use time::{self, Duration};
|
||||
use chrono::Duration;
|
||||
|
||||
use super::delta::DeltaCookie;
|
||||
use super::Cookie;
|
||||
@@ -188,7 +188,7 @@ impl CookieJar {
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_http::cookie::{CookieJar, Cookie};
|
||||
/// use time::Duration;
|
||||
/// use chrono::Duration;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut jar = CookieJar::new();
|
||||
@@ -241,7 +241,7 @@ impl CookieJar {
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_http::cookie::{CookieJar, Cookie};
|
||||
/// use time::Duration;
|
||||
/// use chrono::Duration;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut jar = CookieJar::new();
|
||||
@@ -537,8 +537,8 @@ mod test {
|
||||
#[test]
|
||||
#[cfg(feature = "secure-cookies")]
|
||||
fn delta() {
|
||||
use chrono::Duration;
|
||||
use std::collections::HashMap;
|
||||
use time::Duration;
|
||||
|
||||
let mut c = CookieJar::new();
|
||||
|
||||
|
@@ -65,8 +65,9 @@ use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use chrono::Duration;
|
||||
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
|
||||
use time::{Duration, Tm};
|
||||
use time::Tm;
|
||||
|
||||
pub use self::builder::CookieBuilder;
|
||||
pub use self::draft::*;
|
||||
@@ -624,7 +625,7 @@ impl<'c> Cookie<'c> {
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_http::cookie::Cookie;
|
||||
/// use time::Duration;
|
||||
/// use chrono::Duration;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut c = Cookie::new("name", "value");
|
||||
@@ -703,7 +704,7 @@ impl<'c> Cookie<'c> {
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_http::cookie::Cookie;
|
||||
/// use time::Duration;
|
||||
/// use chrono::Duration;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut c = Cookie::new("foo", "bar");
|
||||
@@ -977,7 +978,7 @@ impl<'a, 'b> PartialEq<Cookie<'b>> for Cookie<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Cookie, SameSite};
|
||||
use time::{strptime, Duration};
|
||||
use time::strptime;
|
||||
|
||||
#[test]
|
||||
fn format() {
|
||||
@@ -987,9 +988,7 @@ mod tests {
|
||||
let cookie = Cookie::build("foo", "bar").http_only(true).finish();
|
||||
assert_eq!(&cookie.to_string(), "foo=bar; HttpOnly");
|
||||
|
||||
let cookie = Cookie::build("foo", "bar")
|
||||
.max_age(Duration::seconds(10))
|
||||
.finish();
|
||||
let cookie = Cookie::build("foo", "bar").max_age(10).finish();
|
||||
assert_eq!(&cookie.to_string(), "foo=bar; Max-Age=10");
|
||||
|
||||
let cookie = Cookie::build("foo", "bar").secure(true).finish();
|
||||
|
@@ -5,8 +5,8 @@ use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::str::Utf8Error;
|
||||
|
||||
use chrono::Duration;
|
||||
use percent_encoding::percent_decode;
|
||||
use time::{self, Duration};
|
||||
|
||||
use super::{Cookie, CookieStr, SameSite};
|
||||
|
||||
@@ -220,7 +220,8 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Cookie, SameSite};
|
||||
use time::{strptime, Duration};
|
||||
use chrono::Duration;
|
||||
use time::strptime;
|
||||
|
||||
macro_rules! assert_eq_parse {
|
||||
($string:expr, $expected:expr) => {
|
||||
@@ -418,9 +419,7 @@ mod tests {
|
||||
#[test]
|
||||
fn do_not_panic_on_large_max_ages() {
|
||||
let max_seconds = Duration::max_value().num_seconds();
|
||||
let expected = Cookie::build("foo", "bar")
|
||||
.max_age(Duration::seconds(max_seconds))
|
||||
.finish();
|
||||
let expected = Cookie::build("foo", "bar").max_age(max_seconds).finish();
|
||||
assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", max_seconds + 1), expected);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#![allow(unused_imports, unused_variables, dead_code)]
|
||||
use std::fmt;
|
||||
use std::io::{self, Write};
|
||||
use std::io::Write;
|
||||
use std::{fmt, io, net};
|
||||
|
||||
use actix_codec::{Decoder, Encoder};
|
||||
use bitflags::bitflags;
|
||||
@@ -40,7 +40,6 @@ pub struct Codec {
|
||||
// encoder part
|
||||
flags: Flags,
|
||||
encoder: encoder::MessageEncoder<Response<()>>,
|
||||
// headers_size: u32,
|
||||
}
|
||||
|
||||
impl Default for Codec {
|
||||
@@ -67,13 +66,11 @@ impl Codec {
|
||||
};
|
||||
Codec {
|
||||
config,
|
||||
flags,
|
||||
decoder: decoder::MessageDecoder::default(),
|
||||
payload: None,
|
||||
version: Version::HTTP_11,
|
||||
ctype: ConnectionType::Close,
|
||||
|
||||
flags,
|
||||
// headers_size: 0,
|
||||
encoder: encoder::MessageEncoder::default(),
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::time::Instant;
|
||||
use std::{fmt, io};
|
||||
use std::{fmt, io, net};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts};
|
||||
use actix_codec::{Decoder, Encoder, Framed, FramedParts};
|
||||
use actix_server_config::IoStream;
|
||||
use actix_service::Service;
|
||||
use actix_utils::cloneable::CloneableService;
|
||||
use bitflags::bitflags;
|
||||
@@ -81,6 +82,7 @@ where
|
||||
expect: CloneableService<X>,
|
||||
upgrade: Option<CloneableService<U>>,
|
||||
flags: Flags,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
error: Option<DispatchError>,
|
||||
|
||||
state: State<S, B, X>,
|
||||
@@ -161,7 +163,7 @@ impl PartialEq for PollResponse {
|
||||
|
||||
impl<T, S, B, X, U> Dispatcher<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
@@ -220,14 +222,15 @@ where
|
||||
|
||||
Dispatcher {
|
||||
inner: DispatcherState::Normal(InnerDispatcher {
|
||||
io,
|
||||
codec,
|
||||
read_buf,
|
||||
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
||||
payload: None,
|
||||
state: State::None,
|
||||
error: None,
|
||||
peer_addr: io.peer_addr(),
|
||||
messages: VecDeque::new(),
|
||||
io,
|
||||
codec,
|
||||
read_buf,
|
||||
service,
|
||||
expect,
|
||||
upgrade,
|
||||
@@ -241,7 +244,7 @@ where
|
||||
|
||||
impl<T, S, B, X, U> InnerDispatcher<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
@@ -490,6 +493,7 @@ where
|
||||
match msg {
|
||||
Message::Item(mut req) => {
|
||||
let pl = self.codec.message_type();
|
||||
req.head_mut().peer_addr = self.peer_addr;
|
||||
|
||||
if pl == MessageType::Stream && self.upgrade.is_some() {
|
||||
self.messages.push_back(DispatcherMessage::Upgrade(req));
|
||||
@@ -649,7 +653,7 @@ where
|
||||
|
||||
impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
|
@@ -1,8 +1,8 @@
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_server_config::{Io, ServerConfig as SrvConfig};
|
||||
use actix_codec::Framed;
|
||||
use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig};
|
||||
use actix_service::{IntoNewService, NewService, Service};
|
||||
use actix_utils::cloneable::CloneableService;
|
||||
use futures::future::{ok, FutureResult};
|
||||
@@ -104,7 +104,7 @@ where
|
||||
|
||||
impl<T, P, S, B, X, U> NewService<SrvConfig> for H1Service<T, P, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
@@ -161,7 +161,7 @@ where
|
||||
|
||||
impl<T, P, S, B, X, U> Future for H1ServiceResponse<T, P, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
@@ -245,7 +245,7 @@ where
|
||||
|
||||
impl<T, P, S, B, X, U> Service for H1ServiceHandler<T, P, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
@@ -309,7 +309,7 @@ pub struct OneRequest<T, P> {
|
||||
|
||||
impl<T, P> OneRequest<T, P>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
{
|
||||
/// Create new `H1SimpleService` instance.
|
||||
pub fn new() -> Self {
|
||||
@@ -322,7 +322,7 @@ where
|
||||
|
||||
impl<T, P> NewService<SrvConfig> for OneRequest<T, P>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
{
|
||||
type Request = Io<T, P>;
|
||||
type Response = (Request, Framed<T, Codec>);
|
||||
@@ -348,7 +348,7 @@ pub struct OneRequestService<T, P> {
|
||||
|
||||
impl<T, P> Service for OneRequestService<T, P>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
{
|
||||
type Request = Io<T, P>;
|
||||
type Response = (Request, Framed<T, Codec>);
|
||||
@@ -372,14 +372,14 @@ where
|
||||
#[doc(hidden)]
|
||||
pub struct OneRequestServiceResponse<T>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
{
|
||||
framed: Option<Framed<T, Codec>>,
|
||||
}
|
||||
|
||||
impl<T> Future for OneRequestServiceResponse<T>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
{
|
||||
type Item = (Request, Framed<T, Codec>);
|
||||
type Error = ParseError;
|
||||
|
@@ -1,9 +1,10 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::marker::PhantomData;
|
||||
use std::time::Instant;
|
||||
use std::{fmt, mem};
|
||||
use std::{fmt, mem, net};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_server_config::IoStream;
|
||||
use actix_service::Service;
|
||||
use actix_utils::cloneable::CloneableService;
|
||||
use bitflags::bitflags;
|
||||
@@ -29,14 +30,11 @@ use crate::response::Response;
|
||||
const CHUNK_SIZE: usize = 16_384;
|
||||
|
||||
/// Dispatcher for HTTP/2 protocol
|
||||
pub struct Dispatcher<
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request = Request>,
|
||||
B: MessageBody,
|
||||
> {
|
||||
pub struct Dispatcher<T: IoStream, S: Service<Request = Request>, B: MessageBody> {
|
||||
service: CloneableService<S>,
|
||||
connection: Connection<T, Bytes>,
|
||||
config: ServiceConfig,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
ka_expire: Instant,
|
||||
ka_timer: Option<Delay>,
|
||||
_t: PhantomData<B>,
|
||||
@@ -44,7 +42,7 @@ pub struct Dispatcher<
|
||||
|
||||
impl<T, S, B> Dispatcher<T, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
@@ -56,6 +54,7 @@ where
|
||||
connection: Connection<T, Bytes>,
|
||||
config: ServiceConfig,
|
||||
timeout: Option<Delay>,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
) -> Self {
|
||||
// let keepalive = config.keep_alive_enabled();
|
||||
// let flags = if keepalive {
|
||||
@@ -76,9 +75,10 @@ where
|
||||
Dispatcher {
|
||||
service,
|
||||
config,
|
||||
peer_addr,
|
||||
connection,
|
||||
ka_expire,
|
||||
ka_timer,
|
||||
connection,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ where
|
||||
|
||||
impl<T, S, B> Future for Dispatcher<T, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
@@ -117,6 +117,7 @@ where
|
||||
head.method = parts.method;
|
||||
head.version = parts.version;
|
||||
head.headers = parts.headers.into();
|
||||
head.peer_addr = self.peer_addr;
|
||||
tokio_current_thread::spawn(ServiceResponse::<S::Future, B> {
|
||||
state: ServiceResponseState::ServiceCall(
|
||||
self.service.call(req),
|
||||
|
@@ -3,7 +3,7 @@ use std::marker::PhantomData;
|
||||
use std::{io, net};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_server_config::{Io, ServerConfig as SrvConfig};
|
||||
use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig};
|
||||
use actix_service::{IntoNewService, NewService, Service};
|
||||
use actix_utils::cloneable::CloneableService;
|
||||
use bytes::Bytes;
|
||||
@@ -63,7 +63,7 @@ where
|
||||
|
||||
impl<T, P, S, B> NewService<SrvConfig> for H2Service<T, P, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
@@ -95,7 +95,7 @@ pub struct H2ServiceResponse<T, P, S: NewService<SrvConfig, Request = Request>,
|
||||
|
||||
impl<T, P, S, B> Future for H2ServiceResponse<T, P, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
@@ -140,7 +140,7 @@ where
|
||||
|
||||
impl<T, P, S, B> Service for H2ServiceHandler<T, P, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
@@ -161,17 +161,20 @@ where
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||
let io = req.into_parts().0;
|
||||
let peer_addr = io.peer_addr();
|
||||
H2ServiceHandlerResponse {
|
||||
state: State::Handshake(
|
||||
Some(self.srv.clone()),
|
||||
Some(self.cfg.clone()),
|
||||
server::handshake(req.into_parts().0),
|
||||
peer_addr,
|
||||
server::handshake(io),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum State<T: AsyncRead + AsyncWrite, S: Service<Request = Request>, B: MessageBody>
|
||||
enum State<T: IoStream, S: Service<Request = Request>, B: MessageBody>
|
||||
where
|
||||
S::Future: 'static,
|
||||
{
|
||||
@@ -179,13 +182,14 @@ where
|
||||
Handshake(
|
||||
Option<CloneableService<S>>,
|
||||
Option<ServiceConfig>,
|
||||
Option<net::SocketAddr>,
|
||||
Handshake<T, Bytes>,
|
||||
),
|
||||
}
|
||||
|
||||
pub struct H2ServiceHandlerResponse<T, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
@@ -197,7 +201,7 @@ where
|
||||
|
||||
impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
@@ -210,14 +214,19 @@ where
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.state {
|
||||
State::Incoming(ref mut disp) => disp.poll(),
|
||||
State::Handshake(ref mut srv, ref mut config, ref mut handshake) => {
|
||||
match handshake.poll() {
|
||||
State::Handshake(
|
||||
ref mut srv,
|
||||
ref mut config,
|
||||
ref peer_addr,
|
||||
ref mut handshake,
|
||||
) => match handshake.poll() {
|
||||
Ok(Async::Ready(conn)) => {
|
||||
self.state = State::Incoming(Dispatcher::new(
|
||||
srv.take().unwrap(),
|
||||
conn,
|
||||
config.take().unwrap(),
|
||||
None,
|
||||
peer_addr.clone(),
|
||||
));
|
||||
self.poll()
|
||||
}
|
||||
@@ -226,8 +235,7 @@ where
|
||||
trace!("H2 handshake error: {}", err);
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -60,6 +60,7 @@ impl Response {
|
||||
STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE);
|
||||
STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE);
|
||||
STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
|
||||
STATIC_RESP!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY);
|
||||
|
||||
STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR);
|
||||
STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED);
|
||||
|
@@ -12,7 +12,6 @@ pub mod body;
|
||||
mod builder;
|
||||
pub mod client;
|
||||
mod config;
|
||||
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust", feature = "brotli"))]
|
||||
pub mod encoding;
|
||||
mod extensions;
|
||||
mod header;
|
||||
|
@@ -1,4 +1,5 @@
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::net;
|
||||
use std::rc::Rc;
|
||||
|
||||
use bitflags::bitflags;
|
||||
@@ -43,6 +44,7 @@ pub struct RequestHead {
|
||||
pub version: Version,
|
||||
pub headers: HeaderMap,
|
||||
pub extensions: RefCell<Extensions>,
|
||||
pub peer_addr: Option<net::SocketAddr>,
|
||||
flags: Flags,
|
||||
}
|
||||
|
||||
@@ -54,6 +56,7 @@ impl Default for RequestHead {
|
||||
version: Version::HTTP_11,
|
||||
headers: HeaderMap::with_capacity(16),
|
||||
flags: Flags::empty(),
|
||||
peer_addr: None,
|
||||
extensions: RefCell::new(Extensions::new()),
|
||||
}
|
||||
}
|
||||
|
@@ -53,6 +53,7 @@ where
|
||||
type Item = Bytes;
|
||||
type Error = PayloadError;
|
||||
|
||||
#[inline]
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
match self {
|
||||
Payload::None => Ok(Async::Ready(None)),
|
||||
|
@@ -1,5 +1,5 @@
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::fmt;
|
||||
use std::{fmt, net};
|
||||
|
||||
use http::{header, Method, Uri, Version};
|
||||
|
||||
@@ -139,6 +139,7 @@ impl<P> Request<P> {
|
||||
}
|
||||
|
||||
/// Check if request requires connection upgrade
|
||||
#[inline]
|
||||
pub fn upgrade(&self) -> bool {
|
||||
if let Some(conn) = self.head().headers.get(header::CONNECTION) {
|
||||
if let Ok(s) = conn.to_str() {
|
||||
@@ -147,6 +148,15 @@ impl<P> Request<P> {
|
||||
}
|
||||
self.head().method == Method::CONNECT
|
||||
}
|
||||
|
||||
/// Peer socket address
|
||||
///
|
||||
/// Peer address is actual socket address, if proxy is used in front of
|
||||
/// actix http server, then peer address would be address of this proxy.
|
||||
#[inline]
|
||||
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
|
||||
self.head().peer_addr
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> fmt::Debug for Request<P> {
|
||||
@@ -168,3 +178,28 @@ impl<P> fmt::Debug for Request<P> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use http::HttpTryFrom;
|
||||
|
||||
#[test]
|
||||
fn test_basics() {
|
||||
let msg = Message::new();
|
||||
let mut req = Request::from(msg);
|
||||
req.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("text/plain"),
|
||||
);
|
||||
assert!(req.headers().contains_key(header::CONTENT_TYPE));
|
||||
|
||||
*req.uri_mut() = Uri::try_from("/index.html?q=1").unwrap();
|
||||
assert_eq!(req.uri().path(), "/index.html");
|
||||
assert_eq!(req.uri().query(), Some("q=1"));
|
||||
|
||||
let s = format!("{:?}", req);
|
||||
println!("T: {:?}", s);
|
||||
assert!(s.contains("Request HTTP/1.1 GET:/index.html"));
|
||||
}
|
||||
}
|
||||
|
@@ -860,7 +860,7 @@ mod tests {
|
||||
.domain("www.rust-lang.org")
|
||||
.path("/test")
|
||||
.http_only(true)
|
||||
.max_age(time::Duration::days(1))
|
||||
.max_age_time(time::Duration::days(1))
|
||||
.finish(),
|
||||
)
|
||||
.del_cookie(&cookies[1])
|
||||
|
@@ -1,8 +1,10 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::{fmt, io};
|
||||
use std::{fmt, io, net};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_server_config::{Io as ServerIo, Protocol, ServerConfig as SrvConfig};
|
||||
use actix_server_config::{
|
||||
Io as ServerIo, IoStream, Protocol, ServerConfig as SrvConfig,
|
||||
};
|
||||
use actix_service::{IntoNewService, NewService, Service};
|
||||
use actix_utils::cloneable::CloneableService;
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
@@ -128,7 +130,7 @@ where
|
||||
|
||||
impl<T, P, S, B, X, U> NewService<SrvConfig> for HttpService<T, P, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
@@ -182,7 +184,7 @@ pub struct HttpServiceResponse<
|
||||
|
||||
impl<T, P, S, B, X, U> Future for HttpServiceResponse<T, P, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
@@ -268,7 +270,7 @@ where
|
||||
|
||||
impl<T, P, S, B, X, U> Service for HttpServiceHandler<T, P, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
@@ -317,6 +319,7 @@ where
|
||||
let (io, _, proto) = req.into_parts();
|
||||
match proto {
|
||||
Protocol::Http2 => {
|
||||
let peer_addr = io.peer_addr();
|
||||
let io = Io {
|
||||
inner: io,
|
||||
unread: None,
|
||||
@@ -326,6 +329,7 @@ where
|
||||
server::handshake(io),
|
||||
self.cfg.clone(),
|
||||
self.srv.clone(),
|
||||
peer_addr,
|
||||
))),
|
||||
}
|
||||
}
|
||||
@@ -357,7 +361,7 @@ where
|
||||
S: Service<Request = Request>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Error>,
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
@@ -376,12 +380,19 @@ where
|
||||
Option<CloneableService<U>>,
|
||||
)>,
|
||||
),
|
||||
Handshake(Option<(Handshake<Io<T>, Bytes>, ServiceConfig, CloneableService<S>)>),
|
||||
Handshake(
|
||||
Option<(
|
||||
Handshake<Io<T>, Bytes>,
|
||||
ServiceConfig,
|
||||
CloneableService<S>,
|
||||
Option<net::SocketAddr>,
|
||||
)>,
|
||||
),
|
||||
}
|
||||
|
||||
pub struct HttpServiceHandlerResponse<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
@@ -399,7 +410,7 @@ const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
|
||||
|
||||
impl<T, S, B, X, U> Future for HttpServiceHandlerResponse<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
T: IoStream,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
@@ -437,12 +448,17 @@ where
|
||||
}
|
||||
let (io, buf, cfg, srv, expect, upgrade) = data.take().unwrap();
|
||||
if buf[..14] == HTTP2_PREFACE[..] {
|
||||
let peer_addr = io.peer_addr();
|
||||
let io = Io {
|
||||
inner: io,
|
||||
unread: Some(buf),
|
||||
};
|
||||
self.state =
|
||||
State::Handshake(Some((server::handshake(io), cfg, srv)));
|
||||
self.state = State::Handshake(Some((
|
||||
server::handshake(io),
|
||||
cfg,
|
||||
srv,
|
||||
peer_addr,
|
||||
)));
|
||||
} else {
|
||||
self.state = State::H1(h1::Dispatcher::with_timeout(
|
||||
io,
|
||||
@@ -470,8 +486,8 @@ where
|
||||
} else {
|
||||
panic!()
|
||||
};
|
||||
let (_, cfg, srv) = data.take().unwrap();
|
||||
self.state = State::H2(Dispatcher::new(srv, conn, cfg, None));
|
||||
let (_, cfg, srv, peer_addr) = data.take().unwrap();
|
||||
self.state = State::H2(Dispatcher::new(srv, conn, cfg, None, peer_addr));
|
||||
self.poll()
|
||||
}
|
||||
}
|
||||
@@ -523,3 +539,25 @@ impl<T: AsyncWrite> AsyncWrite for Io<T> {
|
||||
self.inner.write_buf(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IoStream> IoStream for Io<T> {
|
||||
#[inline]
|
||||
fn peer_addr(&self) -> Option<net::SocketAddr> {
|
||||
self.inner.peer_addr()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||
self.inner.set_nodelay(nodelay)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_linger(&mut self, dur: Option<std::time::Duration>) -> io::Result<()> {
|
||||
self.inner.set_linger(dur)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_keepalive(&mut self, dur: Option<std::time::Duration>) -> io::Result<()> {
|
||||
self.inner.set_keepalive(dur)
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ use std::io;
|
||||
use std::str::FromStr;
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_server_config::IoStream;
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
use futures::{Async, Poll};
|
||||
use http::header::{self, HeaderName, HeaderValue};
|
||||
@@ -253,3 +254,17 @@ impl AsyncWrite for TestBuffer {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
impl IoStream for TestBuffer {
|
||||
fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_linger(&mut self, _dur: Option<std::time::Duration>) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_keepalive(&mut self, _dur: Option<std::time::Duration>) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@@ -35,7 +35,10 @@ fn test_h1() {
|
||||
.keep_alive(KeepAlive::Disabled)
|
||||
.client_timeout(1000)
|
||||
.client_disconnect(1000)
|
||||
.h1(|_| future::ok::<_, ()>(Response::Ok().finish()))
|
||||
.h1(|req: Request| {
|
||||
assert!(req.peer_addr().is_some());
|
||||
future::ok::<_, ()>(Response::Ok().finish())
|
||||
})
|
||||
});
|
||||
|
||||
let response = srv.block_on(srv.get("/").send()).unwrap();
|
||||
@@ -50,6 +53,7 @@ fn test_h1_2() {
|
||||
.client_timeout(1000)
|
||||
.client_disconnect(1000)
|
||||
.finish(|req: Request| {
|
||||
assert!(req.peer_addr().is_some());
|
||||
assert_eq!(req.version(), http::Version::HTTP_11);
|
||||
future::ok::<_, ()>(Response::Ok().finish())
|
||||
})
|
||||
@@ -115,6 +119,7 @@ fn test_h2_1() -> std::io::Result<()> {
|
||||
.and_then(
|
||||
HttpService::build()
|
||||
.finish(|req: Request| {
|
||||
assert!(req.peer_addr().is_some());
|
||||
assert_eq!(req.version(), http::Version::HTTP_2);
|
||||
future::ok::<_, Error>(Response::Ok().finish())
|
||||
})
|
||||
|
@@ -1,5 +1,9 @@
|
||||
# Changes
|
||||
|
||||
## [0.1.0-alpha.1] - 2019-04-xx
|
||||
## [0.1.0-beta.1] - 2019-04-21
|
||||
|
||||
* Do not support nested multipart
|
||||
|
||||
* Split multipart support to separate crate
|
||||
|
||||
* Optimize multipart handling #634, #769
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-multipart"
|
||||
version = "0.1.0-alpha.1"
|
||||
version = "0.1.0-beta.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Multipart support for actix web framework."
|
||||
readme = "README.md"
|
||||
@@ -18,8 +18,8 @@ name = "actix_multipart"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-web = "1.0.0-alpha.3"
|
||||
actix-service = "0.3.4"
|
||||
actix-web = "1.0.0-beta.1"
|
||||
actix-service = "0.3.6"
|
||||
bytes = "0.4"
|
||||
derive_more = "0.14"
|
||||
httparse = "1.3"
|
||||
@@ -31,4 +31,4 @@ twoway = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "0.2.2"
|
||||
actix-http = "0.1.0-alpha.3"
|
||||
actix-http = "0.1.1"
|
@@ -16,6 +16,9 @@ pub enum MultipartError {
|
||||
/// Multipart boundary is not found
|
||||
#[display(fmt = "Multipart boundary is not found")]
|
||||
Boundary,
|
||||
/// Nested multipart is not supported
|
||||
#[display(fmt = "Nested multipart is not supported")]
|
||||
Nested,
|
||||
/// Multipart stream is incomplete
|
||||
#[display(fmt = "Multipart stream is incomplete")]
|
||||
Incomplete,
|
||||
|
@@ -1,9 +1,5 @@
|
||||
//! Multipart payload support
|
||||
use bytes::Bytes;
|
||||
use futures::Stream;
|
||||
|
||||
use actix_web::error::{Error, PayloadError};
|
||||
use actix_web::{dev::Payload, FromRequest, HttpRequest};
|
||||
use actix_web::{dev::Payload, Error, FromRequest, HttpRequest};
|
||||
|
||||
use crate::server::Multipart;
|
||||
|
||||
@@ -21,34 +17,26 @@ use crate::server::Multipart;
|
||||
///
|
||||
/// fn index(payload: mp::Multipart) -> impl Future<Item = HttpResponse, Error = Error> {
|
||||
/// payload.from_err() // <- get multipart stream for current request
|
||||
/// .and_then(|item| match item { // <- iterate over multipart items
|
||||
/// mp::Item::Field(field) => {
|
||||
/// .and_then(|field| { // <- iterate over multipart items
|
||||
/// // Field in turn is stream of *Bytes* object
|
||||
/// Either::A(field.from_err()
|
||||
/// field.from_err()
|
||||
/// .fold((), |_, chunk| {
|
||||
/// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk));
|
||||
/// Ok::<_, Error>(())
|
||||
/// }))
|
||||
/// },
|
||||
/// mp::Item::Nested(mp) => {
|
||||
/// // Or item could be nested Multipart stream
|
||||
/// Either::B(ok(()))
|
||||
/// }
|
||||
/// })
|
||||
/// })
|
||||
/// .fold((), |_, _| Ok::<_, Error>(()))
|
||||
/// .map(|_| HttpResponse::Ok().into())
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
impl<P> FromRequest<P> for Multipart
|
||||
where
|
||||
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
{
|
||||
impl FromRequest for Multipart {
|
||||
type Error = Error;
|
||||
type Future = Result<Multipart, Error>;
|
||||
type Config = ();
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
Ok(Multipart::new(req.headers(), payload.take()))
|
||||
}
|
||||
}
|
||||
|
@@ -3,4 +3,4 @@ mod extractor;
|
||||
mod server;
|
||||
|
||||
pub use self::error::MultipartError;
|
||||
pub use self::server::{Field, Item, Multipart};
|
||||
pub use self::server::{Field, Multipart};
|
||||
|
@@ -32,18 +32,9 @@ pub struct Multipart {
|
||||
inner: Option<Rc<RefCell<InnerMultipart>>>,
|
||||
}
|
||||
|
||||
/// Multipart item
|
||||
pub enum Item {
|
||||
/// Multipart field
|
||||
Field(Field),
|
||||
/// Nested multipart stream
|
||||
Nested(Multipart),
|
||||
}
|
||||
|
||||
enum InnerMultipartItem {
|
||||
None,
|
||||
Field(Rc<RefCell<InnerField>>),
|
||||
Multipart(Rc<RefCell<InnerMultipart>>),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
@@ -113,7 +104,7 @@ impl Multipart {
|
||||
}
|
||||
|
||||
impl Stream for Multipart {
|
||||
type Item = Item;
|
||||
type Item = Field;
|
||||
type Error = MultipartError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
@@ -177,7 +168,7 @@ impl InnerMultipart {
|
||||
match payload.readline() {
|
||||
None => {
|
||||
if payload.eof {
|
||||
Err(MultipartError::Incomplete)
|
||||
Ok(Some(true))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
@@ -210,8 +201,7 @@ impl InnerMultipart {
|
||||
match payload.readline() {
|
||||
Some(chunk) => {
|
||||
if chunk.is_empty() {
|
||||
//ValueError("Could not find starting boundary %r"
|
||||
//% (self._boundary))
|
||||
return Err(MultipartError::Boundary);
|
||||
}
|
||||
if chunk.len() < boundary.len() {
|
||||
continue;
|
||||
@@ -245,7 +235,7 @@ impl InnerMultipart {
|
||||
Ok(Some(eof))
|
||||
}
|
||||
|
||||
fn poll(&mut self, safety: &Safety) -> Poll<Option<Item>, MultipartError> {
|
||||
fn poll(&mut self, safety: &Safety) -> Poll<Option<Field>, MultipartError> {
|
||||
if self.state == InnerState::Eof {
|
||||
Ok(Async::Ready(None))
|
||||
} else {
|
||||
@@ -262,14 +252,7 @@ impl InnerMultipart {
|
||||
Async::Ready(None) => true,
|
||||
}
|
||||
}
|
||||
InnerMultipartItem::Multipart(ref mut multipart) => {
|
||||
match multipart.borrow_mut().poll(safety)? {
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
Async::Ready(Some(_)) => continue,
|
||||
Async::Ready(None) => true,
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
InnerMultipartItem::None => false,
|
||||
};
|
||||
if stop {
|
||||
self.item = InnerMultipartItem::None;
|
||||
@@ -346,24 +329,7 @@ impl InnerMultipart {
|
||||
|
||||
// nested multipart stream
|
||||
if mt.type_() == mime::MULTIPART {
|
||||
let inner = if let Some(boundary) = mt.get_param(mime::BOUNDARY) {
|
||||
Rc::new(RefCell::new(InnerMultipart {
|
||||
payload: self.payload.clone(),
|
||||
boundary: boundary.as_str().to_owned(),
|
||||
state: InnerState::FirstBoundary,
|
||||
item: InnerMultipartItem::None,
|
||||
}))
|
||||
} else {
|
||||
return Err(MultipartError::Boundary);
|
||||
};
|
||||
|
||||
self.item = InnerMultipartItem::Multipart(Rc::clone(&inner));
|
||||
|
||||
Ok(Async::Ready(Some(Item::Nested(Multipart {
|
||||
safety: safety.clone(),
|
||||
error: None,
|
||||
inner: Some(inner),
|
||||
}))))
|
||||
Err(MultipartError::Nested)
|
||||
} else {
|
||||
let field = Rc::new(RefCell::new(InnerField::new(
|
||||
self.payload.clone(),
|
||||
@@ -372,12 +338,12 @@ impl InnerMultipart {
|
||||
)?));
|
||||
self.item = InnerMultipartItem::Field(Rc::clone(&field));
|
||||
|
||||
Ok(Async::Ready(Some(Item::Field(Field::new(
|
||||
Ok(Async::Ready(Some(Field::new(
|
||||
safety.clone(),
|
||||
headers,
|
||||
mt,
|
||||
field,
|
||||
)))))
|
||||
))))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -538,47 +504,72 @@ impl InnerField {
|
||||
payload: &mut PayloadBuffer,
|
||||
boundary: &str,
|
||||
) -> Poll<Option<Bytes>, MultipartError> {
|
||||
match payload.read_until(b"\r") {
|
||||
None => {
|
||||
if payload.eof {
|
||||
Err(MultipartError::Incomplete)
|
||||
let mut pos = 0;
|
||||
|
||||
let len = payload.buf.len();
|
||||
if len == 0 {
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
|
||||
// check boundary
|
||||
if len > 4 && payload.buf[0] == b'\r' {
|
||||
let b_len = if &payload.buf[..2] == b"\r\n" && &payload.buf[2..4] == b"--" {
|
||||
Some(4)
|
||||
} else if &payload.buf[1..3] == b"--" {
|
||||
Some(3)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(b_len) = b_len {
|
||||
let b_size = boundary.len() + b_len;
|
||||
if len < b_size {
|
||||
return Ok(Async::NotReady);
|
||||
} else {
|
||||
if &payload.buf[b_len..b_size] == boundary.as_bytes() {
|
||||
// found boundary
|
||||
return Ok(Async::Ready(None));
|
||||
} else {
|
||||
pos = b_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
return if let Some(idx) = twoway::find_bytes(&payload.buf[pos..], b"\r") {
|
||||
let cur = pos + idx;
|
||||
|
||||
// check if we have enough data for boundary detection
|
||||
if cur + 4 > len {
|
||||
if cur > 0 {
|
||||
Ok(Async::Ready(Some(payload.buf.split_to(cur).freeze())))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
Some(mut chunk) => {
|
||||
if chunk.len() == 1 {
|
||||
payload.unprocessed(chunk);
|
||||
match payload.read_exact(boundary.len() + 4) {
|
||||
None => {
|
||||
if payload.eof {
|
||||
Err(MultipartError::Incomplete)
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
Some(mut chunk) => {
|
||||
if &chunk[..2] == b"\r\n"
|
||||
&& &chunk[2..4] == b"--"
|
||||
&& &chunk[4..] == boundary.as_bytes()
|
||||
// check boundary
|
||||
if (&payload.buf[cur..cur + 2] == b"\r\n"
|
||||
&& &payload.buf[cur + 2..cur + 4] == b"--")
|
||||
|| (&payload.buf[cur..cur + 1] == b"\r"
|
||||
&& &payload.buf[cur + 1..cur + 3] == b"--")
|
||||
{
|
||||
payload.unprocessed(chunk);
|
||||
Ok(Async::Ready(None))
|
||||
if cur != 0 {
|
||||
// return buffer
|
||||
Ok(Async::Ready(Some(payload.buf.split_to(cur).freeze())))
|
||||
} else {
|
||||
// \r might be part of data stream
|
||||
let ch = chunk.split_to(1);
|
||||
payload.unprocessed(chunk);
|
||||
Ok(Async::Ready(Some(ch)))
|
||||
pos = cur + 1;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// not boundary
|
||||
pos = cur + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let to = chunk.len() - 1;
|
||||
let ch = chunk.split_to(to);
|
||||
payload.unprocessed(chunk);
|
||||
Ok(Async::Ready(Some(ch)))
|
||||
}
|
||||
}
|
||||
return Ok(Async::Ready(Some(payload.buf.take().freeze())));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -588,6 +579,7 @@ impl InnerField {
|
||||
}
|
||||
|
||||
let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) {
|
||||
if !self.eof {
|
||||
let res = if let Some(ref mut len) = self.length {
|
||||
InnerField::read_len(payload, len)?
|
||||
} else {
|
||||
@@ -595,10 +587,12 @@ impl InnerField {
|
||||
};
|
||||
|
||||
match res {
|
||||
Async::NotReady => Async::NotReady,
|
||||
Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)),
|
||||
Async::Ready(None) => {
|
||||
self.eof = true;
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
Async::Ready(Some(bytes)) => return Ok(Async::Ready(Some(bytes))),
|
||||
Async::Ready(None) => self.eof = true,
|
||||
}
|
||||
}
|
||||
|
||||
match payload.readline() {
|
||||
None => Async::Ready(None),
|
||||
Some(line) => {
|
||||
@@ -608,8 +602,6 @@ impl InnerField {
|
||||
Async::Ready(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Async::NotReady
|
||||
};
|
||||
@@ -737,7 +729,7 @@ impl PayloadBuffer {
|
||||
}
|
||||
|
||||
/// Read exact number of bytes
|
||||
#[inline]
|
||||
#[cfg(test)]
|
||||
fn read_exact(&mut self, size: usize) -> Option<Bytes> {
|
||||
if size <= self.buf.len() {
|
||||
Some(self.buf.split_to(size).freeze())
|
||||
@@ -864,16 +856,11 @@ mod tests {
|
||||
|
||||
let mut multipart = Multipart::new(&headers, payload);
|
||||
match multipart.poll().unwrap() {
|
||||
Async::Ready(Some(item)) => match item {
|
||||
Item::Field(mut field) => {
|
||||
{
|
||||
Async::Ready(Some(mut field)) => {
|
||||
let cd = field.content_disposition().unwrap();
|
||||
assert_eq!(cd.disposition, DispositionType::FormData);
|
||||
assert_eq!(
|
||||
cd.parameters[0],
|
||||
DispositionParam::Name("file".into())
|
||||
);
|
||||
}
|
||||
assert_eq!(cd.parameters[0], DispositionParam::Name("file".into()));
|
||||
|
||||
assert_eq!(field.content_type().type_(), mime::TEXT);
|
||||
assert_eq!(field.content_type().subtype(), mime::PLAIN);
|
||||
|
||||
@@ -887,13 +874,10 @@ mod tests {
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match multipart.poll().unwrap() {
|
||||
Async::Ready(Some(item)) => match item {
|
||||
Item::Field(mut field) => {
|
||||
Async::Ready(Some(mut field)) => {
|
||||
assert_eq!(field.content_type().type_(), mime::TEXT);
|
||||
assert_eq!(field.content_type().subtype(), mime::PLAIN);
|
||||
|
||||
@@ -907,7 +891,77 @@ mod tests {
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
}
|
||||
|
||||
match multipart.poll().unwrap() {
|
||||
Async::Ready(None) => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stream() {
|
||||
run_on(|| {
|
||||
let (sender, payload) = create_stream();
|
||||
|
||||
let bytes = Bytes::from(
|
||||
"testasdadsad\r\n\
|
||||
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
|
||||
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
|
||||
Content-Type: text/plain; charset=utf-8\r\n\r\n\
|
||||
test\r\n\
|
||||
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
|
||||
Content-Type: text/plain; charset=utf-8\r\n\r\n\
|
||||
data\r\n\
|
||||
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n",
|
||||
);
|
||||
sender.unbounded_send(Ok(bytes)).unwrap();
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static(
|
||||
"multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
|
||||
),
|
||||
);
|
||||
|
||||
let mut multipart = Multipart::new(&headers, payload);
|
||||
match multipart.poll().unwrap() {
|
||||
Async::Ready(Some(mut field)) => {
|
||||
let cd = field.content_disposition().unwrap();
|
||||
assert_eq!(cd.disposition, DispositionType::FormData);
|
||||
assert_eq!(cd.parameters[0], DispositionParam::Name("file".into()));
|
||||
|
||||
assert_eq!(field.content_type().type_(), mime::TEXT);
|
||||
assert_eq!(field.content_type().subtype(), mime::PLAIN);
|
||||
|
||||
match field.poll().unwrap() {
|
||||
Async::Ready(Some(chunk)) => assert_eq!(chunk, "test"),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
match field.poll().unwrap() {
|
||||
Async::Ready(None) => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match multipart.poll().unwrap() {
|
||||
Async::Ready(Some(mut field)) => {
|
||||
assert_eq!(field.content_type().type_(), mime::TEXT);
|
||||
assert_eq!(field.content_type().subtype(), mime::PLAIN);
|
||||
|
||||
match field.poll() {
|
||||
Ok(Async::Ready(Some(chunk))) => assert_eq!(chunk, "data"),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
match field.poll() {
|
||||
Ok(Async::Ready(None)) => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,15 @@
|
||||
# Changes
|
||||
|
||||
## [0.1.0-beta.1] - 2019-04-20
|
||||
|
||||
* Update actix-web to beta.1
|
||||
|
||||
* `CookieSession::max_age()` accepts value in seconds
|
||||
|
||||
## [0.1.0-alpha.6] - 2019-04-14
|
||||
|
||||
* Update actix-web alpha.6
|
||||
|
||||
## [0.1.0-alpha.4] - 2019-04-08
|
||||
|
||||
* Update actix-web
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-session"
|
||||
version = "0.1.0-alpha.4"
|
||||
version = "0.1.0-beta.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Session for actix web framework."
|
||||
readme = "README.md"
|
||||
@@ -24,15 +24,15 @@ default = ["cookie-session"]
|
||||
cookie-session = ["actix-web/secure-cookies"]
|
||||
|
||||
[dependencies]
|
||||
actix-web = "1.0.0-alpha.5"
|
||||
actix-web = "1.0.0-beta.1"
|
||||
actix-service = "0.3.4"
|
||||
bytes = "0.4"
|
||||
derive_more = "0.14"
|
||||
futures = "0.1.25"
|
||||
hashbrown = "0.2.0"
|
||||
hashbrown = "0.2.2"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
time = "0.1"
|
||||
time = "0.1.42"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "0.2.2"
|
||||
|
@@ -27,7 +27,6 @@ use derive_more::{Display, From};
|
||||
use futures::future::{ok, Future, FutureResult};
|
||||
use futures::Poll;
|
||||
use serde_json::error::Error as JsonError;
|
||||
use time::Duration;
|
||||
|
||||
use crate::Session;
|
||||
|
||||
@@ -57,7 +56,7 @@ struct CookieSessionInner {
|
||||
domain: Option<String>,
|
||||
secure: bool,
|
||||
http_only: bool,
|
||||
max_age: Option<Duration>,
|
||||
max_age: Option<time::Duration>,
|
||||
same_site: Option<SameSite>,
|
||||
}
|
||||
|
||||
@@ -120,7 +119,7 @@ impl CookieSessionInner {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load<P>(&self, req: &ServiceRequest<P>) -> HashMap<String, String> {
|
||||
fn load(&self, req: &ServiceRequest) -> HashMap<String, String> {
|
||||
if let Ok(cookies) = req.cookies() {
|
||||
for cookie in cookies.iter() {
|
||||
if cookie.name() == self.name {
|
||||
@@ -250,19 +249,24 @@ impl CookieSession {
|
||||
}
|
||||
|
||||
/// Sets the `max-age` field in the session cookie being built.
|
||||
pub fn max_age(mut self, value: Duration) -> CookieSession {
|
||||
pub fn max_age(self, seconds: i64) -> CookieSession {
|
||||
self.max_age_time(time::Duration::seconds(seconds))
|
||||
}
|
||||
|
||||
/// Sets the `max-age` field in the session cookie being built.
|
||||
pub fn max_age_time(mut self, value: time::Duration) -> CookieSession {
|
||||
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, P, B: 'static> Transform<S> for CookieSession
|
||||
impl<S, B: 'static> Transform<S> for CookieSession
|
||||
where
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = S::Error;
|
||||
type InitError = ();
|
||||
@@ -283,13 +287,13 @@ pub struct CookieSessionMiddleware<S> {
|
||||
inner: Rc<CookieSessionInner>,
|
||||
}
|
||||
|
||||
impl<S, P, B: 'static> Service for CookieSessionMiddleware<S>
|
||||
impl<S, B: 'static> Service for CookieSessionMiddleware<S>
|
||||
where
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = S::Error;
|
||||
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
|
||||
@@ -298,7 +302,7 @@ where
|
||||
self.service.poll_ready()
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future {
|
||||
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
||||
let inner = self.inner.clone();
|
||||
let state = self.inner.load(&req);
|
||||
Session::set_session(state.into_iter(), &mut req);
|
||||
@@ -317,6 +321,7 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
use actix_web::{test, web, App};
|
||||
use bytes::Bytes;
|
||||
|
||||
#[test]
|
||||
fn cookie_session() {
|
||||
@@ -338,6 +343,26 @@ mod tests {
|
||||
.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_cookie() {
|
||||
let mut app = test::init_service(
|
||||
App::new()
|
||||
.wrap(CookieSession::private(&[0; 32]).secure(false))
|
||||
.service(web::resource("/").to(|ses: Session| {
|
||||
let _ = ses.set("counter", 100);
|
||||
"test"
|
||||
})),
|
||||
);
|
||||
|
||||
let request = test::TestRequest::get().to_request();
|
||||
let response = test::block_on(app.call(request)).unwrap();
|
||||
assert!(response
|
||||
.response()
|
||||
.cookies()
|
||||
.find(|c| c.name() == "actix-session")
|
||||
.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cookie_session_extractor() {
|
||||
let mut app = test::init_service(
|
||||
@@ -357,4 +382,44 @@ mod tests {
|
||||
.find(|c| c.name() == "actix-session")
|
||||
.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basics() {
|
||||
let mut app = test::init_service(
|
||||
App::new()
|
||||
.wrap(
|
||||
CookieSession::signed(&[0; 32])
|
||||
.path("/test/")
|
||||
.name("actix-test")
|
||||
.domain("localhost")
|
||||
.http_only(true)
|
||||
.same_site(SameSite::Lax)
|
||||
.max_age(100),
|
||||
)
|
||||
.service(web::resource("/").to(|ses: Session| {
|
||||
let _ = ses.set("counter", 100);
|
||||
"test"
|
||||
}))
|
||||
.service(web::resource("/test/").to(|ses: Session| {
|
||||
let val: usize = ses.get("counter").unwrap().unwrap();
|
||||
format!("counter: {}", val)
|
||||
})),
|
||||
);
|
||||
|
||||
let request = test::TestRequest::get().to_request();
|
||||
let response = test::block_on(app.call(request)).unwrap();
|
||||
let cookie = response
|
||||
.response()
|
||||
.cookies()
|
||||
.find(|c| c.name() == "actix-test")
|
||||
.unwrap()
|
||||
.clone();
|
||||
assert_eq!(cookie.path().unwrap(), "/test/");
|
||||
|
||||
let request = test::TestRequest::with_uri("/test/")
|
||||
.cookie(cookie)
|
||||
.to_request();
|
||||
let body = test::read_response(&mut app, request);
|
||||
assert_eq!(body, Bytes::from_static(b"counter: 100"));
|
||||
}
|
||||
}
|
||||
|
@@ -119,9 +119,9 @@ impl Session {
|
||||
inner.state.clear()
|
||||
}
|
||||
|
||||
pub fn set_session<P>(
|
||||
pub fn set_session(
|
||||
data: impl Iterator<Item = (String, String)>,
|
||||
req: &mut ServiceRequest<P>,
|
||||
req: &mut ServiceRequest,
|
||||
) {
|
||||
let session = Session::get_session(&mut *req.extensions_mut());
|
||||
let mut inner = session.0.borrow_mut();
|
||||
@@ -172,12 +172,13 @@ impl Session {
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
impl<P> FromRequest<P> for Session {
|
||||
impl FromRequest for Session {
|
||||
type Error = Error;
|
||||
type Future = Result<Session, Error>;
|
||||
type Config = ();
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
Ok(Session::get_session(&mut *req.extensions_mut()))
|
||||
}
|
||||
}
|
||||
|
@@ -18,13 +18,13 @@ name = "actix_web_actors"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix = "0.8.0-alpha.3"
|
||||
actix-web = "1.0.0-alpha.5"
|
||||
actix-http = "0.1.0-alpha.5"
|
||||
actix = "0.8.0"
|
||||
actix-web = "1.0.0-beta.1"
|
||||
actix-http = "0.1.1"
|
||||
actix-codec = "0.1.2"
|
||||
bytes = "0.4"
|
||||
futures = "0.1.25"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.6"
|
||||
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
|
||||
actix-http-test = { version = "0.1.0", features=["ssl"] }
|
||||
|
@@ -199,7 +199,7 @@ mod tests {
|
||||
|
||||
use actix::Actor;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::test::{block_on, call_success, init_service, TestRequest};
|
||||
use actix_web::test::{block_on, call_service, init_service, TestRequest};
|
||||
use actix_web::{web, App, HttpResponse};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
||||
@@ -237,7 +237,7 @@ mod tests {
|
||||
})));
|
||||
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let mut resp = call_success(&mut srv, req);
|
||||
let mut resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let body = block_on(resp.take_body().fold(
|
||||
|
@@ -1,5 +1,13 @@
|
||||
# Changes
|
||||
|
||||
## [0.1.0-beta.1] - 2019-04-20
|
||||
|
||||
* Gen code for actix-web 1.0.0-beta.1
|
||||
|
||||
## [0.1.0-alpha.6] - 2019-04-14
|
||||
|
||||
* Gen code for actix-web 1.0.0-alpha.6
|
||||
|
||||
## [0.1.0-alpha.1] - 2019-03-28
|
||||
|
||||
* Initial impl
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web-codegen"
|
||||
version = "0.1.0-alpha.1"
|
||||
version = "0.1.0-beta.1"
|
||||
description = "Actix web proc macros"
|
||||
readme = "README.md"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
@@ -16,7 +16,7 @@ quote = "0.6"
|
||||
syn = { version = "0.15", features = ["full", "parsing"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-web = { version = "1.0.0-alpha.5" }
|
||||
actix-http = { version = "0.1.0-alpha.5", features=["ssl"] }
|
||||
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
|
||||
actix-web = { version = "1.0.0-alpha.6" }
|
||||
actix-http = { version = "0.1.1", features=["ssl"] }
|
||||
actix-http-test = { version = "0.1.0", features=["ssl"] }
|
||||
futures = { version = "0.1" }
|
||||
|
@@ -61,8 +61,8 @@ impl fmt::Display for Args {
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct {name};
|
||||
|
||||
impl<P: 'static> actix_web::dev::HttpServiceFactory<P> for {name} {{
|
||||
fn register(self, config: &mut actix_web::dev::ServiceConfig<P>) {{
|
||||
impl actix_web::dev::HttpServiceFactory for {name} {{
|
||||
fn register(self, config: &mut actix_web::dev::AppService) {{
|
||||
{ast}
|
||||
|
||||
let resource = actix_web::Resource::new(\"{path}\"){guards}.{to}({name});
|
||||
|
@@ -4,11 +4,6 @@ use actix_web::{http, App, HttpResponse, Responder};
|
||||
use actix_web_codegen::get;
|
||||
use futures::{future, Future};
|
||||
|
||||
//fn guard_head(head: &actix_web::dev::RequestHead) -> bool {
|
||||
// true
|
||||
//}
|
||||
|
||||
//#[get("/test", guard="guard_head")]
|
||||
#[get("/test")]
|
||||
fn test() -> impl Responder {
|
||||
HttpResponse::Ok()
|
||||
|
@@ -1,5 +1,28 @@
|
||||
# Changes
|
||||
|
||||
## [0.1.1] - 2019-04-19
|
||||
|
||||
### Added
|
||||
|
||||
* Allow to specify server address for http and ws requests.
|
||||
|
||||
### Changed
|
||||
|
||||
* `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref
|
||||
|
||||
|
||||
## [0.1.0] - 2019-04-16
|
||||
|
||||
* No changes
|
||||
|
||||
|
||||
## [0.1.0-alpha.6] - 2019-04-14
|
||||
|
||||
### Changed
|
||||
|
||||
* Do not set default headers for websocket request
|
||||
|
||||
|
||||
## [0.1.0-alpha.5] - 2019-04-12
|
||||
|
||||
### Changed
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "awc"
|
||||
version = "0.1.0-alpha.5"
|
||||
version = "0.1.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix http client."
|
||||
readme = "README.md"
|
||||
@@ -38,7 +38,7 @@ flate2-rust = ["actix-http/flate2-rust"]
|
||||
[dependencies]
|
||||
actix-codec = "0.1.2"
|
||||
actix-service = "0.3.6"
|
||||
actix-http = "0.1.0-alpha.5"
|
||||
actix-http = "0.1.1"
|
||||
base64 = "0.10.1"
|
||||
bytes = "0.4"
|
||||
derive_more = "0.14"
|
||||
@@ -55,11 +55,11 @@ openssl = { version="0.10", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "0.2.2"
|
||||
actix-web = { version = "1.0.0-alpha.5", features=["ssl"] }
|
||||
actix-http = { version = "0.1.0-alpha.5", features=["ssl"] }
|
||||
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
|
||||
actix-web = { version = "1.0.0-alpha.6", features=["ssl"] }
|
||||
actix-http = { version = "0.1.1", features=["ssl"] }
|
||||
actix-http-test = { version = "0.1.0", features=["ssl"] }
|
||||
actix-utils = "0.3.4"
|
||||
actix-server = { version = "0.4.1", features=["ssl"] }
|
||||
actix-server = { version = "0.4.3", features=["ssl"] }
|
||||
brotli2 = { version="0.3.2" }
|
||||
flate2 = { version="1.0.2" }
|
||||
env_logger = "0.6"
|
||||
|
@@ -1 +1,33 @@
|
||||
# Actix http client [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/awc) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
An HTTP Client
|
||||
|
||||
## Documentation & community resources
|
||||
|
||||
* [User Guide](https://actix.rs/docs/)
|
||||
* [API Documentation](https://docs.rs/awc/)
|
||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||
* Cargo package: [awc](https://crates.io/crates/awc)
|
||||
* Minimum supported Rust version: 1.33 or later
|
||||
|
||||
## Example
|
||||
|
||||
```rust
|
||||
use actix_rt::System;
|
||||
use awc::Client;
|
||||
use futures::future::{Future, lazy};
|
||||
|
||||
fn main() {
|
||||
System::new("test").block_on(lazy(|| {
|
||||
let mut client = Client::default();
|
||||
|
||||
client.get("http://www.rust-lang.org") // <- Create request builder
|
||||
.header("User-Agent", "Actix-web")
|
||||
.send() // <- Send http request
|
||||
.and_then(|response| { // <- server http response
|
||||
println!("Response: {:?}", response);
|
||||
Ok(())
|
||||
})
|
||||
}));
|
||||
}
|
||||
```
|
||||
|
@@ -3,8 +3,8 @@ use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use actix_http::client::{ConnectError, Connection, Connector};
|
||||
use actix_http::http::{header, HeaderMap, HeaderName, HttpTryFrom, Uri};
|
||||
use actix_http::client::{Connect, ConnectError, Connection, Connector};
|
||||
use actix_http::http::{header, HeaderMap, HeaderName, HttpTryFrom};
|
||||
use actix_service::Service;
|
||||
|
||||
use crate::connect::ConnectorWrapper;
|
||||
@@ -40,7 +40,7 @@ impl ClientBuilder {
|
||||
/// Use custom connector service.
|
||||
pub fn connector<T>(mut self, connector: T) -> Self
|
||||
where
|
||||
T: Service<Request = Uri, Error = ConnectError> + 'static,
|
||||
T: Service<Request = Connect, Error = ConnectError> + 'static,
|
||||
T::Response: Connection,
|
||||
<T::Response as Connection>::Future: 'static,
|
||||
T::Future: 'static,
|
||||
|
@@ -1,10 +1,12 @@
|
||||
use std::{fmt, io};
|
||||
use std::{fmt, io, net};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_http::body::Body;
|
||||
use actix_http::client::{ConnectError, Connection, SendRequestError};
|
||||
use actix_http::client::{
|
||||
Connect as ClientConnect, ConnectError, Connection, SendRequestError,
|
||||
};
|
||||
use actix_http::h1::ClientCodec;
|
||||
use actix_http::{http, RequestHead, ResponseHead};
|
||||
use actix_http::{RequestHead, ResponseHead};
|
||||
use actix_service::Service;
|
||||
use futures::{Future, Poll};
|
||||
|
||||
@@ -17,12 +19,14 @@ pub(crate) trait Connect {
|
||||
&mut self,
|
||||
head: RequestHead,
|
||||
body: Body,
|
||||
addr: Option<net::SocketAddr>,
|
||||
) -> Box<Future<Item = ClientResponse, Error = SendRequestError>>;
|
||||
|
||||
/// Send request, returns Response and Framed
|
||||
fn open_tunnel(
|
||||
&mut self,
|
||||
head: RequestHead,
|
||||
addr: Option<net::SocketAddr>,
|
||||
) -> Box<
|
||||
Future<
|
||||
Item = (ResponseHead, Framed<BoxedSocket, ClientCodec>),
|
||||
@@ -33,7 +37,7 @@ pub(crate) trait Connect {
|
||||
|
||||
impl<T> Connect for ConnectorWrapper<T>
|
||||
where
|
||||
T: Service<Request = http::Uri, Error = ConnectError>,
|
||||
T: Service<Request = ClientConnect, Error = ConnectError>,
|
||||
T::Response: Connection,
|
||||
<T::Response as Connection>::Io: 'static,
|
||||
<T::Response as Connection>::Future: 'static,
|
||||
@@ -44,11 +48,15 @@ where
|
||||
&mut self,
|
||||
head: RequestHead,
|
||||
body: Body,
|
||||
addr: Option<net::SocketAddr>,
|
||||
) -> Box<Future<Item = ClientResponse, Error = SendRequestError>> {
|
||||
Box::new(
|
||||
self.0
|
||||
// connect to the host
|
||||
.call(head.uri.clone())
|
||||
.call(ClientConnect {
|
||||
uri: head.uri.clone(),
|
||||
addr,
|
||||
})
|
||||
.from_err()
|
||||
// send request
|
||||
.and_then(move |connection| connection.send_request(head, body))
|
||||
@@ -59,6 +67,7 @@ where
|
||||
fn open_tunnel(
|
||||
&mut self,
|
||||
head: RequestHead,
|
||||
addr: Option<net::SocketAddr>,
|
||||
) -> Box<
|
||||
Future<
|
||||
Item = (ResponseHead, Framed<BoxedSocket, ClientCodec>),
|
||||
@@ -68,7 +77,10 @@ where
|
||||
Box::new(
|
||||
self.0
|
||||
// connect to the host
|
||||
.call(head.uri.clone())
|
||||
.call(ClientConnect {
|
||||
uri: head.uri.clone(),
|
||||
addr,
|
||||
})
|
||||
.from_err()
|
||||
// send request
|
||||
.and_then(move |connection| connection.open_tunnel(head))
|
||||
|
@@ -1,8 +1,8 @@
|
||||
use std::fmt;
|
||||
use std::fmt::Write as FmtWrite;
|
||||
use std::io::Write;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
use std::{fmt, net};
|
||||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use futures::future::{err, Either};
|
||||
@@ -60,6 +60,7 @@ const HTTPS_ENCODING: &str = "gzip, deflate";
|
||||
pub struct ClientRequest {
|
||||
pub(crate) head: RequestHead,
|
||||
err: Option<HttpError>,
|
||||
addr: Option<net::SocketAddr>,
|
||||
cookies: Option<CookieJar>,
|
||||
response_decompress: bool,
|
||||
timeout: Option<Duration>,
|
||||
@@ -76,6 +77,7 @@ impl ClientRequest {
|
||||
config,
|
||||
head: RequestHead::default(),
|
||||
err: None,
|
||||
addr: None,
|
||||
cookies: None,
|
||||
timeout: None,
|
||||
response_decompress: true,
|
||||
@@ -97,6 +99,15 @@ impl ClientRequest {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set socket address of the server.
|
||||
///
|
||||
/// This address is used for connection. If address is not
|
||||
/// provided url's host name get resolved.
|
||||
pub fn address(mut self, addr: net::SocketAddr) -> Self {
|
||||
self.addr = Some(addr);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set HTTP method of this request.
|
||||
#[inline]
|
||||
pub fn method(mut self, method: Method) -> Self {
|
||||
@@ -331,27 +342,29 @@ impl ClientRequest {
|
||||
|
||||
/// This method calls provided closure with builder reference if
|
||||
/// value is `true`.
|
||||
pub fn if_true<F>(mut self, value: bool, f: F) -> Self
|
||||
pub fn if_true<F>(self, value: bool, f: F) -> Self
|
||||
where
|
||||
F: FnOnce(&mut ClientRequest),
|
||||
F: FnOnce(ClientRequest) -> ClientRequest,
|
||||
{
|
||||
if value {
|
||||
f(&mut self);
|
||||
}
|
||||
f(self)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// This method calls provided closure with builder reference if
|
||||
/// value is `Some`.
|
||||
pub fn if_some<T, F>(mut self, value: Option<T>, f: F) -> Self
|
||||
pub fn if_some<T, F>(self, value: Option<T>, f: F) -> Self
|
||||
where
|
||||
F: FnOnce(T, &mut ClientRequest),
|
||||
F: FnOnce(T, ClientRequest) -> ClientRequest,
|
||||
{
|
||||
if let Some(val) = value {
|
||||
f(val, &mut self);
|
||||
}
|
||||
f(val, self)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Complete request construction and send body.
|
||||
pub fn send_body<B>(
|
||||
@@ -433,7 +446,7 @@ impl ClientRequest {
|
||||
let fut = config
|
||||
.connector
|
||||
.borrow_mut()
|
||||
.send_request(head, body.into())
|
||||
.send_request(head, body.into(), slf.addr)
|
||||
.map(move |res| {
|
||||
res.map_body(|head, payload| {
|
||||
if response_decompress {
|
||||
@@ -544,22 +557,48 @@ impl fmt::Debug for ClientRequest {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::SystemTime;
|
||||
|
||||
use super::*;
|
||||
use crate::{test, Client};
|
||||
use crate::Client;
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
test::run_on(|| {
|
||||
let request = Client::new().get("/").header("x-test", "111");
|
||||
let repr = format!("{:?}", request);
|
||||
assert!(repr.contains("ClientRequest"));
|
||||
assert!(repr.contains("x-test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basics() {
|
||||
let mut req = Client::new()
|
||||
.put("/")
|
||||
.version(Version::HTTP_2)
|
||||
.set(header::Date(SystemTime::now().into()))
|
||||
.content_type("plain/text")
|
||||
.if_true(true, |req| req.header(header::SERVER, "awc"))
|
||||
.if_true(false, |req| req.header(header::EXPECT, "awc"))
|
||||
.if_some(Some("server"), |val, req| {
|
||||
req.header(header::USER_AGENT, val)
|
||||
})
|
||||
.if_some(Option::<&str>::None, |_, req| {
|
||||
req.header(header::ALLOW, "1")
|
||||
})
|
||||
.content_length(100);
|
||||
assert!(req.headers().contains_key(header::CONTENT_TYPE));
|
||||
assert!(req.headers().contains_key(header::DATE));
|
||||
assert!(req.headers().contains_key(header::SERVER));
|
||||
assert!(req.headers().contains_key(header::USER_AGENT));
|
||||
assert!(!req.headers().contains_key(header::ALLOW));
|
||||
assert!(!req.headers().contains_key(header::EXPECT));
|
||||
assert_eq!(req.head.version, Version::HTTP_2);
|
||||
let _ = req.headers_mut();
|
||||
let _ = req.send_body("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_header() {
|
||||
test::run_on(|| {
|
||||
let req = Client::build()
|
||||
.header(header::CONTENT_TYPE, "111")
|
||||
.finish()
|
||||
@@ -574,12 +613,10 @@ mod tests {
|
||||
.unwrap(),
|
||||
"111"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_header_override() {
|
||||
test::run_on(|| {
|
||||
let req = Client::build()
|
||||
.header(header::CONTENT_TYPE, "111")
|
||||
.finish()
|
||||
@@ -595,12 +632,10 @@ mod tests {
|
||||
.unwrap(),
|
||||
"222"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_basic_auth() {
|
||||
test::run_on(|| {
|
||||
let req = Client::new()
|
||||
.get("/")
|
||||
.basic_auth("username", Some("password"));
|
||||
@@ -624,12 +659,10 @@ mod tests {
|
||||
.unwrap(),
|
||||
"Basic dXNlcm5hbWU="
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_bearer_auth() {
|
||||
test::run_on(|| {
|
||||
let req = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n");
|
||||
assert_eq!(
|
||||
req.head
|
||||
@@ -640,6 +673,5 @@ mod tests {
|
||||
.unwrap(),
|
||||
"Bearer someS3cr3tAutht0k3n"
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -134,3 +134,23 @@ impl TestResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::SystemTime;
|
||||
|
||||
use super::*;
|
||||
use crate::{cookie, http::header};
|
||||
|
||||
#[test]
|
||||
fn test_basics() {
|
||||
let res = TestResponse::default()
|
||||
.version(Version::HTTP_2)
|
||||
.set(header::Date(SystemTime::now().into()))
|
||||
.cookie(cookie::Cookie::build("name", "value").finish())
|
||||
.finish();
|
||||
assert!(res.headers().contains_key(header::SET_COOKIE));
|
||||
assert!(res.headers().contains_key(header::DATE));
|
||||
assert_eq!(res.version(), Version::HTTP_2);
|
||||
}
|
||||
}
|
||||
|
215
awc/src/ws.rs
215
awc/src/ws.rs
@@ -1,13 +1,12 @@
|
||||
//! Websockets client
|
||||
use std::fmt::Write as FmtWrite;
|
||||
use std::io::Write;
|
||||
use std::net::SocketAddr;
|
||||
use std::rc::Rc;
|
||||
use std::{fmt, str};
|
||||
|
||||
use actix_codec::Framed;
|
||||
use actix_http::cookie::{Cookie, CookieJar};
|
||||
use actix_http::{ws, Payload, RequestHead};
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures::future::{err, Either, Future};
|
||||
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
|
||||
use tokio_timer::Timeout;
|
||||
@@ -31,9 +30,9 @@ pub struct WebsocketsRequest {
|
||||
err: Option<HttpError>,
|
||||
origin: Option<HeaderValue>,
|
||||
protocols: Option<String>,
|
||||
addr: Option<SocketAddr>,
|
||||
max_size: usize,
|
||||
server_mode: bool,
|
||||
default_headers: bool,
|
||||
cookies: Option<CookieJar>,
|
||||
config: Rc<ClientConfig>,
|
||||
}
|
||||
@@ -58,15 +57,24 @@ impl WebsocketsRequest {
|
||||
head,
|
||||
err,
|
||||
config,
|
||||
addr: None,
|
||||
origin: None,
|
||||
protocols: None,
|
||||
max_size: 65_536,
|
||||
server_mode: false,
|
||||
cookies: None,
|
||||
default_headers: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set socket address of the server.
|
||||
///
|
||||
/// This address is used for connection. If address is not
|
||||
/// provided url's host name get resolved.
|
||||
pub fn address(mut self, addr: SocketAddr) -> Self {
|
||||
self.addr = Some(addr);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set supported websocket protocols
|
||||
pub fn protocols<U, V>(mut self, protos: U) -> Self
|
||||
where
|
||||
@@ -119,13 +127,6 @@ impl WebsocketsRequest {
|
||||
self
|
||||
}
|
||||
|
||||
/// Do not add default request headers.
|
||||
/// By default `Date` and `User-Agent` headers are set.
|
||||
pub fn no_default_headers(mut self) -> Self {
|
||||
self.default_headers = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Append a header.
|
||||
///
|
||||
/// Header gets appended to existing header.
|
||||
@@ -188,10 +189,9 @@ impl WebsocketsRequest {
|
||||
}
|
||||
|
||||
/// Set HTTP basic authorization header
|
||||
pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> Self
|
||||
pub fn basic_auth<U>(self, username: U, password: Option<&str>) -> Self
|
||||
where
|
||||
U: fmt::Display,
|
||||
P: fmt::Display,
|
||||
{
|
||||
let auth = match password {
|
||||
Some(password) => format!("{}:{}", username, password),
|
||||
@@ -232,67 +232,36 @@ impl WebsocketsRequest {
|
||||
return Either::A(err(InvalidUrl::UnknownScheme.into()));
|
||||
}
|
||||
|
||||
// set default headers
|
||||
let mut slf = if self.default_headers {
|
||||
// set request host header
|
||||
if let Some(host) = self.head.uri.host() {
|
||||
if !self.head.headers.contains_key(&header::HOST) {
|
||||
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
|
||||
|
||||
let _ = match self.head.uri.port_u16() {
|
||||
None | Some(80) | Some(443) => write!(wrt, "{}", host),
|
||||
Some(port) => write!(wrt, "{}:{}", host, port),
|
||||
};
|
||||
|
||||
match wrt.get_mut().take().freeze().try_into() {
|
||||
Ok(value) => {
|
||||
self.head.headers.insert(header::HOST, value);
|
||||
}
|
||||
Err(e) => return Either::A(err(HttpError::from(e).into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// user agent
|
||||
self.set_header_if_none(
|
||||
header::USER_AGENT,
|
||||
concat!("awc/", env!("CARGO_PKG_VERSION")),
|
||||
)
|
||||
} else {
|
||||
self
|
||||
};
|
||||
|
||||
let mut head = slf.head;
|
||||
|
||||
// set cookies
|
||||
if let Some(ref mut jar) = slf.cookies {
|
||||
if let Some(ref mut jar) = self.cookies {
|
||||
let mut cookie = String::new();
|
||||
for c in jar.delta() {
|
||||
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
|
||||
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
|
||||
let _ = write!(&mut cookie, "; {}={}", name, value);
|
||||
}
|
||||
head.headers.insert(
|
||||
self.head.headers.insert(
|
||||
header::COOKIE,
|
||||
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
// origin
|
||||
if let Some(origin) = slf.origin.take() {
|
||||
head.headers.insert(header::ORIGIN, origin);
|
||||
if let Some(origin) = self.origin.take() {
|
||||
self.head.headers.insert(header::ORIGIN, origin);
|
||||
}
|
||||
|
||||
head.set_connection_type(ConnectionType::Upgrade);
|
||||
head.headers
|
||||
self.head.set_connection_type(ConnectionType::Upgrade);
|
||||
self.head
|
||||
.headers
|
||||
.insert(header::UPGRADE, HeaderValue::from_static("websocket"));
|
||||
head.headers.insert(
|
||||
self.head.headers.insert(
|
||||
header::SEC_WEBSOCKET_VERSION,
|
||||
HeaderValue::from_static("13"),
|
||||
);
|
||||
|
||||
if let Some(protocols) = slf.protocols.take() {
|
||||
head.headers.insert(
|
||||
if let Some(protocols) = self.protocols.take() {
|
||||
self.head.headers.insert(
|
||||
header::SEC_WEBSOCKET_PROTOCOL,
|
||||
HeaderValue::try_from(protocols.as_str()).unwrap(),
|
||||
);
|
||||
@@ -304,19 +273,20 @@ impl WebsocketsRequest {
|
||||
let sec_key: [u8; 16] = rand::random();
|
||||
let key = base64::encode(&sec_key);
|
||||
|
||||
head.headers.insert(
|
||||
self.head.headers.insert(
|
||||
header::SEC_WEBSOCKET_KEY,
|
||||
HeaderValue::try_from(key.as_str()).unwrap(),
|
||||
);
|
||||
|
||||
let max_size = slf.max_size;
|
||||
let server_mode = slf.server_mode;
|
||||
let head = self.head;
|
||||
let max_size = self.max_size;
|
||||
let server_mode = self.server_mode;
|
||||
|
||||
let fut = slf
|
||||
let fut = self
|
||||
.config
|
||||
.connector
|
||||
.borrow_mut()
|
||||
.open_tunnel(head)
|
||||
.open_tunnel(head, self.addr)
|
||||
.from_err()
|
||||
.and_then(move |(head, framed)| {
|
||||
// verify response
|
||||
@@ -387,7 +357,7 @@ impl WebsocketsRequest {
|
||||
});
|
||||
|
||||
// set request timeout
|
||||
if let Some(timeout) = slf.config.timeout {
|
||||
if let Some(timeout) = self.config.timeout {
|
||||
Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| {
|
||||
if let Some(e) = e.into_inner() {
|
||||
e
|
||||
@@ -400,3 +370,128 @@ impl WebsocketsRequest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for WebsocketsRequest {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"\nWebsocketsRequest {}:{}",
|
||||
self.head.method, self.head.uri
|
||||
)?;
|
||||
writeln!(f, " headers:")?;
|
||||
for (key, val) in self.head.headers.iter() {
|
||||
writeln!(f, " {:?}: {:?}", key, val)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Client;
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let request = Client::new().ws("/").header("x-test", "111");
|
||||
let repr = format!("{:?}", request);
|
||||
assert!(repr.contains("WebsocketsRequest"));
|
||||
assert!(repr.contains("x-test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_override() {
|
||||
let req = Client::build()
|
||||
.header(header::CONTENT_TYPE, "111")
|
||||
.finish()
|
||||
.ws("/")
|
||||
.set_header(header::CONTENT_TYPE, "222");
|
||||
|
||||
assert_eq!(
|
||||
req.head
|
||||
.headers
|
||||
.get(header::CONTENT_TYPE)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"222"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_auth() {
|
||||
let req = Client::new()
|
||||
.ws("/")
|
||||
.basic_auth("username", Some("password"));
|
||||
assert_eq!(
|
||||
req.head
|
||||
.headers
|
||||
.get(header::AUTHORIZATION)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"Basic dXNlcm5hbWU6cGFzc3dvcmQ="
|
||||
);
|
||||
|
||||
let req = Client::new().ws("/").basic_auth("username", None);
|
||||
assert_eq!(
|
||||
req.head
|
||||
.headers
|
||||
.get(header::AUTHORIZATION)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"Basic dXNlcm5hbWU="
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bearer_auth() {
|
||||
let req = Client::new().ws("/").bearer_auth("someS3cr3tAutht0k3n");
|
||||
assert_eq!(
|
||||
req.head
|
||||
.headers
|
||||
.get(header::AUTHORIZATION)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"Bearer someS3cr3tAutht0k3n"
|
||||
);
|
||||
let _ = req.connect();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basics() {
|
||||
actix_http_test::run_on(|| {
|
||||
let req = Client::new()
|
||||
.ws("http://localhost/")
|
||||
.origin("test-origin")
|
||||
.max_frame_size(100)
|
||||
.server_mode()
|
||||
.protocols(&["v1", "v2"])
|
||||
.set_header_if_none(header::CONTENT_TYPE, "json")
|
||||
.set_header_if_none(header::CONTENT_TYPE, "text")
|
||||
.cookie(Cookie::build("cookie1", "value1").finish());
|
||||
assert_eq!(
|
||||
req.origin.as_ref().unwrap().to_str().unwrap(),
|
||||
"test-origin"
|
||||
);
|
||||
assert_eq!(req.max_size, 100);
|
||||
assert_eq!(req.server_mode, true);
|
||||
assert_eq!(req.protocols, Some("v1,v2".to_string()));
|
||||
assert_eq!(
|
||||
req.head.headers.get(header::CONTENT_TYPE).unwrap(),
|
||||
header::HeaderValue::from_static("json")
|
||||
);
|
||||
let _ = req.connect();
|
||||
});
|
||||
|
||||
assert!(Client::new().ws("/").connect().poll().is_err());
|
||||
assert!(Client::new().ws("http:///test").connect().poll().is_err());
|
||||
assert!(Client::new()
|
||||
.ws("hmm://test.com/")
|
||||
.connect()
|
||||
.poll()
|
||||
.is_err());
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,10 @@
|
||||
use std::io::Write;
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Read, Write};
|
||||
use std::time::Duration;
|
||||
|
||||
use brotli2::write::BrotliEncoder;
|
||||
use bytes::Bytes;
|
||||
use flate2::read::GzDecoder;
|
||||
use flate2::write::GzEncoder;
|
||||
use flate2::Compression;
|
||||
use futures::future::Future;
|
||||
@@ -10,6 +12,8 @@ use rand::Rng;
|
||||
|
||||
use actix_http::HttpService;
|
||||
use actix_http_test::TestServer;
|
||||
use actix_web::http::Cookie;
|
||||
use actix_web::middleware::{BodyEncoding, Compress};
|
||||
use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse};
|
||||
use awc::error::SendRequestError;
|
||||
|
||||
@@ -60,6 +64,38 @@ fn test_simple() {
|
||||
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json() {
|
||||
let mut srv = TestServer::new(|| {
|
||||
HttpService::new(App::new().service(
|
||||
web::resource("/").route(web::to(|_: web::Json<String>| HttpResponse::Ok())),
|
||||
))
|
||||
});
|
||||
|
||||
let request = srv
|
||||
.get("/")
|
||||
.header("x-test", "111")
|
||||
.send_json(&"TEST".to_string());
|
||||
let response = srv.block_on(request).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_form() {
|
||||
let mut srv = TestServer::new(|| {
|
||||
HttpService::new(App::new().service(web::resource("/").route(web::to(
|
||||
|_: web::Form<HashMap<String, String>>| HttpResponse::Ok(),
|
||||
))))
|
||||
});
|
||||
|
||||
let mut data = HashMap::new();
|
||||
let _ = data.insert("key".to_string(), "TEST".to_string());
|
||||
|
||||
let request = srv.get("/").header("x-test", "111").send_form(&data);
|
||||
let response = srv.block_on(request).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timeout() {
|
||||
let mut srv = TestServer::new(|| {
|
||||
@@ -94,11 +130,9 @@ fn test_timeout_override() {
|
||||
))))
|
||||
});
|
||||
|
||||
let client = srv.execute(|| {
|
||||
awc::Client::build()
|
||||
let client = awc::Client::build()
|
||||
.timeout(Duration::from_millis(50000))
|
||||
.finish()
|
||||
});
|
||||
.finish();
|
||||
let request = client
|
||||
.get(srv.url("/"))
|
||||
.timeout(Duration::from_millis(50))
|
||||
@@ -109,58 +143,77 @@ fn test_timeout_override() {
|
||||
}
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_connection_close() {
|
||||
// let mut srv =
|
||||
// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
|
||||
#[test]
|
||||
fn test_connection_close() {
|
||||
let mut srv = TestServer::new(|| {
|
||||
HttpService::new(
|
||||
App::new().service(web::resource("/").to(|| HttpResponse::Ok())),
|
||||
)
|
||||
});
|
||||
|
||||
// let request = srv.get("/").header("Connection", "close").finish().unwrap();
|
||||
// let response = srv.execute(request.send()).unwrap();
|
||||
// assert!(response.status().is_success());
|
||||
// }
|
||||
let res = srv
|
||||
.block_on(awc::Client::new().get(srv.url("/")).force_close().send())
|
||||
.unwrap();
|
||||
assert!(res.status().is_success());
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_with_query_parameter() {
|
||||
// let mut srv = test::TestServer::new(|app| {
|
||||
// app.handler(|req: &HttpRequest| match req.query().get("qp") {
|
||||
// Some(_) => HttpResponse::Ok().finish(),
|
||||
// None => HttpResponse::BadRequest().finish(),
|
||||
// })
|
||||
// });
|
||||
#[test]
|
||||
fn test_with_query_parameter() {
|
||||
let mut srv = TestServer::new(|| {
|
||||
HttpService::new(App::new().service(web::resource("/").to(
|
||||
|req: HttpRequest| {
|
||||
if req.query_string().contains("qp") {
|
||||
HttpResponse::Ok()
|
||||
} else {
|
||||
HttpResponse::BadRequest()
|
||||
}
|
||||
},
|
||||
)))
|
||||
});
|
||||
|
||||
// let request = srv.get("/").uri(srv.url("/?qp=5").as_str()).finish().unwrap();
|
||||
let res = srv
|
||||
.block_on(awc::Client::new().get(srv.url("/?qp=5")).send())
|
||||
.unwrap();
|
||||
assert!(res.status().is_success());
|
||||
}
|
||||
|
||||
// let response = srv.execute(request.send()).unwrap();
|
||||
// assert!(response.status().is_success());
|
||||
// }
|
||||
#[test]
|
||||
fn test_no_decompress() {
|
||||
let mut srv = TestServer::new(|| {
|
||||
HttpService::new(App::new().wrap(Compress::default()).service(
|
||||
web::resource("/").route(web::to(|| {
|
||||
let mut res = HttpResponse::Ok().body(STR);
|
||||
res.encoding(header::ContentEncoding::Gzip);
|
||||
res
|
||||
})),
|
||||
))
|
||||
});
|
||||
|
||||
// #[test]
|
||||
// fn test_no_decompress() {
|
||||
// let mut srv =
|
||||
// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
|
||||
let mut res = srv
|
||||
.block_on(awc::Client::new().get(srv.url("/")).no_decompress().send())
|
||||
.unwrap();
|
||||
assert!(res.status().is_success());
|
||||
|
||||
// let request = srv.get("/").disable_decompress().finish().unwrap();
|
||||
// let response = srv.execute(request.send()).unwrap();
|
||||
// assert!(response.status().is_success());
|
||||
// read response
|
||||
let bytes = srv.block_on(res.body()).unwrap();
|
||||
|
||||
// // read response
|
||||
// let bytes = srv.execute(response.body()).unwrap();
|
||||
let mut e = GzDecoder::new(&bytes[..]);
|
||||
let mut dec = Vec::new();
|
||||
e.read_to_end(&mut dec).unwrap();
|
||||
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
|
||||
|
||||
// let mut e = GzDecoder::new(&bytes[..]);
|
||||
// let mut dec = Vec::new();
|
||||
// e.read_to_end(&mut dec).unwrap();
|
||||
// assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
|
||||
// POST
|
||||
let mut res = srv
|
||||
.block_on(awc::Client::new().post(srv.url("/")).no_decompress().send())
|
||||
.unwrap();
|
||||
assert!(res.status().is_success());
|
||||
|
||||
// // POST
|
||||
// let request = srv.post().disable_decompress().finish().unwrap();
|
||||
// let response = srv.execute(request.send()).unwrap();
|
||||
|
||||
// let bytes = srv.execute(response.body()).unwrap();
|
||||
// let mut e = GzDecoder::new(&bytes[..]);
|
||||
// let mut dec = Vec::new();
|
||||
// e.read_to_end(&mut dec).unwrap();
|
||||
// assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
|
||||
// }
|
||||
let bytes = srv.block_on(res.body()).unwrap();
|
||||
let mut e = GzDecoder::new(&bytes[..]);
|
||||
let mut dec = Vec::new();
|
||||
e.read_to_end(&mut dec).unwrap();
|
||||
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_gzip_encoding() {
|
||||
@@ -406,7 +459,6 @@ fn test_client_brotli_encoding() {
|
||||
|
||||
#[test]
|
||||
fn test_client_cookie_handling() {
|
||||
use actix_web::http::Cookie;
|
||||
fn err() -> Error {
|
||||
use std::io::{Error as IoError, ErrorKind};
|
||||
// stub some generic error
|
||||
@@ -468,36 +520,6 @@ fn test_client_cookie_handling() {
|
||||
assert_eq!(c2, cookie2);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_default_headers() {
|
||||
// let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
|
||||
|
||||
// let request = srv.get("/").finish().unwrap();
|
||||
// let repr = format!("{:?}", request);
|
||||
// assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\""));
|
||||
// assert!(repr.contains(concat!(
|
||||
// "\"user-agent\": \"actix-web/",
|
||||
// env!("CARGO_PKG_VERSION"),
|
||||
// "\""
|
||||
// )));
|
||||
|
||||
// let request_override = srv
|
||||
// .get("/")
|
||||
// .header("User-Agent", "test")
|
||||
// .header("Accept-Encoding", "over_test")
|
||||
// .finish()
|
||||
// .unwrap();
|
||||
// let repr_override = format!("{:?}", request_override);
|
||||
// assert!(repr_override.contains("\"user-agent\": \"test\""));
|
||||
// assert!(repr_override.contains("\"accept-encoding\": \"over_test\""));
|
||||
// assert!(!repr_override.contains("\"accept-encoding\": \"gzip, deflate\""));
|
||||
// assert!(!repr_override.contains(concat!(
|
||||
// "\"user-agent\": \"Actix-web/",
|
||||
// env!("CARGO_PKG_VERSION"),
|
||||
// "\""
|
||||
// )));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn client_read_until_eof() {
|
||||
// let addr = test::TestServer::unused_addr();
|
||||
|
@@ -27,7 +27,7 @@ fn main() -> std::io::Result<()> {
|
||||
HttpServer::new(|| {
|
||||
App::new()
|
||||
.wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
|
||||
.wrap(middleware::encoding::Compress::default())
|
||||
.wrap(middleware::Compress::default())
|
||||
.wrap(middleware::Logger::default())
|
||||
.service(index)
|
||||
.service(no_params)
|
||||
@@ -36,9 +36,9 @@ fn main() -> std::io::Result<()> {
|
||||
.wrap(
|
||||
middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"),
|
||||
)
|
||||
.default_resource(|r| {
|
||||
r.route(web::route().to(|| HttpResponse::MethodNotAllowed()))
|
||||
})
|
||||
.default_service(
|
||||
web::route().to(|| HttpResponse::MethodNotAllowed()),
|
||||
)
|
||||
.route(web::get().to_async(index_async)),
|
||||
)
|
||||
.service(web::resource("/test1.html").to(|| "Test\r\n"))
|
||||
|
657
src/app.rs
657
src/app.rs
@@ -1,24 +1,21 @@
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_http::body::{Body, MessageBody};
|
||||
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||
use actix_http::encoding::{Decoder, Encoder};
|
||||
use actix_server_config::ServerConfig;
|
||||
use actix_service::boxed::{self, BoxedNewService};
|
||||
use actix_service::{
|
||||
apply_transform, IntoNewService, IntoTransform, NewService, Transform,
|
||||
};
|
||||
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||
use bytes::Bytes;
|
||||
use futures::{IntoFuture, Stream};
|
||||
use futures::IntoFuture;
|
||||
|
||||
use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory};
|
||||
use crate::config::{AppConfig, AppConfigInner, RouterConfig};
|
||||
use crate::app_service::{AppEntry, AppInit, AppRoutingFactory};
|
||||
use crate::config::{AppConfig, AppConfigInner, ServiceConfig};
|
||||
use crate::data::{Data, DataFactory};
|
||||
use crate::dev::{Payload, PayloadStream, ResourceDef};
|
||||
use crate::error::{Error, PayloadError};
|
||||
use crate::dev::ResourceDef;
|
||||
use crate::error::Error;
|
||||
use crate::resource::Resource;
|
||||
use crate::route::Route;
|
||||
use crate::service::{
|
||||
@@ -26,40 +23,44 @@ use crate::service::{
|
||||
ServiceResponse,
|
||||
};
|
||||
|
||||
type HttpNewService<P> =
|
||||
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
|
||||
type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||
|
||||
/// Application builder - structure that follows the builder pattern
|
||||
/// for building application instances.
|
||||
pub struct App<In, Out, T>
|
||||
where
|
||||
T: NewService<Request = ServiceRequest<In>, Response = ServiceRequest<Out>>,
|
||||
{
|
||||
chain: T,
|
||||
pub struct App<T, B> {
|
||||
endpoint: T,
|
||||
services: Vec<Box<ServiceFactory>>,
|
||||
default: Option<Rc<HttpNewService>>,
|
||||
factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
||||
data: Vec<Box<DataFactory>>,
|
||||
config: AppConfigInner,
|
||||
_t: PhantomData<(In, Out)>,
|
||||
external: Vec<ResourceDef>,
|
||||
_t: PhantomData<(B)>,
|
||||
}
|
||||
|
||||
impl App<PayloadStream, PayloadStream, AppChain> {
|
||||
impl App<AppEntry, Body> {
|
||||
/// Create application builder. Application can be configured with a builder-like pattern.
|
||||
pub fn new() -> Self {
|
||||
let fref = Rc::new(RefCell::new(None));
|
||||
App {
|
||||
chain: AppChain,
|
||||
endpoint: AppEntry::new(fref.clone()),
|
||||
data: Vec::new(),
|
||||
services: Vec::new(),
|
||||
default: None,
|
||||
factory_ref: fref,
|
||||
config: AppConfigInner::default(),
|
||||
external: Vec::new(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<In, Out, T> App<In, Out, T>
|
||||
impl<T, B> App<T, B>
|
||||
where
|
||||
In: 'static,
|
||||
Out: 'static,
|
||||
B: MessageBody,
|
||||
T: NewService<
|
||||
Request = ServiceRequest<In>,
|
||||
Response = ServiceRequest<Out>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
@@ -112,151 +113,6 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Registers middleware, in the form of a middleware component (type),
|
||||
/// that runs during inbound and/or outbound processing in the request
|
||||
/// lifecycle (request -> response), modifying request/response as
|
||||
/// necessary, across all requests managed by the *Application*.
|
||||
///
|
||||
/// Use middleware when you need to read or modify *every* request or response in some way.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_service::Service;
|
||||
/// # use futures::Future;
|
||||
/// use actix_web::{middleware, web, App};
|
||||
/// use actix_web::http::{header::CONTENT_TYPE, HeaderValue};
|
||||
///
|
||||
/// fn index() -> &'static str {
|
||||
/// "Welcome!"
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .wrap(middleware::Logger::default())
|
||||
/// .route("/index.html", web::get().to(index));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn wrap<M, B, F>(
|
||||
self,
|
||||
mw: F,
|
||||
) -> AppRouter<
|
||||
T,
|
||||
Out,
|
||||
B,
|
||||
impl NewService<
|
||||
Request = ServiceRequest<Out>,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
>
|
||||
where
|
||||
M: Transform<
|
||||
AppRouting<Out>,
|
||||
Request = ServiceRequest<Out>,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
F: IntoTransform<M, AppRouting<Out>>,
|
||||
{
|
||||
let fref = Rc::new(RefCell::new(None));
|
||||
let endpoint = apply_transform(mw, AppEntry::new(fref.clone()));
|
||||
AppRouter {
|
||||
endpoint,
|
||||
chain: self.chain,
|
||||
data: self.data,
|
||||
services: Vec::new(),
|
||||
default: None,
|
||||
factory_ref: fref,
|
||||
config: self.config,
|
||||
external: Vec::new(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers middleware, in the form of a closure, that runs during inbound
|
||||
/// and/or outbound processing in the request lifecycle (request -> response),
|
||||
/// modifying request/response as necessary, across all requests managed by
|
||||
/// the *Application*.
|
||||
///
|
||||
/// Use middleware when you need to read or modify *every* request or response in some way.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_service::Service;
|
||||
/// # use futures::Future;
|
||||
/// use actix_web::{web, App};
|
||||
/// use actix_web::http::{header::CONTENT_TYPE, HeaderValue};
|
||||
///
|
||||
/// fn index() -> &'static str {
|
||||
/// "Welcome!"
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .wrap_fn(|req, srv|
|
||||
/// srv.call(req).map(|mut res| {
|
||||
/// res.headers_mut().insert(
|
||||
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
|
||||
/// );
|
||||
/// res
|
||||
/// }))
|
||||
/// .route("/index.html", web::get().to(index));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn wrap_fn<F, R, B>(
|
||||
self,
|
||||
mw: F,
|
||||
) -> AppRouter<
|
||||
T,
|
||||
Out,
|
||||
B,
|
||||
impl NewService<
|
||||
Request = ServiceRequest<Out>,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
>
|
||||
where
|
||||
F: FnMut(ServiceRequest<Out>, &mut AppRouting<Out>) -> R + Clone,
|
||||
R: IntoFuture<Item = ServiceResponse<B>, Error = Error>,
|
||||
{
|
||||
self.wrap(mw)
|
||||
}
|
||||
|
||||
/// Register a request modifier. It can modify any request parameters
|
||||
/// including request payload type.
|
||||
pub fn chain<C, F, P>(
|
||||
self,
|
||||
chain: F,
|
||||
) -> App<
|
||||
In,
|
||||
P,
|
||||
impl NewService<
|
||||
Request = ServiceRequest<In>,
|
||||
Response = ServiceRequest<P>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
>
|
||||
where
|
||||
C: NewService<
|
||||
Request = ServiceRequest<Out>,
|
||||
Response = ServiceRequest<P>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
F: IntoNewService<C>,
|
||||
{
|
||||
let chain = self.chain.and_then(chain.into_new_service());
|
||||
App {
|
||||
chain,
|
||||
data: self.data,
|
||||
config: self.config,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run external configuration as part of the application building
|
||||
/// process
|
||||
///
|
||||
@@ -269,7 +125,7 @@ where
|
||||
/// use actix_web::{web, middleware, App, HttpResponse};
|
||||
///
|
||||
/// // this function could be located in different module
|
||||
/// fn config<P>(cfg: &mut web::RouterConfig<P>) {
|
||||
/// fn config(cfg: &mut web::ServiceConfig) {
|
||||
/// cfg.service(web::resource("/test")
|
||||
/// .route(web::get().to(|| HttpResponse::Ok()))
|
||||
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
|
||||
@@ -283,27 +139,16 @@ where
|
||||
/// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn configure<F>(mut self, f: F) -> AppRouter<T, Out, Body, AppEntry<Out>>
|
||||
pub fn configure<F>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Fn(&mut RouterConfig<Out>),
|
||||
F: Fn(&mut ServiceConfig),
|
||||
{
|
||||
let mut cfg = RouterConfig::new();
|
||||
let mut cfg = ServiceConfig::new();
|
||||
f(&mut cfg);
|
||||
self.data.extend(cfg.data);
|
||||
|
||||
let fref = Rc::new(RefCell::new(None));
|
||||
|
||||
AppRouter {
|
||||
chain: self.chain,
|
||||
default: None,
|
||||
endpoint: AppEntry::new(fref.clone()),
|
||||
factory_ref: fref,
|
||||
data: self.data,
|
||||
config: self.config,
|
||||
services: cfg.services,
|
||||
external: cfg.external,
|
||||
_t: PhantomData,
|
||||
}
|
||||
self.services.extend(cfg.services);
|
||||
self.external.extend(cfg.external);
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure route for a specific path.
|
||||
@@ -325,171 +170,7 @@ where
|
||||
/// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn route(
|
||||
self,
|
||||
path: &str,
|
||||
mut route: Route<Out>,
|
||||
) -> AppRouter<T, Out, Body, AppEntry<Out>> {
|
||||
self.service(
|
||||
Resource::new(path)
|
||||
.add_guards(route.take_guards())
|
||||
.route(route),
|
||||
)
|
||||
}
|
||||
|
||||
/// Register http service.
|
||||
///
|
||||
/// Http service is any type that implements `HttpServiceFactory` trait.
|
||||
///
|
||||
/// Actix web provides several services implementations:
|
||||
///
|
||||
/// * *Resource* is an entry in resource table which corresponds to requested URL.
|
||||
/// * *Scope* is a set of resources with common root path.
|
||||
/// * "StaticFiles" is a service for static files support
|
||||
pub fn service<F>(self, service: F) -> AppRouter<T, Out, Body, AppEntry<Out>>
|
||||
where
|
||||
F: HttpServiceFactory<Out> + 'static,
|
||||
{
|
||||
let fref = Rc::new(RefCell::new(None));
|
||||
|
||||
AppRouter {
|
||||
chain: self.chain,
|
||||
default: None,
|
||||
endpoint: AppEntry::new(fref.clone()),
|
||||
factory_ref: fref,
|
||||
data: self.data,
|
||||
config: self.config,
|
||||
services: vec![Box::new(ServiceFactoryWrapper::new(service))],
|
||||
external: Vec::new(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set server host name.
|
||||
///
|
||||
/// Host name is used by application router as a hostname for url
|
||||
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
|
||||
/// html#method.host) documentation for more information.
|
||||
///
|
||||
/// By default host name is set to a "localhost" value.
|
||||
pub fn hostname(mut self, val: &str) -> Self {
|
||||
self.config.host = val.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||
/// Enable content compression and decompression.
|
||||
pub fn enable_encoding(
|
||||
self,
|
||||
) -> AppRouter<
|
||||
impl NewService<
|
||||
Request = ServiceRequest<In>,
|
||||
Response = ServiceRequest<Decoder<Payload<Out>>>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
Decoder<Payload<Out>>,
|
||||
Encoder<Body>,
|
||||
impl NewService<
|
||||
Request = ServiceRequest<Decoder<Payload<Out>>>,
|
||||
Response = ServiceResponse<Encoder<Body>>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
>
|
||||
where
|
||||
Out: Stream<Item = Bytes, Error = PayloadError>,
|
||||
{
|
||||
use crate::middleware::encoding::{Compress, Decompress};
|
||||
|
||||
self.chain(Decompress::new()).wrap(Compress::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Application router builder - Structure that follows the builder pattern
|
||||
/// for building application instances.
|
||||
pub struct AppRouter<C, P, B, T> {
|
||||
chain: C,
|
||||
endpoint: T,
|
||||
services: Vec<Box<ServiceFactory<P>>>,
|
||||
default: Option<Rc<HttpNewService<P>>>,
|
||||
factory_ref: Rc<RefCell<Option<AppRoutingFactory<P>>>>,
|
||||
data: Vec<Box<DataFactory>>,
|
||||
config: AppConfigInner,
|
||||
external: Vec<ResourceDef>,
|
||||
_t: PhantomData<(P, B)>,
|
||||
}
|
||||
|
||||
impl<C, P, B, T> AppRouter<C, P, B, T>
|
||||
where
|
||||
P: 'static,
|
||||
B: MessageBody,
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
/// Run external configuration as part of the application building
|
||||
/// process
|
||||
///
|
||||
/// This function is useful for moving parts of configuration to a
|
||||
/// different module or even library. For example,
|
||||
/// some of the resource's configuration could be moved to different module.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{web, middleware, App, HttpResponse};
|
||||
///
|
||||
/// // this function could be located in different module
|
||||
/// fn config<P>(cfg: &mut web::RouterConfig<P>) {
|
||||
/// cfg.service(web::resource("/test")
|
||||
/// .route(web::get().to(|| HttpResponse::Ok()))
|
||||
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
|
||||
/// );
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .wrap(middleware::Logger::default())
|
||||
/// .configure(config) // <- register resources
|
||||
/// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn configure<F>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Fn(&mut RouterConfig<P>),
|
||||
{
|
||||
let mut cfg = RouterConfig::new();
|
||||
f(&mut cfg);
|
||||
self.data.extend(cfg.data);
|
||||
self.services.extend(cfg.services);
|
||||
self.external.extend(cfg.external);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure route for a specific path.
|
||||
///
|
||||
/// This is a simplified version of the `App::service()` method.
|
||||
/// This method can not be could multiple times, in that case
|
||||
/// multiple resources with one route would be registered for same resource path.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, App, HttpResponse};
|
||||
///
|
||||
/// fn index(data: web::Path<(String, String)>) -> &'static str {
|
||||
/// "Welcome!"
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .route("/test1", web::get().to(index))
|
||||
/// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn route(self, path: &str, mut route: Route<P>) -> Self {
|
||||
pub fn route(self, path: &str, mut route: Route) -> Self {
|
||||
self.service(
|
||||
Resource::new(path)
|
||||
.add_guards(route.take_guards())
|
||||
@@ -508,102 +189,75 @@ where
|
||||
/// * "StaticFiles" is a service for static files support
|
||||
pub fn service<F>(mut self, factory: F) -> Self
|
||||
where
|
||||
F: HttpServiceFactory<P> + 'static,
|
||||
F: HttpServiceFactory + 'static,
|
||||
{
|
||||
self.services
|
||||
.push(Box::new(ServiceFactoryWrapper::new(factory)));
|
||||
self
|
||||
}
|
||||
|
||||
/// Registers middleware, in the form of a middleware component (type),
|
||||
/// that runs during inbound and/or outbound processing in the request
|
||||
/// lifecycle (request -> response), modifying request/response as
|
||||
/// necessary, across all requests managed by the *Route*.
|
||||
/// Set server host name.
|
||||
///
|
||||
/// Use middleware when you need to read or modify *every* request or response in some way.
|
||||
/// Host name is used by application router as a hostname for url
|
||||
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
|
||||
/// html#method.host) documentation for more information.
|
||||
///
|
||||
pub fn wrap<M, B1, F>(
|
||||
self,
|
||||
mw: F,
|
||||
) -> AppRouter<
|
||||
C,
|
||||
P,
|
||||
B1,
|
||||
impl NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse<B1>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
>
|
||||
where
|
||||
M: Transform<
|
||||
T::Service,
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse<B1>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
B1: MessageBody,
|
||||
F: IntoTransform<M, T::Service>,
|
||||
{
|
||||
let endpoint = apply_transform(mw, self.endpoint);
|
||||
AppRouter {
|
||||
endpoint,
|
||||
chain: self.chain,
|
||||
data: self.data,
|
||||
services: self.services,
|
||||
default: self.default,
|
||||
factory_ref: self.factory_ref,
|
||||
config: self.config,
|
||||
external: self.external,
|
||||
_t: PhantomData,
|
||||
}
|
||||
/// By default host name is set to a "localhost" value.
|
||||
pub fn hostname(mut self, val: &str) -> Self {
|
||||
self.config.host = val.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
/// Registers middleware, in the form of a closure, that runs during inbound
|
||||
/// and/or outbound processing in the request lifecycle (request -> response),
|
||||
/// modifying request/response as necessary, across all requests managed by
|
||||
/// the *Route*.
|
||||
/// Default service to be used if no matching resource could be found.
|
||||
///
|
||||
/// Use middleware when you need to read or modify *every* request or response in some way.
|
||||
/// It is possible to use services like `Resource`, `Route`.
|
||||
///
|
||||
pub fn wrap_fn<B1, F, R>(
|
||||
self,
|
||||
mw: F,
|
||||
) -> AppRouter<
|
||||
C,
|
||||
P,
|
||||
B1,
|
||||
impl NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse<B1>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
>
|
||||
/// ```rust
|
||||
/// use actix_web::{web, App, HttpResponse};
|
||||
///
|
||||
/// fn index() -> &'static str {
|
||||
/// "Welcome!"
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .service(
|
||||
/// web::resource("/index.html").route(web::get().to(index)))
|
||||
/// .default_service(
|
||||
/// web::route().to(|| HttpResponse::NotFound()));
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// It is also possible to use static files as default service.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_files::Files;
|
||||
/// use actix_web::{web, App, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .service(
|
||||
/// web::resource("/index.html").to(|| HttpResponse::Ok()))
|
||||
/// .default_service(
|
||||
/// Files::new("", "./static")
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub fn default_service<F, U>(mut self, f: F) -> Self
|
||||
where
|
||||
B1: MessageBody,
|
||||
F: FnMut(ServiceRequest<P>, &mut T::Service) -> R + Clone,
|
||||
R: IntoFuture<Item = ServiceResponse<B1>, Error = Error>,
|
||||
{
|
||||
self.wrap(mw)
|
||||
}
|
||||
|
||||
/// Default resource to be used if no matching resource could be found.
|
||||
pub fn default_resource<F, U>(mut self, f: F) -> Self
|
||||
where
|
||||
F: FnOnce(Resource<P>) -> Resource<P, U>,
|
||||
F: IntoNewService<U>,
|
||||
U: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
> + 'static,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
// create and configure default resource
|
||||
self.default = Some(Rc::new(boxed::new_service(
|
||||
f(Resource::new("")).into_new_service().map_init_err(|_| ()),
|
||||
f.into_new_service().map_init_err(|e| {
|
||||
log::error!("Can not construct default service: {:?}", e)
|
||||
}),
|
||||
)));
|
||||
|
||||
self
|
||||
@@ -641,27 +295,128 @@ where
|
||||
self.external.push(rdef);
|
||||
self
|
||||
}
|
||||
|
||||
/// Registers middleware, in the form of a middleware component (type),
|
||||
/// that runs during inbound and/or outbound processing in the request
|
||||
/// lifecycle (request -> response), modifying request/response as
|
||||
/// necessary, across all requests managed by the *Application*.
|
||||
///
|
||||
/// Use middleware when you need to read or modify *every* request or response in some way.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_service::Service;
|
||||
/// # use futures::Future;
|
||||
/// use actix_web::{middleware, web, App};
|
||||
/// use actix_web::http::{header::CONTENT_TYPE, HeaderValue};
|
||||
///
|
||||
/// fn index() -> &'static str {
|
||||
/// "Welcome!"
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .wrap(middleware::Logger::default())
|
||||
/// .route("/index.html", web::get().to(index));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn wrap<M, B1, F>(
|
||||
self,
|
||||
mw: F,
|
||||
) -> App<
|
||||
impl NewService<
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B1>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
B1,
|
||||
>
|
||||
where
|
||||
M: Transform<
|
||||
T::Service,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B1>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
B1: MessageBody,
|
||||
F: IntoTransform<M, T::Service>,
|
||||
{
|
||||
let endpoint = apply_transform(mw, self.endpoint);
|
||||
App {
|
||||
endpoint,
|
||||
data: self.data,
|
||||
services: self.services,
|
||||
default: self.default,
|
||||
factory_ref: self.factory_ref,
|
||||
config: self.config,
|
||||
external: self.external,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers middleware, in the form of a closure, that runs during inbound
|
||||
/// and/or outbound processing in the request lifecycle (request -> response),
|
||||
/// modifying request/response as necessary, across all requests managed by
|
||||
/// the *Application*.
|
||||
///
|
||||
/// Use middleware when you need to read or modify *every* request or response in some way.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_service::Service;
|
||||
/// # use futures::Future;
|
||||
/// use actix_web::{web, App};
|
||||
/// use actix_web::http::{header::CONTENT_TYPE, HeaderValue};
|
||||
///
|
||||
/// fn index() -> &'static str {
|
||||
/// "Welcome!"
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .wrap_fn(|req, srv|
|
||||
/// srv.call(req).map(|mut res| {
|
||||
/// res.headers_mut().insert(
|
||||
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
|
||||
/// );
|
||||
/// res
|
||||
/// }))
|
||||
/// .route("/index.html", web::get().to(index));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn wrap_fn<B1, F, R>(
|
||||
self,
|
||||
mw: F,
|
||||
) -> App<
|
||||
impl NewService<
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B1>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
B1,
|
||||
>
|
||||
where
|
||||
B1: MessageBody,
|
||||
F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone,
|
||||
R: IntoFuture<Item = ServiceResponse<B1>, Error = Error>,
|
||||
{
|
||||
self.wrap(mw)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, T, P: 'static, B: MessageBody> IntoNewService<AppInit<C, T, P, B>, ServerConfig>
|
||||
for AppRouter<C, P, B, T>
|
||||
impl<T, B> IntoNewService<AppInit<T, B>, ServerConfig> for App<T, B>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
C: NewService<
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceRequest<P>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
fn into_new_service(self) -> AppInit<C, T, P, B> {
|
||||
fn into_new_service(self) -> AppInit<T, B> {
|
||||
AppInit {
|
||||
chain: self.chain,
|
||||
data: self.data,
|
||||
endpoint: self.endpoint,
|
||||
services: RefCell::new(self.services),
|
||||
@@ -681,7 +436,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::http::{header, HeaderValue, Method, StatusCode};
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
use crate::test::{block_on, call_success, init_service, TestRequest};
|
||||
use crate::test::{block_on, call_service, init_service, TestRequest};
|
||||
use crate::{web, Error, HttpResponse};
|
||||
|
||||
#[test]
|
||||
@@ -702,10 +457,14 @@ mod tests {
|
||||
.service(web::resource("/test").to(|| HttpResponse::Ok()))
|
||||
.service(
|
||||
web::resource("/test2")
|
||||
.default_resource(|r| r.to(|| HttpResponse::Created()))
|
||||
.default_service(|r: ServiceRequest| {
|
||||
r.into_response(HttpResponse::Created())
|
||||
})
|
||||
.route(web::get().to(|| HttpResponse::Ok())),
|
||||
)
|
||||
.default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())),
|
||||
.default_service(|r: ServiceRequest| {
|
||||
r.into_response(HttpResponse::MethodNotAllowed())
|
||||
}),
|
||||
);
|
||||
|
||||
let req = TestRequest::with_uri("/blah").to_request();
|
||||
@@ -742,13 +501,13 @@ mod tests {
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
fn md<S, P, B>(
|
||||
req: ServiceRequest<P>,
|
||||
fn md<S, B>(
|
||||
req: ServiceRequest,
|
||||
srv: &mut S,
|
||||
) -> impl IntoFuture<Item = ServiceResponse<B>, Error = Error>
|
||||
where
|
||||
S: Service<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
>,
|
||||
@@ -768,7 +527,7 @@ mod tests {
|
||||
.route("/test", web::get().to(|| HttpResponse::Ok())),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
@@ -784,7 +543,7 @@ mod tests {
|
||||
.wrap(md),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
@@ -808,7 +567,7 @@ mod tests {
|
||||
.service(web::resource("/test").to(|| HttpResponse::Ok())),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
@@ -832,7 +591,7 @@ mod tests {
|
||||
}),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
|
@@ -6,11 +6,11 @@ use actix_http::{Request, Response};
|
||||
use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url};
|
||||
use actix_server_config::ServerConfig;
|
||||
use actix_service::boxed::{self, BoxedNewService, BoxedService};
|
||||
use actix_service::{fn_service, AndThen, NewService, Service, ServiceExt};
|
||||
use actix_service::{fn_service, NewService, Service};
|
||||
use futures::future::{ok, Either, FutureResult};
|
||||
use futures::{Async, Future, Poll};
|
||||
|
||||
use crate::config::{AppConfig, ServiceConfig};
|
||||
use crate::config::{AppConfig, AppService};
|
||||
use crate::data::{DataFactory, DataFactoryResult};
|
||||
use crate::error::Error;
|
||||
use crate::guard::Guard;
|
||||
@@ -19,9 +19,8 @@ use crate::rmap::ResourceMap;
|
||||
use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse};
|
||||
|
||||
type Guards = Vec<Box<Guard>>;
|
||||
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>;
|
||||
type HttpNewService<P> =
|
||||
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
|
||||
type HttpService = BoxedService<ServiceRequest, ServiceResponse, Error>;
|
||||
type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||
type BoxedResponse = Either<
|
||||
FutureResult<ServiceResponse, Error>,
|
||||
Box<Future<Item = ServiceResponse, Error = Error>>,
|
||||
@@ -29,36 +28,28 @@ type BoxedResponse = Either<
|
||||
|
||||
/// Service factory to convert `Request` to a `ServiceRequest<S>`.
|
||||
/// It also executes data factories.
|
||||
pub struct AppInit<C, T, P, B>
|
||||
pub struct AppInit<T, B>
|
||||
where
|
||||
C: NewService<Request = ServiceRequest, Response = ServiceRequest<P>>,
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
pub(crate) chain: C,
|
||||
pub(crate) endpoint: T,
|
||||
pub(crate) data: Vec<Box<DataFactory>>,
|
||||
pub(crate) config: RefCell<AppConfig>,
|
||||
pub(crate) services: RefCell<Vec<Box<ServiceFactory<P>>>>,
|
||||
pub(crate) default: Option<Rc<HttpNewService<P>>>,
|
||||
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory<P>>>>,
|
||||
pub(crate) services: RefCell<Vec<Box<ServiceFactory>>>,
|
||||
pub(crate) default: Option<Rc<HttpNewService>>,
|
||||
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
||||
pub(crate) external: RefCell<Vec<ResourceDef>>,
|
||||
}
|
||||
|
||||
impl<C, T, P: 'static, B> NewService<ServerConfig> for AppInit<C, T, P, B>
|
||||
impl<T, B> NewService<ServerConfig> for AppInit<T, B>
|
||||
where
|
||||
C: NewService<
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceRequest<P>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
@@ -66,15 +57,15 @@ where
|
||||
{
|
||||
type Request = Request;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = C::Error;
|
||||
type InitError = C::InitError;
|
||||
type Service = AndThen<AppInitService<C::Service, P>, T::Service>;
|
||||
type Future = AppInitResult<C, T, P, B>;
|
||||
type Error = T::Error;
|
||||
type InitError = T::InitError;
|
||||
type Service = AppInitService<T::Service, B>;
|
||||
type Future = AppInitResult<T, B>;
|
||||
|
||||
fn new_service(&self, cfg: &ServerConfig) -> Self::Future {
|
||||
// update resource default service
|
||||
let default = self.default.clone().unwrap_or_else(|| {
|
||||
Rc::new(boxed::new_service(fn_service(|req: ServiceRequest<P>| {
|
||||
Rc::new(boxed::new_service(fn_service(|req: ServiceRequest| {
|
||||
Ok(req.into_response(Response::NotFound().finish()))
|
||||
})))
|
||||
});
|
||||
@@ -86,8 +77,7 @@ where
|
||||
loc_cfg.addr = cfg.local_addr();
|
||||
}
|
||||
|
||||
let mut config =
|
||||
ServiceConfig::new(self.config.borrow().clone(), default.clone());
|
||||
let mut config = AppService::new(self.config.borrow().clone(), default.clone());
|
||||
|
||||
// register services
|
||||
std::mem::replace(&mut *self.services.borrow_mut(), Vec::new())
|
||||
@@ -121,8 +111,6 @@ where
|
||||
rmap.finish(rmap.clone());
|
||||
|
||||
AppInitResult {
|
||||
chain: None,
|
||||
chain_fut: self.chain.new_service(&()),
|
||||
endpoint: None,
|
||||
endpoint_fut: self.endpoint.new_service(&()),
|
||||
data: self.data.iter().map(|s| s.construct()).collect(),
|
||||
@@ -133,38 +121,29 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppInitResult<C, T, P, B>
|
||||
pub struct AppInitResult<T, B>
|
||||
where
|
||||
C: NewService,
|
||||
T: NewService,
|
||||
{
|
||||
chain: Option<C::Service>,
|
||||
endpoint: Option<T::Service>,
|
||||
chain_fut: C::Future,
|
||||
endpoint_fut: T::Future,
|
||||
rmap: Rc<ResourceMap>,
|
||||
data: Vec<Box<DataFactoryResult>>,
|
||||
config: AppConfig,
|
||||
_t: PhantomData<(P, B)>,
|
||||
_t: PhantomData<B>,
|
||||
}
|
||||
|
||||
impl<C, T, P, B> Future for AppInitResult<C, T, P, B>
|
||||
impl<T, B> Future for AppInitResult<T, B>
|
||||
where
|
||||
C: NewService<
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceRequest<P>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
type Item = AndThen<AppInitService<C::Service, P>, T::Service>;
|
||||
type Error = C::InitError;
|
||||
type Item = AppInitService<T::Service, B>;
|
||||
type Error = T::InitError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let mut idx = 0;
|
||||
@@ -177,28 +156,19 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if self.chain.is_none() {
|
||||
if let Async::Ready(srv) = self.chain_fut.poll()? {
|
||||
self.chain = Some(srv);
|
||||
}
|
||||
}
|
||||
|
||||
if self.endpoint.is_none() {
|
||||
if let Async::Ready(srv) = self.endpoint_fut.poll()? {
|
||||
self.endpoint = Some(srv);
|
||||
}
|
||||
}
|
||||
|
||||
if self.chain.is_some() && self.endpoint.is_some() {
|
||||
Ok(Async::Ready(
|
||||
AppInitService {
|
||||
chain: self.chain.take().unwrap(),
|
||||
if self.endpoint.is_some() {
|
||||
Ok(Async::Ready(AppInitService {
|
||||
service: self.endpoint.take().unwrap(),
|
||||
rmap: self.rmap.clone(),
|
||||
config: self.config.clone(),
|
||||
pool: HttpRequestPool::create(),
|
||||
}
|
||||
.and_then(self.endpoint.take().unwrap()),
|
||||
))
|
||||
}))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
@@ -206,27 +176,27 @@ where
|
||||
}
|
||||
|
||||
/// Service to convert `Request` to a `ServiceRequest<S>`
|
||||
pub struct AppInitService<C, P>
|
||||
pub struct AppInitService<T: Service, B>
|
||||
where
|
||||
C: Service<Request = ServiceRequest, Response = ServiceRequest<P>, Error = Error>,
|
||||
T: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
{
|
||||
chain: C,
|
||||
service: T,
|
||||
rmap: Rc<ResourceMap>,
|
||||
config: AppConfig,
|
||||
pool: &'static HttpRequestPool,
|
||||
}
|
||||
|
||||
impl<C, P> Service for AppInitService<C, P>
|
||||
impl<T, B> Service for AppInitService<T, B>
|
||||
where
|
||||
C: Service<Request = ServiceRequest, Response = ServiceRequest<P>, Error = Error>,
|
||||
T: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
{
|
||||
type Request = Request;
|
||||
type Response = ServiceRequest<P>;
|
||||
type Error = C::Error;
|
||||
type Future = C::Future;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = T::Error;
|
||||
type Future = T::Future;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.chain.poll_ready()
|
||||
self.service.poll_ready()
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request) -> Self::Future {
|
||||
@@ -247,22 +217,22 @@ where
|
||||
self.pool,
|
||||
)
|
||||
};
|
||||
self.chain.call(ServiceRequest::from_parts(req, payload))
|
||||
self.service.call(ServiceRequest::from_parts(req, payload))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppRoutingFactory<P> {
|
||||
services: Rc<Vec<(ResourceDef, HttpNewService<P>, RefCell<Option<Guards>>)>>,
|
||||
default: Rc<HttpNewService<P>>,
|
||||
pub struct AppRoutingFactory {
|
||||
services: Rc<Vec<(ResourceDef, HttpNewService, RefCell<Option<Guards>>)>>,
|
||||
default: Rc<HttpNewService>,
|
||||
}
|
||||
|
||||
impl<P: 'static> NewService for AppRoutingFactory<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
impl NewService for AppRoutingFactory {
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Service = AppRouting<P>;
|
||||
type Future = AppRoutingFactoryResponse<P>;
|
||||
type Service = AppRouting;
|
||||
type Future = AppRoutingFactoryResponse;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
AppRoutingFactoryResponse {
|
||||
@@ -283,23 +253,23 @@ impl<P: 'static> NewService for AppRoutingFactory<P> {
|
||||
}
|
||||
}
|
||||
|
||||
type HttpServiceFut<P> = Box<Future<Item = HttpService<P>, Error = ()>>;
|
||||
type HttpServiceFut = Box<Future<Item = HttpService, Error = ()>>;
|
||||
|
||||
/// Create app service
|
||||
#[doc(hidden)]
|
||||
pub struct AppRoutingFactoryResponse<P> {
|
||||
fut: Vec<CreateAppRoutingItem<P>>,
|
||||
default: Option<HttpService<P>>,
|
||||
default_fut: Option<Box<Future<Item = HttpService<P>, Error = ()>>>,
|
||||
pub struct AppRoutingFactoryResponse {
|
||||
fut: Vec<CreateAppRoutingItem>,
|
||||
default: Option<HttpService>,
|
||||
default_fut: Option<Box<Future<Item = HttpService, Error = ()>>>,
|
||||
}
|
||||
|
||||
enum CreateAppRoutingItem<P> {
|
||||
Future(Option<ResourceDef>, Option<Guards>, HttpServiceFut<P>),
|
||||
Service(ResourceDef, Option<Guards>, HttpService<P>),
|
||||
enum CreateAppRoutingItem {
|
||||
Future(Option<ResourceDef>, Option<Guards>, HttpServiceFut),
|
||||
Service(ResourceDef, Option<Guards>, HttpService),
|
||||
}
|
||||
|
||||
impl<P> Future for AppRoutingFactoryResponse<P> {
|
||||
type Item = AppRouting<P>;
|
||||
impl Future for AppRoutingFactoryResponse {
|
||||
type Item = AppRouting;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
@@ -360,14 +330,14 @@ impl<P> Future for AppRoutingFactoryResponse<P> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppRouting<P> {
|
||||
router: Router<HttpService<P>, Guards>,
|
||||
ready: Option<(ServiceRequest<P>, ResourceInfo)>,
|
||||
default: Option<HttpService<P>>,
|
||||
pub struct AppRouting {
|
||||
router: Router<HttpService, Guards>,
|
||||
ready: Option<(ServiceRequest, ResourceInfo)>,
|
||||
default: Option<HttpService>,
|
||||
}
|
||||
|
||||
impl<P> Service for AppRouting<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
impl Service for AppRouting {
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Future = BoxedResponse;
|
||||
@@ -380,7 +350,7 @@ impl<P> Service for AppRouting<P> {
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future {
|
||||
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
||||
let res = self.router.recognize_mut_checked(&mut req, |req, guards| {
|
||||
if let Some(ref guards) = guards {
|
||||
for f in guards {
|
||||
@@ -404,58 +374,25 @@ impl<P> Service for AppRouting<P> {
|
||||
}
|
||||
|
||||
/// Wrapper service for routing
|
||||
pub struct AppEntry<P> {
|
||||
factory: Rc<RefCell<Option<AppRoutingFactory<P>>>>,
|
||||
pub struct AppEntry {
|
||||
factory: Rc<RefCell<Option<AppRoutingFactory>>>,
|
||||
}
|
||||
|
||||
impl<P> AppEntry<P> {
|
||||
pub fn new(factory: Rc<RefCell<Option<AppRoutingFactory<P>>>>) -> Self {
|
||||
impl AppEntry {
|
||||
pub fn new(factory: Rc<RefCell<Option<AppRoutingFactory>>>) -> Self {
|
||||
AppEntry { factory }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: 'static> NewService for AppEntry<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
impl NewService for AppEntry {
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Service = AppRouting<P>;
|
||||
type Future = AppRoutingFactoryResponse<P>;
|
||||
type Service = AppRouting;
|
||||
type Future = AppRoutingFactoryResponse;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
self.factory.borrow_mut().as_mut().unwrap().new_service(&())
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct AppChain;
|
||||
|
||||
impl NewService for AppChain {
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceRequest;
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Service = AppChain;
|
||||
type Future = FutureResult<Self::Service, Self::InitError>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
ok(AppChain)
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for AppChain {
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceRequest;
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Self::Response, Self::Error>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
ok(req)
|
||||
}
|
||||
}
|
||||
|
@@ -19,26 +19,26 @@ use crate::service::{
|
||||
};
|
||||
|
||||
type Guards = Vec<Box<Guard>>;
|
||||
type HttpNewService<P> =
|
||||
boxed::BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
|
||||
type HttpNewService =
|
||||
boxed::BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||
|
||||
/// Application configuration
|
||||
pub struct ServiceConfig<P> {
|
||||
pub struct AppService {
|
||||
config: AppConfig,
|
||||
root: bool,
|
||||
default: Rc<HttpNewService<P>>,
|
||||
default: Rc<HttpNewService>,
|
||||
services: Vec<(
|
||||
ResourceDef,
|
||||
HttpNewService<P>,
|
||||
HttpNewService,
|
||||
Option<Guards>,
|
||||
Option<Rc<ResourceMap>>,
|
||||
)>,
|
||||
}
|
||||
|
||||
impl<P: 'static> ServiceConfig<P> {
|
||||
impl AppService {
|
||||
/// Crate server settings instance
|
||||
pub(crate) fn new(config: AppConfig, default: Rc<HttpNewService<P>>) -> Self {
|
||||
ServiceConfig {
|
||||
pub(crate) fn new(config: AppConfig, default: Rc<HttpNewService>) -> Self {
|
||||
AppService {
|
||||
config,
|
||||
default,
|
||||
root: true,
|
||||
@@ -55,7 +55,7 @@ impl<P: 'static> ServiceConfig<P> {
|
||||
self,
|
||||
) -> Vec<(
|
||||
ResourceDef,
|
||||
HttpNewService<P>,
|
||||
HttpNewService,
|
||||
Option<Guards>,
|
||||
Option<Rc<ResourceMap>>,
|
||||
)> {
|
||||
@@ -63,7 +63,7 @@ impl<P: 'static> ServiceConfig<P> {
|
||||
}
|
||||
|
||||
pub(crate) fn clone_config(&self) -> Self {
|
||||
ServiceConfig {
|
||||
AppService {
|
||||
config: self.config.clone(),
|
||||
default: self.default.clone(),
|
||||
services: Vec::new(),
|
||||
@@ -77,7 +77,7 @@ impl<P: 'static> ServiceConfig<P> {
|
||||
}
|
||||
|
||||
/// Default resource
|
||||
pub fn default_service(&self) -> Rc<HttpNewService<P>> {
|
||||
pub fn default_service(&self) -> Rc<HttpNewService> {
|
||||
self.default.clone()
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ impl<P: 'static> ServiceConfig<P> {
|
||||
) where
|
||||
F: IntoNewService<S>,
|
||||
S: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
@@ -165,17 +165,17 @@ impl Default for AppConfigInner {
|
||||
}
|
||||
}
|
||||
|
||||
/// Router config. It is used for external configuration.
|
||||
/// Service config is used for external configuration.
|
||||
/// Part of application configuration could be offloaded
|
||||
/// to set of external methods. This could help with
|
||||
/// modularization of big application configuration.
|
||||
pub struct RouterConfig<P: 'static> {
|
||||
pub(crate) services: Vec<Box<ServiceFactory<P>>>,
|
||||
pub struct ServiceConfig {
|
||||
pub(crate) services: Vec<Box<ServiceFactory>>,
|
||||
pub(crate) data: Vec<Box<DataFactory>>,
|
||||
pub(crate) external: Vec<ResourceDef>,
|
||||
}
|
||||
|
||||
impl<P: 'static> RouterConfig<P> {
|
||||
impl ServiceConfig {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
services: Vec::new(),
|
||||
@@ -211,7 +211,7 @@ impl<P: 'static> RouterConfig<P> {
|
||||
/// Configure route for a specific path.
|
||||
///
|
||||
/// This is same as `App::route()` method.
|
||||
pub fn route(&mut self, path: &str, mut route: Route<P>) -> &mut Self {
|
||||
pub fn route(&mut self, path: &str, mut route: Route) -> &mut Self {
|
||||
self.service(
|
||||
Resource::new(path)
|
||||
.add_guards(route.take_guards())
|
||||
@@ -224,7 +224,7 @@ impl<P: 'static> RouterConfig<P> {
|
||||
/// This is same as `App::service()` method.
|
||||
pub fn service<F>(&mut self, factory: F) -> &mut Self
|
||||
where
|
||||
F: HttpServiceFactory<P> + 'static,
|
||||
F: HttpServiceFactory + 'static,
|
||||
{
|
||||
self.services
|
||||
.push(Box::new(ServiceFactoryWrapper::new(factory)));
|
||||
@@ -255,13 +255,13 @@ mod tests {
|
||||
use actix_service::Service;
|
||||
|
||||
use super::*;
|
||||
use crate::http::StatusCode;
|
||||
use crate::test::{block_on, init_service, TestRequest};
|
||||
use crate::http::{Method, StatusCode};
|
||||
use crate::test::{block_on, call_service, init_service, TestRequest};
|
||||
use crate::{web, App, HttpResponse};
|
||||
|
||||
#[test]
|
||||
fn test_data() {
|
||||
let cfg = |cfg: &mut RouterConfig<_>| {
|
||||
let cfg = |cfg: &mut ServiceConfig| {
|
||||
cfg.data(10usize);
|
||||
};
|
||||
|
||||
@@ -276,7 +276,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_data_factory() {
|
||||
let cfg = |cfg: &mut RouterConfig<_>| {
|
||||
let cfg = |cfg: &mut ServiceConfig| {
|
||||
cfg.data_factory(|| Ok::<_, ()>(10usize));
|
||||
};
|
||||
|
||||
@@ -288,7 +288,7 @@ mod tests {
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let cfg2 = |cfg: &mut RouterConfig<_>| {
|
||||
let cfg2 = |cfg: &mut ServiceConfig| {
|
||||
cfg.data_factory(|| Ok::<_, ()>(10u32));
|
||||
};
|
||||
let mut srv = init_service(
|
||||
@@ -300,4 +300,26 @@ mod tests {
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_service() {
|
||||
let mut srv = init_service(App::new().configure(|cfg| {
|
||||
cfg.service(
|
||||
web::resource("/test").route(web::get().to(|| HttpResponse::Created())),
|
||||
)
|
||||
.route("/index.html", web::get().to(|| HttpResponse::Ok()));
|
||||
}));
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::GET)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
|
||||
let req = TestRequest::with_uri("/index.html")
|
||||
.method(Method::GET)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
}
|
||||
|
11
src/data.rs
11
src/data.rs
@@ -61,6 +61,7 @@ pub(crate) trait DataFactoryResult {
|
||||
/// web::get().to(index)));
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Data<T>(Arc<T>);
|
||||
|
||||
impl<T> Data<T> {
|
||||
@@ -88,12 +89,13 @@ impl<T> Clone for Data<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, P> FromRequest<P> for Data<T> {
|
||||
impl<T: 'static> FromRequest for Data<T> {
|
||||
type Config = ();
|
||||
type Error = Error;
|
||||
type Future = Result<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
if let Some(st) = req.app_config().extensions().get::<Data<T>>() {
|
||||
Ok(st.clone())
|
||||
} else {
|
||||
@@ -232,12 +234,13 @@ impl<T> Clone for RouteData<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, P> FromRequest<P> for RouteData<T> {
|
||||
impl<T: 'static> FromRequest for RouteData<T> {
|
||||
type Config = ();
|
||||
type Error = Error;
|
||||
type Future = Result<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
if let Some(st) = req.route_data::<T>() {
|
||||
Ok(st.clone())
|
||||
} else {
|
||||
|
112
src/extract.rs
112
src/extract.rs
@@ -10,15 +10,18 @@ use crate::request::HttpRequest;
|
||||
/// Trait implemented by types that can be extracted from request.
|
||||
///
|
||||
/// Types that implement this trait can be used with `Route` handlers.
|
||||
pub trait FromRequest<P>: Sized {
|
||||
pub trait FromRequest: Sized {
|
||||
/// The associated error which can be returned.
|
||||
type Error: Into<Error>;
|
||||
|
||||
/// Future that resolves to a Self
|
||||
type Future: IntoFuture<Item = Self, Error = Self::Error>;
|
||||
|
||||
/// Configuration for this extractor
|
||||
type Config: Default + 'static;
|
||||
|
||||
/// Convert request to a Self
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future;
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future;
|
||||
|
||||
/// Convert request to a Self
|
||||
///
|
||||
@@ -26,6 +29,14 @@ pub trait FromRequest<P>: Sized {
|
||||
fn extract(req: &HttpRequest) -> Self::Future {
|
||||
Self::from_request(req, &mut Payload::None)
|
||||
}
|
||||
|
||||
/// Create and configure config instance.
|
||||
fn configure<F>(f: F) -> Self::Config
|
||||
where
|
||||
F: FnOnce(Self::Config) -> Self::Config,
|
||||
{
|
||||
f(Self::Config::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Optionally extract a field from the request
|
||||
@@ -45,11 +56,12 @@ pub trait FromRequest<P>: Sized {
|
||||
/// name: String
|
||||
/// }
|
||||
///
|
||||
/// impl<P> FromRequest<P> for Thing {
|
||||
/// impl FromRequest for Thing {
|
||||
/// type Error = Error;
|
||||
/// type Future = Result<Self, Self::Error>;
|
||||
/// type Config = ();
|
||||
///
|
||||
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future {
|
||||
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||
/// if rand::random() {
|
||||
/// Ok(Thing { name: "thingy".into() })
|
||||
/// } else {
|
||||
@@ -75,16 +87,17 @@ pub trait FromRequest<P>: Sized {
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
impl<T: 'static, P> FromRequest<P> for Option<T>
|
||||
impl<T: 'static> FromRequest for Option<T>
|
||||
where
|
||||
T: FromRequest<P>,
|
||||
T: FromRequest,
|
||||
T::Future: 'static,
|
||||
{
|
||||
type Config = T::Config;
|
||||
type Error = Error;
|
||||
type Future = Box<Future<Item = Option<T>, Error = Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
Box::new(
|
||||
T::from_request(req, payload)
|
||||
.into_future()
|
||||
@@ -116,11 +129,12 @@ where
|
||||
/// name: String
|
||||
/// }
|
||||
///
|
||||
/// impl<P> FromRequest<P> for Thing {
|
||||
/// impl FromRequest for Thing {
|
||||
/// type Error = Error;
|
||||
/// type Future = Result<Thing, Error>;
|
||||
/// type Config = ();
|
||||
///
|
||||
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future {
|
||||
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||
/// if rand::random() {
|
||||
/// Ok(Thing { name: "thingy".into() })
|
||||
/// } else {
|
||||
@@ -143,17 +157,18 @@ where
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
impl<T: 'static, P> FromRequest<P> for Result<T, T::Error>
|
||||
impl<T: 'static> FromRequest for Result<T, T::Error>
|
||||
where
|
||||
T: FromRequest<P>,
|
||||
T: FromRequest,
|
||||
T::Future: 'static,
|
||||
T::Error: 'static,
|
||||
{
|
||||
type Config = T::Config;
|
||||
type Error = Error;
|
||||
type Future = Box<Future<Item = Result<T, T::Error>, Error = Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
Box::new(
|
||||
T::from_request(req, payload)
|
||||
.into_future()
|
||||
@@ -166,11 +181,12 @@ where
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl<P> FromRequest<P> for () {
|
||||
impl FromRequest for () {
|
||||
type Config = ();
|
||||
type Error = Error;
|
||||
type Future = Result<(), Error>;
|
||||
|
||||
fn from_request(_: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -179,12 +195,13 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
|
||||
|
||||
/// FromRequest implementation for tuple
|
||||
#[doc(hidden)]
|
||||
impl<P, $($T: FromRequest<P> + 'static),+> FromRequest<P> for ($($T,)+)
|
||||
impl<$($T: FromRequest + 'static),+> FromRequest for ($($T,)+)
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = $fut_type<P, $($T),+>;
|
||||
type Future = $fut_type<$($T),+>;
|
||||
type Config = ($($T::Config),+);
|
||||
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
$fut_type {
|
||||
items: <($(Option<$T>,)+)>::default(),
|
||||
futs: ($($T::from_request(req, payload).into_future(),)+),
|
||||
@@ -193,12 +210,12 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct $fut_type<P, $($T: FromRequest<P>),+> {
|
||||
pub struct $fut_type<$($T: FromRequest),+> {
|
||||
items: ($(Option<$T>,)+),
|
||||
futs: ($(<$T::Future as futures::IntoFuture>::Future,)+),
|
||||
}
|
||||
|
||||
impl<P, $($T: FromRequest<P>),+> Future for $fut_type<P, $($T),+>
|
||||
impl<$($T: FromRequest),+> Future for $fut_type<$($T),+>
|
||||
{
|
||||
type Item = ($($T,)+);
|
||||
type Error = Error;
|
||||
@@ -248,13 +265,12 @@ tuple_from_req!(TupleFromRequest10, (0, A), (1, B), (2, C), (3, D), (4, E), (5,
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_http::http::header;
|
||||
use actix_router::ResourceDef;
|
||||
use bytes::Bytes;
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
use super::*;
|
||||
use crate::test::{block_on, TestRequest};
|
||||
use crate::types::{Form, FormConfig, Path, Query};
|
||||
use crate::types::{Form, FormConfig};
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq)]
|
||||
struct Info {
|
||||
@@ -333,58 +349,4 @@ mod tests {
|
||||
block_on(Result::<Form<Info>, Error>::from_request(&req, &mut pl)).unwrap();
|
||||
assert!(r.is_err());
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MyStruct {
|
||||
key: String,
|
||||
value: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Id {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Test2 {
|
||||
key: String,
|
||||
value: u32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_extract() {
|
||||
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
|
||||
|
||||
let resource = ResourceDef::new("/{key}/{value}/");
|
||||
resource.match_path(req.match_info_mut());
|
||||
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let s = Path::<MyStruct>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.key, "name");
|
||||
assert_eq!(s.value, "user1");
|
||||
|
||||
let s = Path::<(String, String)>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, "user1");
|
||||
|
||||
let s = Query::<Id>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.id, "test");
|
||||
|
||||
let mut req = TestRequest::with_uri("/name/32/").to_srv_request();
|
||||
let resource = ResourceDef::new("/{key}/{value}/");
|
||||
resource.match_path(req.match_info_mut());
|
||||
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let s = Path::<Test2>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.as_ref().key, "name");
|
||||
assert_eq!(s.value, 32);
|
||||
|
||||
let s = Path::<(String, u8)>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, 32);
|
||||
|
||||
let res = Path::<Vec<String>>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(res[0], "name".to_owned());
|
||||
assert_eq!(res[1], "32".to_owned());
|
||||
}
|
||||
}
|
||||
|
@@ -124,7 +124,7 @@ where
|
||||
pub trait AsyncFactory<T, R>: Clone + 'static
|
||||
where
|
||||
R: IntoFuture,
|
||||
R::Item: Into<Response>,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
fn call(&self, param: T) -> R;
|
||||
@@ -134,7 +134,7 @@ impl<F, R> AsyncFactory<(), R> for F
|
||||
where
|
||||
F: Fn() -> R + Clone + 'static,
|
||||
R: IntoFuture,
|
||||
R::Item: Into<Response>,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
fn call(&self, _: ()) -> R {
|
||||
@@ -147,7 +147,7 @@ pub struct AsyncHandler<F, T, R>
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
R: IntoFuture,
|
||||
R::Item: Into<Response>,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
hnd: F,
|
||||
@@ -158,7 +158,7 @@ impl<F, T, R> AsyncHandler<F, T, R>
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
R: IntoFuture,
|
||||
R::Item: Into<Response>,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
pub fn new(hnd: F) -> Self {
|
||||
@@ -173,7 +173,7 @@ impl<F, T, R> Clone for AsyncHandler<F, T, R>
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
R: IntoFuture,
|
||||
R::Item: Into<Response>,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
@@ -188,7 +188,7 @@ impl<F, T, R> Service for AsyncHandler<F, T, R>
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
R: IntoFuture,
|
||||
R::Item: Into<Response>,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
type Request = (T, HttpRequest);
|
||||
@@ -203,31 +203,38 @@ where
|
||||
fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future {
|
||||
AsyncHandlerServiceResponse {
|
||||
fut: self.hnd.call(param).into_future(),
|
||||
fut2: None,
|
||||
req: Some(req),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct AsyncHandlerServiceResponse<T> {
|
||||
pub struct AsyncHandlerServiceResponse<T>
|
||||
where
|
||||
T: Future,
|
||||
T::Item: Responder,
|
||||
{
|
||||
fut: T,
|
||||
fut2: Option<<<T::Item as Responder>::Future as IntoFuture>::Future>,
|
||||
req: Option<HttpRequest>,
|
||||
}
|
||||
|
||||
impl<T> Future for AsyncHandlerServiceResponse<T>
|
||||
where
|
||||
T: Future,
|
||||
T::Item: Into<Response>,
|
||||
T::Item: Responder,
|
||||
T::Error: Into<Error>,
|
||||
{
|
||||
type Item = ServiceResponse;
|
||||
type Error = Void;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.fut.poll() {
|
||||
if let Some(ref mut fut) = self.fut2 {
|
||||
return match fut.poll() {
|
||||
Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new(
|
||||
self.req.take().unwrap(),
|
||||
res.into(),
|
||||
res,
|
||||
))),
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(e) => {
|
||||
@@ -237,18 +244,35 @@ where
|
||||
res,
|
||||
)))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match self.fut.poll() {
|
||||
Ok(Async::Ready(res)) => {
|
||||
self.fut2 =
|
||||
Some(res.respond_to(self.req.as_ref().unwrap()).into_future());
|
||||
return self.poll();
|
||||
}
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(e) => {
|
||||
let res: Response = e.into().into();
|
||||
Ok(Async::Ready(ServiceResponse::new(
|
||||
self.req.take().unwrap(),
|
||||
res,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract arguments from request
|
||||
pub struct Extract<P, T: FromRequest<P>, S> {
|
||||
pub struct Extract<T: FromRequest, S> {
|
||||
config: Rc<RefCell<Option<Rc<Extensions>>>>,
|
||||
service: S,
|
||||
_t: PhantomData<(P, T)>,
|
||||
_t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<P, T: FromRequest<P>, S> Extract<P, T, S> {
|
||||
impl<T: FromRequest, S> Extract<T, S> {
|
||||
pub fn new(config: Rc<RefCell<Option<Rc<Extensions>>>>, service: S) -> Self {
|
||||
Extract {
|
||||
config,
|
||||
@@ -258,16 +282,16 @@ impl<P, T: FromRequest<P>, S> Extract<P, T, S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T: FromRequest<P>, S> NewService for Extract<P, T, S>
|
||||
impl<T: FromRequest, S> NewService for Extract<T, S>
|
||||
where
|
||||
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>
|
||||
+ Clone,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = (Error, ServiceRequest<P>);
|
||||
type Error = (Error, ServiceRequest);
|
||||
type InitError = ();
|
||||
type Service = ExtractService<P, T, S>;
|
||||
type Service = ExtractService<T, S>;
|
||||
type Future = FutureResult<Self::Service, ()>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
@@ -279,27 +303,27 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtractService<P, T: FromRequest<P>, S> {
|
||||
pub struct ExtractService<T: FromRequest, S> {
|
||||
config: Option<Rc<Extensions>>,
|
||||
service: S,
|
||||
_t: PhantomData<(P, T)>,
|
||||
_t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<P, T: FromRequest<P>, S> Service for ExtractService<P, T, S>
|
||||
impl<T: FromRequest, S> Service for ExtractService<T, S>
|
||||
where
|
||||
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>
|
||||
+ Clone,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = (Error, ServiceRequest<P>);
|
||||
type Future = ExtractResponse<P, T, S>;
|
||||
type Error = (Error, ServiceRequest);
|
||||
type Future = ExtractResponse<T, S>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
let (mut req, mut payload) = req.into_parts();
|
||||
req.set_route_data(self.config.clone());
|
||||
let fut = T::from_request(&req, &mut payload).into_future();
|
||||
@@ -313,19 +337,19 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtractResponse<P, T: FromRequest<P>, S: Service> {
|
||||
req: Option<(HttpRequest, Payload<P>)>,
|
||||
pub struct ExtractResponse<T: FromRequest, S: Service> {
|
||||
req: Option<(HttpRequest, Payload)>,
|
||||
service: S,
|
||||
fut: <T::Future as IntoFuture>::Future,
|
||||
fut_s: Option<S::Future>,
|
||||
}
|
||||
|
||||
impl<P, T: FromRequest<P>, S> Future for ExtractResponse<P, T, S>
|
||||
impl<T: FromRequest, S> Future for ExtractResponse<T, S>
|
||||
where
|
||||
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>,
|
||||
{
|
||||
type Item = ServiceResponse;
|
||||
type Error = (Error, ServiceRequest<P>);
|
||||
type Error = (Error, ServiceRequest);
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(ref mut fut) = self.fut_s {
|
||||
@@ -357,7 +381,7 @@ macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => {
|
||||
impl<Func, $($T,)+ Res> AsyncFactory<($($T,)+), Res> for Func
|
||||
where Func: Fn($($T,)+) -> Res + Clone + 'static,
|
||||
Res: IntoFuture,
|
||||
Res::Item: Into<Response>,
|
||||
Res::Item: Responder,
|
||||
Res::Error: Into<Error>,
|
||||
{
|
||||
fn call(&self, param: ($($T,)+)) -> Res {
|
||||
|
@@ -30,7 +30,7 @@ impl ConnectionInfo {
|
||||
let mut host = None;
|
||||
let mut scheme = None;
|
||||
let mut remote = None;
|
||||
let peer = None;
|
||||
let mut peer = None;
|
||||
|
||||
// load forwarded header
|
||||
for hdr in req.headers.get_all(&header::FORWARDED) {
|
||||
@@ -116,10 +116,10 @@ impl ConnectionInfo {
|
||||
remote = h.split(',').next().map(|v| v.trim());
|
||||
}
|
||||
}
|
||||
// if remote.is_none() {
|
||||
if remote.is_none() {
|
||||
// get peeraddr from socketaddr
|
||||
// peer = req.peer_addr().map(|addr| format!("{}", addr));
|
||||
// }
|
||||
peer = req.peer_addr.map(|addr| format!("{}", addr));
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionInfo {
|
||||
|
@@ -133,8 +133,7 @@ pub mod dev {
|
||||
//! use actix_web::dev::*;
|
||||
//! ```
|
||||
|
||||
pub use crate::app::AppRouter;
|
||||
pub use crate::config::{AppConfig, ServiceConfig};
|
||||
pub use crate::config::{AppConfig, AppService};
|
||||
pub use crate::info::ConnectionInfo;
|
||||
pub use crate::rmap::ResourceMap;
|
||||
pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse};
|
||||
@@ -143,6 +142,7 @@ pub mod dev {
|
||||
pub use crate::types::readlines::Readlines;
|
||||
|
||||
pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody};
|
||||
pub use actix_http::encoding::Decoder as Decompress;
|
||||
pub use actix_http::ResponseBuilder as HttpResponseBuilder;
|
||||
pub use actix_http::{
|
||||
Extensions, Payload, PayloadStream, RequestHead, ResponseHead,
|
||||
|
@@ -41,11 +41,11 @@ impl<B> BodyEncoding for Response<B> {
|
||||
/// To disable compression set encoding to `ContentEncoding::Identity` value.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, middleware::encoding, App, HttpResponse};
|
||||
/// use actix_web::{web, middleware, App, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .wrap(encoding::Compress::default())
|
||||
/// .wrap(middleware::Compress::default())
|
||||
/// .service(
|
||||
/// web::resource("/test")
|
||||
/// .route(web::get().to(|| HttpResponse::Ok()))
|
||||
@@ -68,12 +68,12 @@ impl Default for Compress {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, P, B> Transform<S> for Compress
|
||||
impl<S, B> Transform<S> for Compress
|
||||
where
|
||||
B: MessageBody,
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<Encoder<B>>;
|
||||
type Error = S::Error;
|
||||
type InitError = ();
|
||||
@@ -93,21 +93,21 @@ pub struct CompressMiddleware<S> {
|
||||
encoding: ContentEncoding,
|
||||
}
|
||||
|
||||
impl<S, P, B> Service for CompressMiddleware<S>
|
||||
impl<S, B> Service for CompressMiddleware<S>
|
||||
where
|
||||
B: MessageBody,
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<Encoder<B>>;
|
||||
type Error = S::Error;
|
||||
type Future = CompressResponse<S, P, B>;
|
||||
type Future = CompressResponse<S, B>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready()
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
// negotiate content-encoding
|
||||
let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) {
|
||||
if let Ok(enc) = val.to_str() {
|
||||
@@ -128,20 +128,20 @@ where
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct CompressResponse<S, P, B>
|
||||
pub struct CompressResponse<S, B>
|
||||
where
|
||||
S: Service,
|
||||
B: MessageBody,
|
||||
{
|
||||
fut: S::Future,
|
||||
encoding: ContentEncoding,
|
||||
_t: PhantomData<(P, B)>,
|
||||
_t: PhantomData<(B)>,
|
||||
}
|
||||
|
||||
impl<S, P, B> Future for CompressResponse<S, P, B>
|
||||
impl<S, B> Future for CompressResponse<S, B>
|
||||
where
|
||||
B: MessageBody,
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
|
||||
{
|
||||
type Item = ServiceResponse<Encoder<B>>;
|
||||
type Error = S::Error;
|
||||
|
@@ -475,9 +475,9 @@ fn cors<'a>(
|
||||
parts.as_mut()
|
||||
}
|
||||
|
||||
impl<S, P, B> IntoTransform<CorsFactory, S> for Cors
|
||||
impl<S, B> IntoTransform<CorsFactory, S> for Cors
|
||||
where
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
B: 'static,
|
||||
@@ -537,14 +537,14 @@ pub struct CorsFactory {
|
||||
inner: Rc<Inner>,
|
||||
}
|
||||
|
||||
impl<S, P, B> Transform<S> for CorsFactory
|
||||
impl<S, B> Transform<S> for CorsFactory
|
||||
where
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = S::Error;
|
||||
type InitError = ();
|
||||
@@ -678,14 +678,14 @@ impl Inner {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, P, B> Service for CorsMiddleware<S>
|
||||
impl<S, B> Service for CorsMiddleware<S>
|
||||
where
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = S::Error;
|
||||
type Future = Either<
|
||||
@@ -697,7 +697,7 @@ where
|
||||
self.service.poll_ready()
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
if self.inner.preflight && Method::OPTIONS == *req.method() {
|
||||
if let Err(e) = self
|
||||
.inner
|
||||
@@ -815,13 +815,12 @@ mod tests {
|
||||
use actix_service::{FnService, Transform};
|
||||
|
||||
use super::*;
|
||||
use crate::dev::PayloadStream;
|
||||
use crate::test::{self, block_on, TestRequest};
|
||||
|
||||
impl Cors {
|
||||
fn finish<S, P, B>(self, srv: S) -> CorsMiddleware<S>
|
||||
fn finish<S, B>(self, srv: S) -> CorsMiddleware<S>
|
||||
where
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>
|
||||
+ 'static,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
@@ -849,7 +848,18 @@ mod tests {
|
||||
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
.to_srv_request();
|
||||
|
||||
let resp = test::call_success(&mut cors, req);
|
||||
let resp = test::call_service(&mut cors, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
let mut cors =
|
||||
block_on(Cors::default().new_transform(test::ok_service())).unwrap();
|
||||
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
.to_srv_request();
|
||||
|
||||
let resp = test::call_service(&mut cors, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
@@ -869,7 +879,7 @@ mod tests {
|
||||
|
||||
assert!(cors.inner.validate_allowed_method(req.head()).is_err());
|
||||
assert!(cors.inner.validate_allowed_headers(req.head()).is_err());
|
||||
let resp = test::call_success(&mut cors, req);
|
||||
let resp = test::call_service(&mut cors, req);
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
@@ -889,7 +899,7 @@ mod tests {
|
||||
.method(Method::OPTIONS)
|
||||
.to_srv_request();
|
||||
|
||||
let resp = test::call_success(&mut cors, req);
|
||||
let resp = test::call_service(&mut cors, req);
|
||||
assert_eq!(
|
||||
&b"*"[..],
|
||||
resp.headers()
|
||||
@@ -935,7 +945,7 @@ mod tests {
|
||||
.method(Method::OPTIONS)
|
||||
.to_srv_request();
|
||||
|
||||
let resp = test::call_success(&mut cors, req);
|
||||
let resp = test::call_service(&mut cors, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
@@ -974,7 +984,7 @@ mod tests {
|
||||
.method(Method::GET)
|
||||
.to_srv_request();
|
||||
|
||||
let resp = test::call_success(&mut cors, req);
|
||||
let resp = test::call_service(&mut cors, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
@@ -983,7 +993,7 @@ mod tests {
|
||||
let mut cors = Cors::new().disable_preflight().finish(test::ok_service());
|
||||
|
||||
let req = TestRequest::default().method(Method::GET).to_srv_request();
|
||||
let resp = test::call_success(&mut cors, req);
|
||||
let resp = test::call_service(&mut cors, req);
|
||||
assert!(resp
|
||||
.headers()
|
||||
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
|
||||
@@ -992,7 +1002,7 @@ mod tests {
|
||||
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
.method(Method::OPTIONS)
|
||||
.to_srv_request();
|
||||
let resp = test::call_success(&mut cors, req);
|
||||
let resp = test::call_service(&mut cors, req);
|
||||
assert_eq!(
|
||||
&b"https://www.example.com"[..],
|
||||
resp.headers()
|
||||
@@ -1019,7 +1029,7 @@ mod tests {
|
||||
.method(Method::OPTIONS)
|
||||
.to_srv_request();
|
||||
|
||||
let resp = test::call_success(&mut cors, req);
|
||||
let resp = test::call_service(&mut cors, req);
|
||||
assert_eq!(
|
||||
&b"*"[..],
|
||||
resp.headers()
|
||||
@@ -1057,7 +1067,7 @@ mod tests {
|
||||
.allowed_headers(exposed_headers.clone())
|
||||
.expose_headers(exposed_headers.clone())
|
||||
.allowed_header(header::CONTENT_TYPE)
|
||||
.finish(FnService::new(move |req: ServiceRequest<PayloadStream>| {
|
||||
.finish(FnService::new(move |req: ServiceRequest| {
|
||||
req.into_response(
|
||||
HttpResponse::Ok().header(header::VARY, "Accept").finish(),
|
||||
)
|
||||
@@ -1065,7 +1075,7 @@ mod tests {
|
||||
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
.method(Method::OPTIONS)
|
||||
.to_srv_request();
|
||||
let resp = test::call_success(&mut cors, req);
|
||||
let resp = test::call_service(&mut cors, req);
|
||||
assert_eq!(
|
||||
&b"Accept, Origin"[..],
|
||||
resp.headers().get(header::VARY).unwrap().as_bytes()
|
||||
@@ -1081,7 +1091,7 @@ mod tests {
|
||||
.method(Method::OPTIONS)
|
||||
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
|
||||
.to_srv_request();
|
||||
let resp = test::call_success(&mut cors, req);
|
||||
let resp = test::call_service(&mut cors, req);
|
||||
|
||||
let origins_str = resp
|
||||
.headers()
|
||||
@@ -1105,7 +1115,7 @@ mod tests {
|
||||
.method(Method::GET)
|
||||
.to_srv_request();
|
||||
|
||||
let resp = test::call_success(&mut cors, req);
|
||||
let resp = test::call_service(&mut cors, req);
|
||||
assert_eq!(
|
||||
&b"https://example.com"[..],
|
||||
resp.headers()
|
||||
@@ -1118,7 +1128,7 @@ mod tests {
|
||||
.method(Method::GET)
|
||||
.to_srv_request();
|
||||
|
||||
let resp = test::call_success(&mut cors, req);
|
||||
let resp = test::call_service(&mut cors, req);
|
||||
assert_eq!(
|
||||
&b"https://example.org"[..],
|
||||
resp.headers()
|
||||
@@ -1141,7 +1151,7 @@ mod tests {
|
||||
.method(Method::OPTIONS)
|
||||
.to_srv_request();
|
||||
|
||||
let resp = test::call_success(&mut cors, req);
|
||||
let resp = test::call_service(&mut cors, req);
|
||||
assert_eq!(
|
||||
&b"https://example.com"[..],
|
||||
resp.headers()
|
||||
@@ -1155,7 +1165,7 @@ mod tests {
|
||||
.method(Method::OPTIONS)
|
||||
.to_srv_request();
|
||||
|
||||
let resp = test::call_success(&mut cors, req);
|
||||
let resp = test::call_service(&mut cors, req);
|
||||
assert_eq!(
|
||||
&b"https://example.org"[..],
|
||||
resp.headers()
|
||||
|
@@ -1,75 +0,0 @@
|
||||
//! Chain service for decompressing request payload.
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use actix_http::encoding::Decoder;
|
||||
use actix_service::{NewService, Service};
|
||||
use bytes::Bytes;
|
||||
use futures::future::{ok, FutureResult};
|
||||
use futures::{Async, Poll, Stream};
|
||||
|
||||
use crate::dev::Payload;
|
||||
use crate::error::{Error, PayloadError};
|
||||
use crate::service::ServiceRequest;
|
||||
|
||||
/// `Middleware` for decompressing request's payload.
|
||||
/// `Decompress` middleware must be added with `App::chain()` method.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, middleware::encoding, App, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .chain(encoding::Decompress::new())
|
||||
/// .service(
|
||||
/// web::resource("/test")
|
||||
/// .route(web::get().to(|| HttpResponse::Ok()))
|
||||
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Decompress<P>(PhantomData<P>);
|
||||
|
||||
impl<P> Decompress<P>
|
||||
where
|
||||
P: Stream<Item = Bytes, Error = PayloadError>,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Decompress(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> NewService for Decompress<P>
|
||||
where
|
||||
P: Stream<Item = Bytes, Error = PayloadError>,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceRequest<Decoder<Payload<P>>>;
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Service = Decompress<P>;
|
||||
type Future = FutureResult<Self::Service, Self::InitError>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
ok(Decompress(PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> Service for Decompress<P>
|
||||
where
|
||||
P: Stream<Item = Bytes, Error = PayloadError>,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceRequest<Decoder<Payload<P>>>;
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Self::Response, Self::Error>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||
let (req, payload) = req.into_parts();
|
||||
let payload = Decoder::from_headers(payload, req.headers());
|
||||
ok(ServiceRequest::from_parts(req, Payload::Stream(payload)))
|
||||
}
|
||||
}
|
@@ -85,12 +85,12 @@ impl DefaultHeaders {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, P, B> Transform<S> for DefaultHeaders
|
||||
impl<S, B> Transform<S> for DefaultHeaders
|
||||
where
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = S::Error;
|
||||
type InitError = ();
|
||||
@@ -110,12 +110,12 @@ pub struct DefaultHeadersMiddleware<S> {
|
||||
inner: Rc<Inner>,
|
||||
}
|
||||
|
||||
impl<S, P, B> Service for DefaultHeadersMiddleware<S>
|
||||
impl<S, B> Service for DefaultHeadersMiddleware<S>
|
||||
where
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = S::Error;
|
||||
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
|
||||
@@ -124,7 +124,7 @@ where
|
||||
self.service.poll_ready()
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
let inner = self.inner.clone();
|
||||
|
||||
Box::new(self.service.call(req).map(move |mut res| {
|
||||
@@ -171,7 +171,7 @@ mod tests {
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
||||
|
||||
let req = TestRequest::default().to_srv_request();
|
||||
let srv = FnService::new(|req: ServiceRequest<_>| {
|
||||
let srv = FnService::new(|req: ServiceRequest| {
|
||||
req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish())
|
||||
});
|
||||
let mut mw = block_on(
|
||||
@@ -186,7 +186,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_content_type() {
|
||||
let srv = FnService::new(|req: ServiceRequest<_>| {
|
||||
let srv = FnService::new(|req: ServiceRequest| {
|
||||
req.into_response(HttpResponse::Ok().finish())
|
||||
});
|
||||
let mut mw =
|
||||
|
@@ -81,18 +81,14 @@ impl<B> ErrorHandlers<B> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, P, B> Transform<S> for ErrorHandlers<B>
|
||||
impl<S, B> Transform<S> for ErrorHandlers<B>
|
||||
where
|
||||
S: Service<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
>,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
@@ -113,18 +109,14 @@ pub struct ErrorHandlersMiddleware<S, B> {
|
||||
handlers: Rc<HashMap<StatusCode, Box<ErrorHandler<B>>>>,
|
||||
}
|
||||
|
||||
impl<S, P, B> Service for ErrorHandlersMiddleware<S, B>
|
||||
impl<S, B> Service for ErrorHandlersMiddleware<S, B>
|
||||
where
|
||||
S: Service<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
>,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = Error;
|
||||
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
|
||||
@@ -133,7 +125,7 @@ where
|
||||
self.service.poll_ready()
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
let handlers = self.handlers.clone();
|
||||
|
||||
Box::new(self.service.call(req).and_then(move |res| {
|
||||
@@ -169,7 +161,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_handler() {
|
||||
let srv = FnService::new(|req: ServiceRequest<_>| {
|
||||
let srv = FnService::new(|req: ServiceRequest| {
|
||||
req.into_response(HttpResponse::InternalServerError().finish())
|
||||
});
|
||||
|
||||
@@ -180,7 +172,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let resp = test::call_success(&mut mw, TestRequest::default().to_srv_request());
|
||||
let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request());
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
||||
}
|
||||
|
||||
@@ -195,7 +187,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_handler_async() {
|
||||
let srv = FnService::new(|req: ServiceRequest<_>| {
|
||||
let srv = FnService::new(|req: ServiceRequest| {
|
||||
req.into_response(HttpResponse::InternalServerError().finish())
|
||||
});
|
||||
|
||||
@@ -206,7 +198,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let resp = test::call_success(&mut mw, TestRequest::default().to_srv_request());
|
||||
let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request());
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
||||
}
|
||||
}
|
||||
|
@@ -140,12 +140,13 @@ struct IdentityItem {
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
impl<P> FromRequest<P> for Identity {
|
||||
impl FromRequest for Identity {
|
||||
type Config = ();
|
||||
type Error = Error;
|
||||
type Future = Result<Identity, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
Ok(Identity(req.clone()))
|
||||
}
|
||||
}
|
||||
@@ -159,7 +160,7 @@ pub trait IdentityPolicy: Sized + 'static {
|
||||
type ResponseFuture: IntoFuture<Item = (), Error = Error>;
|
||||
|
||||
/// Parse the session from request and load data from a service identity.
|
||||
fn from_request<P>(&self, request: &mut ServiceRequest<P>) -> Self::Future;
|
||||
fn from_request(&self, request: &mut ServiceRequest) -> Self::Future;
|
||||
|
||||
/// Write changes to response
|
||||
fn to_response<B>(
|
||||
@@ -198,16 +199,15 @@ impl<T> IdentityService<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T, P, B> Transform<S> for IdentityService<T>
|
||||
impl<S, T, B> Transform<S> for IdentityService<T>
|
||||
where
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>> + 'static,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
T: IdentityPolicy,
|
||||
P: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = S::Error;
|
||||
type InitError = ();
|
||||
@@ -228,16 +228,15 @@ pub struct IdentityServiceMiddleware<S, T> {
|
||||
service: Rc<RefCell<S>>,
|
||||
}
|
||||
|
||||
impl<S, T, P, B> Service for IdentityServiceMiddleware<S, T>
|
||||
impl<S, T, B> Service for IdentityServiceMiddleware<S, T>
|
||||
where
|
||||
P: 'static,
|
||||
B: 'static,
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>> + 'static,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
T: IdentityPolicy,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = S::Error;
|
||||
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
|
||||
@@ -246,7 +245,7 @@ where
|
||||
self.service.borrow_mut().poll_ready()
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future {
|
||||
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
||||
let srv = self.service.clone();
|
||||
let backend = self.backend.clone();
|
||||
|
||||
@@ -348,7 +347,7 @@ impl CookieIdentityInner {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load<T>(&self, req: &ServiceRequest<T>) -> Option<String> {
|
||||
fn load(&self, req: &ServiceRequest) -> Option<String> {
|
||||
if let Ok(cookies) = req.cookies() {
|
||||
for cookie in cookies.iter() {
|
||||
if cookie.name() == self.name {
|
||||
@@ -428,8 +427,13 @@ impl CookieIdentityPolicy {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `max-age` field in the session cookie being built.
|
||||
pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy {
|
||||
/// Sets the `max-age` field in the session cookie being built with given number of seconds.
|
||||
pub fn max_age(self, seconds: i64) -> CookieIdentityPolicy {
|
||||
self.max_age_time(Duration::seconds(seconds))
|
||||
}
|
||||
|
||||
/// Sets the `max-age` field in the session cookie being built with `chrono::Duration`.
|
||||
pub fn max_age_time(mut self, value: Duration) -> CookieIdentityPolicy {
|
||||
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
|
||||
self
|
||||
}
|
||||
@@ -445,7 +449,7 @@ impl IdentityPolicy for CookieIdentityPolicy {
|
||||
type Future = Result<Option<String>, Error>;
|
||||
type ResponseFuture = Result<(), Error>;
|
||||
|
||||
fn from_request<P>(&self, req: &mut ServiceRequest<P>) -> Self::Future {
|
||||
fn from_request(&self, req: &mut ServiceRequest) -> Self::Future {
|
||||
Ok(self.0.load(req))
|
||||
}
|
||||
|
||||
@@ -501,15 +505,15 @@ mod tests {
|
||||
})),
|
||||
);
|
||||
let resp =
|
||||
test::call_success(&mut srv, TestRequest::with_uri("/index").to_request());
|
||||
test::call_service(&mut srv, TestRequest::with_uri("/index").to_request());
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let resp =
|
||||
test::call_success(&mut srv, TestRequest::with_uri("/login").to_request());
|
||||
test::call_service(&mut srv, TestRequest::with_uri("/login").to_request());
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let c = resp.response().cookies().next().unwrap().to_owned();
|
||||
|
||||
let resp = test::call_success(
|
||||
let resp = test::call_service(
|
||||
&mut srv,
|
||||
TestRequest::with_uri("/index")
|
||||
.cookie(c.clone())
|
||||
@@ -517,7 +521,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
|
||||
let resp = test::call_success(
|
||||
let resp = test::call_service(
|
||||
&mut srv,
|
||||
TestRequest::with_uri("/logout")
|
||||
.cookie(c.clone())
|
||||
@@ -526,4 +530,56 @@ mod tests {
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert!(resp.headers().contains_key(header::SET_COOKIE))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_identity_max_age_time() {
|
||||
let duration = Duration::days(1);
|
||||
let mut srv = test::init_service(
|
||||
App::new()
|
||||
.wrap(IdentityService::new(
|
||||
CookieIdentityPolicy::new(&[0; 32])
|
||||
.domain("www.rust-lang.org")
|
||||
.name("actix_auth")
|
||||
.path("/")
|
||||
.max_age_time(duration)
|
||||
.secure(true),
|
||||
))
|
||||
.service(web::resource("/login").to(|id: Identity| {
|
||||
id.remember("test".to_string());
|
||||
HttpResponse::Ok()
|
||||
})),
|
||||
);
|
||||
let resp =
|
||||
test::call_service(&mut srv, TestRequest::with_uri("/login").to_request());
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert!(resp.headers().contains_key(header::SET_COOKIE));
|
||||
let c = resp.response().cookies().next().unwrap().to_owned();
|
||||
assert_eq!(duration, c.max_age().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_identity_max_age() {
|
||||
let seconds = 60;
|
||||
let mut srv = test::init_service(
|
||||
App::new()
|
||||
.wrap(IdentityService::new(
|
||||
CookieIdentityPolicy::new(&[0; 32])
|
||||
.domain("www.rust-lang.org")
|
||||
.name("actix_auth")
|
||||
.path("/")
|
||||
.max_age(seconds)
|
||||
.secure(true),
|
||||
))
|
||||
.service(web::resource("/login").to(|id: Identity| {
|
||||
id.remember("test".to_string());
|
||||
HttpResponse::Ok()
|
||||
})),
|
||||
);
|
||||
let resp =
|
||||
test::call_service(&mut srv, TestRequest::with_uri("/login").to_request());
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert!(resp.headers().contains_key(header::SET_COOKIE));
|
||||
let c = resp.response().cookies().next().unwrap().to_owned();
|
||||
assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap());
|
||||
}
|
||||
}
|
||||
|
@@ -114,12 +114,12 @@ impl Default for Logger {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, P, B> Transform<S> for Logger
|
||||
impl<S, B> Transform<S> for Logger
|
||||
where
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
|
||||
B: MessageBody,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<StreamLog<B>>;
|
||||
type Error = S::Error;
|
||||
type InitError = ();
|
||||
@@ -140,21 +140,21 @@ pub struct LoggerMiddleware<S> {
|
||||
service: S,
|
||||
}
|
||||
|
||||
impl<S, P, B> Service for LoggerMiddleware<S>
|
||||
impl<S, B> Service for LoggerMiddleware<S>
|
||||
where
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
|
||||
B: MessageBody,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<StreamLog<B>>;
|
||||
type Error = S::Error;
|
||||
type Future = LoggerResponse<S, P, B>;
|
||||
type Future = LoggerResponse<S, B>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready()
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
if self.inner.exclude.contains(req.path()) {
|
||||
LoggerResponse {
|
||||
fut: self.service.call(req),
|
||||
@@ -180,7 +180,7 @@ where
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct LoggerResponse<S, P, B>
|
||||
pub struct LoggerResponse<S, B>
|
||||
where
|
||||
B: MessageBody,
|
||||
S: Service,
|
||||
@@ -188,13 +188,13 @@ where
|
||||
fut: S::Future,
|
||||
time: time::Tm,
|
||||
format: Option<Format>,
|
||||
_t: PhantomData<(P, B)>,
|
||||
_t: PhantomData<(B,)>,
|
||||
}
|
||||
|
||||
impl<S, P, B> Future for LoggerResponse<S, P, B>
|
||||
impl<S, B> Future for LoggerResponse<S, B>
|
||||
where
|
||||
B: MessageBody,
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
|
||||
{
|
||||
type Item = ServiceResponse<StreamLog<B>>;
|
||||
type Error = S::Error;
|
||||
@@ -363,13 +363,6 @@ impl FormatText {
|
||||
let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000.0;
|
||||
fmt.write_fmt(format_args!("{:.6}", rt))
|
||||
}
|
||||
// FormatText::RemoteAddr => {
|
||||
// if let Some(remote) = req.connection_info().remote() {
|
||||
// return remote.fmt(fmt);
|
||||
// } else {
|
||||
// "-".fmt(fmt)
|
||||
// }
|
||||
// }
|
||||
FormatText::EnvironHeader(ref name) => {
|
||||
if let Ok(val) = env::var(name) {
|
||||
fmt.write_fmt(format_args!("{}", val))
|
||||
@@ -402,7 +395,7 @@ impl FormatText {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_request<P>(&mut self, now: time::Tm, req: &ServiceRequest<P>) {
|
||||
fn render_request(&mut self, now: time::Tm, req: &ServiceRequest) {
|
||||
match *self {
|
||||
FormatText::RequestLine => {
|
||||
*self = if req.query_string().is_empty() {
|
||||
@@ -441,6 +434,14 @@ impl FormatText {
|
||||
};
|
||||
*self = FormatText::Str(s.to_string());
|
||||
}
|
||||
FormatText::RemoteAddr => {
|
||||
let s = if let Some(remote) = req.connection_info().remote() {
|
||||
FormatText::Str(remote.to_string())
|
||||
} else {
|
||||
FormatText::Str("-".to_string())
|
||||
};
|
||||
*self = s;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -464,7 +465,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_logger() {
|
||||
let srv = FnService::new(|req: ServiceRequest<_>| {
|
||||
let srv = FnService::new(|req: ServiceRequest| {
|
||||
req.into_response(
|
||||
HttpResponse::build(StatusCode::OK)
|
||||
.header("X-Test", "ttt")
|
||||
|
@@ -1,22 +1,16 @@
|
||||
//! Middlewares
|
||||
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||
mod compress;
|
||||
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||
mod decompress;
|
||||
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||
pub mod encoding {
|
||||
//! Middlewares for compressing/decompressing payloads.
|
||||
pub use super::compress::{BodyEncoding, Compress};
|
||||
pub use super::decompress::Decompress;
|
||||
}
|
||||
pub use self::compress::{BodyEncoding, Compress};
|
||||
|
||||
pub mod cors;
|
||||
mod defaultheaders;
|
||||
pub mod errhandlers;
|
||||
mod logger;
|
||||
mod normalize;
|
||||
|
||||
pub use self::defaultheaders::DefaultHeaders;
|
||||
pub use self::logger::Logger;
|
||||
pub use self::normalize::NormalizePath;
|
||||
|
||||
#[cfg(feature = "secure-cookies")]
|
||||
pub mod identity;
|
||||
|
108
src/middleware/normalize.rs
Normal file
108
src/middleware/normalize.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
//! `Middleware` to normalize request's URI
|
||||
|
||||
use actix_service::{Service, Transform};
|
||||
use futures::future::{self, FutureResult};
|
||||
use regex::Regex;
|
||||
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
/// `Middleware` to normalize request's URI in place
|
||||
///
|
||||
/// Performs following:
|
||||
///
|
||||
/// - Merges multiple slashes into one.
|
||||
pub struct NormalizePath;
|
||||
|
||||
impl<S> Transform<S> for NormalizePath
|
||||
where
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse>,
|
||||
{
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = S::Error;
|
||||
type InitError = ();
|
||||
type Transform = NormalizePathNormalization<S>;
|
||||
type Future = FutureResult<Self::Transform, Self::InitError>;
|
||||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
future::ok(NormalizePathNormalization {
|
||||
service,
|
||||
merge_slash: Regex::new("//+").unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NormalizePathNormalization<S> {
|
||||
service: S,
|
||||
merge_slash: Regex,
|
||||
}
|
||||
|
||||
impl<S> Service for NormalizePathNormalization<S>
|
||||
where
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse>,
|
||||
{
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
fn poll_ready(&mut self) -> futures::Poll<(), Self::Error> {
|
||||
self.service.poll_ready()
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
||||
let head = req.head_mut();
|
||||
|
||||
let path = head.uri.path();
|
||||
let original_len = path.len();
|
||||
let path = self.merge_slash.replace_all(path, "/");
|
||||
|
||||
if original_len != path.len() {
|
||||
head.uri = path.parse().unwrap();
|
||||
}
|
||||
|
||||
self.service.call(req)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_service::FnService;
|
||||
|
||||
use super::*;
|
||||
use crate::dev::ServiceRequest;
|
||||
use crate::test::{block_on, TestRequest};
|
||||
use crate::HttpResponse;
|
||||
|
||||
#[test]
|
||||
fn test_in_place_normalization() {
|
||||
let srv = FnService::new(|req: ServiceRequest| {
|
||||
assert_eq!("/v1/something/", req.path());
|
||||
req.into_response(HttpResponse::Ok().finish())
|
||||
});
|
||||
|
||||
let mut normalize = block_on(NormalizePath.new_transform(srv)).unwrap();
|
||||
|
||||
let req = TestRequest::with_uri("/v1//something////").to_srv_request();
|
||||
let res = block_on(normalize.call(req)).unwrap();
|
||||
assert!(res.status().is_success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_normalize_nothing() {
|
||||
const URI: &str = "/v1/something/";
|
||||
|
||||
let srv = FnService::new(|req: ServiceRequest| {
|
||||
assert_eq!(URI, req.path());
|
||||
req.into_response(HttpResponse::Ok().finish())
|
||||
});
|
||||
|
||||
let mut normalize = block_on(NormalizePath.new_transform(srv)).unwrap();
|
||||
|
||||
let req = TestRequest::with_uri(URI).to_srv_request();
|
||||
let res = block_on(normalize.call(req)).unwrap();
|
||||
assert!(res.status().is_success());
|
||||
}
|
||||
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use std::{fmt, net};
|
||||
|
||||
use actix_http::http::{HeaderMap, Method, Uri, Version};
|
||||
use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead};
|
||||
@@ -170,6 +170,17 @@ impl HttpRequest {
|
||||
self.url_for(name, &NO_PARAMS)
|
||||
}
|
||||
|
||||
/// Peer socket address
|
||||
///
|
||||
/// Peer address is actual socket address, if proxy is used in front of
|
||||
/// actix http server, then peer address would be address of this proxy.
|
||||
///
|
||||
/// To get client connection information `.connection_info()` should be used.
|
||||
#[inline]
|
||||
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
|
||||
self.head().peer_addr
|
||||
}
|
||||
|
||||
/// Get *ConnectionInfo* for the current request.
|
||||
#[inline]
|
||||
pub fn connection_info(&self) -> Ref<ConnectionInfo> {
|
||||
@@ -265,12 +276,13 @@ impl Drop for HttpRequest {
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
impl<P> FromRequest<P> for HttpRequest {
|
||||
impl FromRequest for HttpRequest {
|
||||
type Config = ();
|
||||
type Error = Error;
|
||||
type Future = Result<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
Ok(req.clone())
|
||||
}
|
||||
}
|
||||
@@ -323,7 +335,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::dev::{ResourceDef, ResourceMap};
|
||||
use crate::http::{header, StatusCode};
|
||||
use crate::test::{call_success, init_service, TestRequest};
|
||||
use crate::test::{call_service, init_service, TestRequest};
|
||||
use crate::{web, App, HttpResponse};
|
||||
|
||||
#[test]
|
||||
@@ -452,7 +464,7 @@ mod tests {
|
||||
));
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let mut srv = init_service(App::new().data(10u32).service(
|
||||
@@ -466,7 +478,7 @@ mod tests {
|
||||
));
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
158
src/resource.rs
158
src/resource.rs
@@ -1,4 +1,5 @@
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_http::{Error, Response};
|
||||
@@ -9,7 +10,7 @@ use actix_service::{
|
||||
use futures::future::{ok, Either, FutureResult};
|
||||
use futures::{Async, Future, IntoFuture, Poll};
|
||||
|
||||
use crate::dev::{insert_slash, HttpServiceFactory, ResourceDef, ServiceConfig};
|
||||
use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef};
|
||||
use crate::extract::FromRequest;
|
||||
use crate::guard::Guard;
|
||||
use crate::handler::{AsyncFactory, Factory};
|
||||
@@ -17,9 +18,8 @@ use crate::responder::Responder;
|
||||
use crate::route::{CreateRouteService, Route, RouteService};
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
|
||||
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>;
|
||||
type HttpNewService<P> =
|
||||
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
|
||||
type HttpService = BoxedService<ServiceRequest, ServiceResponse, Error>;
|
||||
type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||
|
||||
/// *Resource* is an entry in resources table which corresponds to requested URL.
|
||||
///
|
||||
@@ -43,18 +43,18 @@ type HttpNewService<P> =
|
||||
///
|
||||
/// If no matching route could be found, *405* response code get returned.
|
||||
/// Default behavior could be overriden with `default_resource()` method.
|
||||
pub struct Resource<P, T = ResourceEndpoint<P>> {
|
||||
pub struct Resource<T = ResourceEndpoint> {
|
||||
endpoint: T,
|
||||
rdef: String,
|
||||
name: Option<String>,
|
||||
routes: Vec<Route<P>>,
|
||||
routes: Vec<Route>,
|
||||
guards: Vec<Box<Guard>>,
|
||||
default: Rc<RefCell<Option<Rc<HttpNewService<P>>>>>,
|
||||
factory_ref: Rc<RefCell<Option<ResourceFactory<P>>>>,
|
||||
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
|
||||
factory_ref: Rc<RefCell<Option<ResourceFactory>>>,
|
||||
}
|
||||
|
||||
impl<P> Resource<P> {
|
||||
pub fn new(path: &str) -> Resource<P> {
|
||||
impl Resource {
|
||||
pub fn new(path: &str) -> Resource {
|
||||
let fref = Rc::new(RefCell::new(None));
|
||||
|
||||
Resource {
|
||||
@@ -69,11 +69,10 @@ impl<P> Resource<P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> Resource<P, T>
|
||||
impl<T> Resource<T>
|
||||
where
|
||||
P: 'static,
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
@@ -154,7 +153,7 @@ where
|
||||
/// # fn post_handler() {}
|
||||
/// # fn delete_handler() {}
|
||||
/// ```
|
||||
pub fn route(mut self, route: Route<P>) -> Self {
|
||||
pub fn route(mut self, route: Route) -> Self {
|
||||
self.routes.push(route.finish());
|
||||
self
|
||||
}
|
||||
@@ -182,7 +181,7 @@ where
|
||||
pub fn to<F, I, R>(mut self, handler: F) -> Self
|
||||
where
|
||||
F: Factory<I, R> + 'static,
|
||||
I: FromRequest<P> + 'static,
|
||||
I: FromRequest + 'static,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
self.routes.push(Route::new().to(handler));
|
||||
@@ -216,9 +215,9 @@ where
|
||||
pub fn to_async<F, I, R>(mut self, handler: F) -> Self
|
||||
where
|
||||
F: AsyncFactory<I, R>,
|
||||
I: FromRequest<P> + 'static,
|
||||
I: FromRequest + 'static,
|
||||
R: IntoFuture + 'static,
|
||||
R::Item: Into<Response>,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
self.routes.push(Route::new().to_async(handler));
|
||||
@@ -236,9 +235,8 @@ where
|
||||
self,
|
||||
mw: F,
|
||||
) -> Resource<
|
||||
P,
|
||||
impl NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
@@ -247,7 +245,7 @@ where
|
||||
where
|
||||
M: Transform<
|
||||
T::Service,
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
@@ -302,54 +300,54 @@ where
|
||||
self,
|
||||
mw: F,
|
||||
) -> Resource<
|
||||
P,
|
||||
impl NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
>
|
||||
where
|
||||
F: FnMut(ServiceRequest<P>, &mut T::Service) -> R + Clone,
|
||||
F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone,
|
||||
R: IntoFuture<Item = ServiceResponse, Error = Error>,
|
||||
{
|
||||
self.wrap(mw)
|
||||
}
|
||||
|
||||
/// Default resource to be used if no matching route could be found.
|
||||
/// Default service to be used if no matching route could be found.
|
||||
/// By default *405* response get returned. Resource does not use
|
||||
/// default handler from `App` or `Scope`.
|
||||
pub fn default_resource<F, R, U>(mut self, f: F) -> Self
|
||||
pub fn default_service<F, U>(mut self, f: F) -> Self
|
||||
where
|
||||
F: FnOnce(Resource<P>) -> R,
|
||||
R: IntoNewService<U>,
|
||||
F: IntoNewService<U>,
|
||||
U: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
> + 'static,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
// create and configure default resource
|
||||
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service(
|
||||
f(Resource::new("")).into_new_service().map_init_err(|_| ()),
|
||||
f.into_new_service().map_init_err(|e| {
|
||||
log::error!("Can not construct default service: {:?}", e)
|
||||
}),
|
||||
)))));
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> HttpServiceFactory<P> for Resource<P, T>
|
||||
impl<T> HttpServiceFactory for Resource<T>
|
||||
where
|
||||
P: 'static,
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
> + 'static,
|
||||
{
|
||||
fn register(mut self, config: &mut ServiceConfig<P>) {
|
||||
fn register(mut self, config: &mut AppService) {
|
||||
let guards = if self.guards.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -367,10 +365,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> IntoNewService<T> for Resource<P, T>
|
||||
impl<T> IntoNewService<T> for Resource<T>
|
||||
where
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
@@ -386,18 +384,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResourceFactory<P> {
|
||||
routes: Vec<Route<P>>,
|
||||
default: Rc<RefCell<Option<Rc<HttpNewService<P>>>>>,
|
||||
pub struct ResourceFactory {
|
||||
routes: Vec<Route>,
|
||||
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
|
||||
}
|
||||
|
||||
impl<P: 'static> NewService for ResourceFactory<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
impl NewService for ResourceFactory {
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Service = ResourceService<P>;
|
||||
type Future = CreateResourceService<P>;
|
||||
type Service = ResourceService;
|
||||
type Future = CreateResourceService;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
let default_fut = if let Some(ref default) = *self.default.borrow() {
|
||||
@@ -418,19 +416,19 @@ impl<P: 'static> NewService for ResourceFactory<P> {
|
||||
}
|
||||
}
|
||||
|
||||
enum CreateRouteServiceItem<P> {
|
||||
Future(CreateRouteService<P>),
|
||||
Service(RouteService<P>),
|
||||
enum CreateRouteServiceItem {
|
||||
Future(CreateRouteService),
|
||||
Service(RouteService),
|
||||
}
|
||||
|
||||
pub struct CreateResourceService<P> {
|
||||
fut: Vec<CreateRouteServiceItem<P>>,
|
||||
default: Option<HttpService<P>>,
|
||||
default_fut: Option<Box<Future<Item = HttpService<P>, Error = ()>>>,
|
||||
pub struct CreateResourceService {
|
||||
fut: Vec<CreateRouteServiceItem>,
|
||||
default: Option<HttpService>,
|
||||
default_fut: Option<Box<Future<Item = HttpService, Error = ()>>>,
|
||||
}
|
||||
|
||||
impl<P> Future for CreateResourceService<P> {
|
||||
type Item = ResourceService<P>;
|
||||
impl Future for CreateResourceService {
|
||||
type Item = ResourceService;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
@@ -477,13 +475,13 @@ impl<P> Future for CreateResourceService<P> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResourceService<P> {
|
||||
routes: Vec<RouteService<P>>,
|
||||
default: Option<HttpService<P>>,
|
||||
pub struct ResourceService {
|
||||
routes: Vec<RouteService>,
|
||||
default: Option<HttpService>,
|
||||
}
|
||||
|
||||
impl<P> Service for ResourceService<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
impl Service for ResourceService {
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Future = Either<
|
||||
@@ -495,7 +493,7 @@ impl<P> Service for ResourceService<P> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future {
|
||||
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
||||
for route in self.routes.iter_mut() {
|
||||
if route.check(&mut req) {
|
||||
return route.call(req);
|
||||
@@ -514,23 +512,23 @@ impl<P> Service for ResourceService<P> {
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct ResourceEndpoint<P> {
|
||||
factory: Rc<RefCell<Option<ResourceFactory<P>>>>,
|
||||
pub struct ResourceEndpoint {
|
||||
factory: Rc<RefCell<Option<ResourceFactory>>>,
|
||||
}
|
||||
|
||||
impl<P> ResourceEndpoint<P> {
|
||||
fn new(factory: Rc<RefCell<Option<ResourceFactory<P>>>>) -> Self {
|
||||
impl ResourceEndpoint {
|
||||
fn new(factory: Rc<RefCell<Option<ResourceFactory>>>) -> Self {
|
||||
ResourceEndpoint { factory }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: 'static> NewService for ResourceEndpoint<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
impl NewService for ResourceEndpoint {
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Service = ResourceService<P>;
|
||||
type Future = CreateResourceService<P>;
|
||||
type Service = ResourceService;
|
||||
type Future = CreateResourceService;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
self.factory.borrow_mut().as_mut().unwrap().new_service(&())
|
||||
@@ -547,16 +545,16 @@ mod tests {
|
||||
|
||||
use crate::http::{header, HeaderValue, Method, StatusCode};
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
use crate::test::{call_success, init_service, TestRequest};
|
||||
use crate::test::{call_service, init_service, TestRequest};
|
||||
use crate::{web, App, Error, HttpResponse};
|
||||
|
||||
fn md<S, P, B>(
|
||||
req: ServiceRequest<P>,
|
||||
fn md<S, B>(
|
||||
req: ServiceRequest,
|
||||
srv: &mut S,
|
||||
) -> impl IntoFuture<Item = ServiceResponse<B>, Error = Error>
|
||||
where
|
||||
S: Service<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
>,
|
||||
@@ -579,7 +577,7 @@ mod tests {
|
||||
),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
@@ -605,7 +603,7 @@ mod tests {
|
||||
),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
@@ -620,7 +618,7 @@ mod tests {
|
||||
sleep(Duration::from_millis(100)).then(|_| HttpResponse::Ok())
|
||||
})));
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
@@ -631,34 +629,38 @@ mod tests {
|
||||
.service(
|
||||
web::resource("/test").route(web::get().to(|| HttpResponse::Ok())),
|
||||
)
|
||||
.default_resource(|r| r.to(|| HttpResponse::BadRequest())),
|
||||
.default_service(|r: ServiceRequest| {
|
||||
r.into_response(HttpResponse::BadRequest())
|
||||
}),
|
||||
);
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::POST)
|
||||
.to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::resource("/test")
|
||||
.route(web::get().to(|| HttpResponse::Ok()))
|
||||
.default_resource(|r| r.to(|| HttpResponse::BadRequest())),
|
||||
.default_service(|r: ServiceRequest| {
|
||||
r.into_response(HttpResponse::BadRequest())
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::POST)
|
||||
.to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
@@ -307,11 +307,11 @@ pub(crate) mod tests {
|
||||
);
|
||||
|
||||
let req = TestRequest::with_uri("/none").to_request();
|
||||
let resp = TestRequest::block_on(srv.call(req)).unwrap();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let req = TestRequest::with_uri("/some").to_request();
|
||||
let resp = TestRequest::block_on(srv.call(req)).unwrap();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
match resp.response().body() {
|
||||
ResponseBody::Body(Body::Bytes(ref b)) => {
|
||||
|
149
src/route.rs
149
src/route.rs
@@ -1,8 +1,7 @@
|
||||
use std::cell::RefCell;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_http::{http::Method, Error, Extensions, Response};
|
||||
use actix_http::{http::Method, Error, Extensions};
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::future::{ok, Either, FutureResult};
|
||||
use futures::{Async, Future, IntoFuture, Poll};
|
||||
@@ -42,16 +41,16 @@ type BoxedRouteNewService<Req, Res> = Box<
|
||||
///
|
||||
/// Route uses builder-like pattern for configuration.
|
||||
/// If handler is not explicitly set, default *404 Not Found* handler is used.
|
||||
pub struct Route<P> {
|
||||
service: BoxedRouteNewService<ServiceRequest<P>, ServiceResponse>,
|
||||
pub struct Route {
|
||||
service: BoxedRouteNewService<ServiceRequest, ServiceResponse>,
|
||||
guards: Rc<Vec<Box<Guard>>>,
|
||||
data: Option<Extensions>,
|
||||
data_ref: Rc<RefCell<Option<Rc<Extensions>>>>,
|
||||
}
|
||||
|
||||
impl<P: 'static> Route<P> {
|
||||
impl Route {
|
||||
/// Create new route which matches any request.
|
||||
pub fn new() -> Route<P> {
|
||||
pub fn new() -> Route {
|
||||
let data_ref = Rc::new(RefCell::new(None));
|
||||
Route {
|
||||
service: Box::new(RouteNewService::new(Extract::new(
|
||||
@@ -74,13 +73,13 @@ impl<P: 'static> Route<P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> NewService for Route<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
impl NewService for Route {
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Service = RouteService<P>;
|
||||
type Future = CreateRouteService<P>;
|
||||
type Service = RouteService;
|
||||
type Future = CreateRouteService;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
CreateRouteService {
|
||||
@@ -90,17 +89,16 @@ impl<P> NewService for Route<P> {
|
||||
}
|
||||
}
|
||||
|
||||
type RouteFuture<P> = Box<
|
||||
Future<Item = BoxedRouteService<ServiceRequest<P>, ServiceResponse>, Error = ()>,
|
||||
>;
|
||||
type RouteFuture =
|
||||
Box<Future<Item = BoxedRouteService<ServiceRequest, ServiceResponse>, Error = ()>>;
|
||||
|
||||
pub struct CreateRouteService<P> {
|
||||
fut: RouteFuture<P>,
|
||||
pub struct CreateRouteService {
|
||||
fut: RouteFuture,
|
||||
guards: Rc<Vec<Box<Guard>>>,
|
||||
}
|
||||
|
||||
impl<P> Future for CreateRouteService<P> {
|
||||
type Item = RouteService<P>;
|
||||
impl Future for CreateRouteService {
|
||||
type Item = RouteService;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
@@ -114,13 +112,13 @@ impl<P> Future for CreateRouteService<P> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RouteService<P> {
|
||||
service: BoxedRouteService<ServiceRequest<P>, ServiceResponse>,
|
||||
pub struct RouteService {
|
||||
service: BoxedRouteService<ServiceRequest, ServiceResponse>,
|
||||
guards: Rc<Vec<Box<Guard>>>,
|
||||
}
|
||||
|
||||
impl<P> RouteService<P> {
|
||||
pub fn check(&self, req: &mut ServiceRequest<P>) -> bool {
|
||||
impl RouteService {
|
||||
pub fn check(&self, req: &mut ServiceRequest) -> bool {
|
||||
for f in self.guards.iter() {
|
||||
if !f.check(req.head()) {
|
||||
return false;
|
||||
@@ -130,8 +128,8 @@ impl<P> RouteService<P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> Service for RouteService<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
impl Service for RouteService {
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Future = Either<
|
||||
@@ -143,12 +141,12 @@ impl<P> Service for RouteService<P> {
|
||||
self.service.poll_ready()
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
self.service.call(req)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: 'static> Route<P> {
|
||||
impl Route {
|
||||
/// Add method guard to the route.
|
||||
///
|
||||
/// ```rust
|
||||
@@ -235,10 +233,10 @@ impl<P: 'static> Route<P> {
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub fn to<F, T, R>(mut self, handler: F) -> Route<P>
|
||||
pub fn to<F, T, R>(mut self, handler: F) -> Route
|
||||
where
|
||||
F: Factory<T, R> + 'static,
|
||||
T: FromRequest<P> + 'static,
|
||||
T: FromRequest + 'static,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
self.service = Box::new(RouteNewService::new(Extract::new(
|
||||
@@ -278,9 +276,9 @@ impl<P: 'static> Route<P> {
|
||||
pub fn to_async<F, T, R>(mut self, handler: F) -> Self
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
T: FromRequest<P> + 'static,
|
||||
T: FromRequest + 'static,
|
||||
R: IntoFuture + 'static,
|
||||
R::Item: Into<Response>,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
self.service = Box::new(RouteNewService::new(Extract::new(
|
||||
@@ -294,7 +292,7 @@ impl<P: 'static> Route<P> {
|
||||
/// configuration or specific state available via `RouteData<T>` extractor.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, App};
|
||||
/// use actix_web::{web, App, FromRequest};
|
||||
///
|
||||
/// /// extract text data from request
|
||||
/// fn index(body: String) -> String {
|
||||
@@ -306,13 +304,15 @@ impl<P: 'static> Route<P> {
|
||||
/// web::resource("/index.html").route(
|
||||
/// web::get()
|
||||
/// // limit size of the payload
|
||||
/// .data(web::PayloadConfig::new(4096))
|
||||
/// .data(String::configure(|cfg| {
|
||||
/// cfg.limit(4096)
|
||||
/// }))
|
||||
/// // register handler
|
||||
/// .to(index)
|
||||
/// ));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn data<C: 'static>(mut self, data: C) -> Self {
|
||||
pub fn data<T: 'static>(mut self, data: T) -> Self {
|
||||
if self.data.is_none() {
|
||||
self.data = Some(Extensions::new());
|
||||
}
|
||||
@@ -321,49 +321,45 @@ impl<P: 'static> Route<P> {
|
||||
}
|
||||
}
|
||||
|
||||
struct RouteNewService<P, T>
|
||||
struct RouteNewService<T>
|
||||
where
|
||||
T: NewService<Request = ServiceRequest<P>, Error = (Error, ServiceRequest<P>)>,
|
||||
T: NewService<Request = ServiceRequest, Error = (Error, ServiceRequest)>,
|
||||
{
|
||||
service: T,
|
||||
_t: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: 'static, T> RouteNewService<P, T>
|
||||
impl<T> RouteNewService<T>
|
||||
where
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = (Error, ServiceRequest<P>),
|
||||
Error = (Error, ServiceRequest),
|
||||
>,
|
||||
T::Future: 'static,
|
||||
T::Service: 'static,
|
||||
<T::Service as Service>::Future: 'static,
|
||||
{
|
||||
pub fn new(service: T) -> Self {
|
||||
RouteNewService {
|
||||
service,
|
||||
_t: PhantomData,
|
||||
}
|
||||
RouteNewService { service }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: 'static, T> NewService for RouteNewService<P, T>
|
||||
impl<T> NewService for RouteNewService<T>
|
||||
where
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = (Error, ServiceRequest<P>),
|
||||
Error = (Error, ServiceRequest),
|
||||
>,
|
||||
T::Future: 'static,
|
||||
T::Service: 'static,
|
||||
<T::Service as Service>::Future: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Service = BoxedRouteService<ServiceRequest<P>, Self::Response>;
|
||||
type Service = BoxedRouteService<ServiceRequest, Self::Response>;
|
||||
type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
@@ -373,31 +369,27 @@ where
|
||||
.map_err(|_| ())
|
||||
.and_then(|service| {
|
||||
let service: BoxedRouteService<_, _> =
|
||||
Box::new(RouteServiceWrapper {
|
||||
service,
|
||||
_t: PhantomData,
|
||||
});
|
||||
Box::new(RouteServiceWrapper { service });
|
||||
Ok(service)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct RouteServiceWrapper<P, T: Service> {
|
||||
struct RouteServiceWrapper<T: Service> {
|
||||
service: T,
|
||||
_t: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P, T> Service for RouteServiceWrapper<P, T>
|
||||
impl<T> Service for RouteServiceWrapper<T>
|
||||
where
|
||||
T::Future: 'static,
|
||||
T: Service<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = (Error, ServiceRequest<P>),
|
||||
Error = (Error, ServiceRequest),
|
||||
>,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Future = Either<
|
||||
@@ -409,7 +401,7 @@ where
|
||||
self.service.poll_ready().map_err(|(e, _)| e)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
let mut fut = self.service.call(req);
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(res)) => Either::A(ok(res)),
|
||||
@@ -426,18 +418,25 @@ where
|
||||
mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::Future;
|
||||
use serde_derive::Serialize;
|
||||
use tokio_timer::sleep;
|
||||
|
||||
use crate::http::{Method, StatusCode};
|
||||
use crate::test::{call_success, init_service, TestRequest};
|
||||
use crate::test::{call_service, init_service, read_body, TestRequest};
|
||||
use crate::{error, web, App, HttpResponse};
|
||||
|
||||
#[derive(Serialize, PartialEq, Debug)]
|
||||
struct MyObject {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_route() {
|
||||
let mut srv =
|
||||
init_service(
|
||||
App::new().service(
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(
|
||||
web::resource("/test")
|
||||
.route(web::get().to(|| HttpResponse::Ok()))
|
||||
.route(web::put().to(|| {
|
||||
@@ -452,37 +451,51 @@ mod tests {
|
||||
Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
|
||||
})
|
||||
})),
|
||||
),
|
||||
)
|
||||
.service(web::resource("/json").route(web::get().to_async(|| {
|
||||
sleep(Duration::from_millis(25)).then(|_| {
|
||||
Ok::<_, crate::Error>(web::Json(MyObject {
|
||||
name: "test".to_string(),
|
||||
}))
|
||||
})
|
||||
}))),
|
||||
);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::GET)
|
||||
.to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::POST)
|
||||
.to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::PUT)
|
||||
.to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::DELETE)
|
||||
.to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::HEAD)
|
||||
.to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
|
||||
let req = TestRequest::with_uri("/json").to_request();
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let body = read_body(resp);
|
||||
assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}"));
|
||||
}
|
||||
}
|
||||
|
151
src/scope.rs
151
src/scope.rs
@@ -1,4 +1,5 @@
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_http::Response;
|
||||
@@ -10,7 +11,7 @@ use actix_service::{
|
||||
use futures::future::{ok, Either, Future, FutureResult};
|
||||
use futures::{Async, IntoFuture, Poll};
|
||||
|
||||
use crate::dev::{HttpServiceFactory, ServiceConfig};
|
||||
use crate::dev::{AppService, HttpServiceFactory};
|
||||
use crate::error::Error;
|
||||
use crate::guard::Guard;
|
||||
use crate::resource::Resource;
|
||||
@@ -21,9 +22,8 @@ use crate::service::{
|
||||
};
|
||||
|
||||
type Guards = Vec<Box<Guard>>;
|
||||
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>;
|
||||
type HttpNewService<P> =
|
||||
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
|
||||
type HttpService = BoxedService<ServiceRequest, ServiceResponse, Error>;
|
||||
type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||
type BoxedResponse = Either<
|
||||
FutureResult<ServiceResponse, Error>,
|
||||
Box<Future<Item = ServiceResponse, Error = Error>>,
|
||||
@@ -58,18 +58,18 @@ type BoxedResponse = Either<
|
||||
/// * /{project_id}/path2 - `GET` requests
|
||||
/// * /{project_id}/path3 - `HEAD` requests
|
||||
///
|
||||
pub struct Scope<P, T = ScopeEndpoint<P>> {
|
||||
pub struct Scope<T = ScopeEndpoint> {
|
||||
endpoint: T,
|
||||
rdef: String,
|
||||
services: Vec<Box<ServiceFactory<P>>>,
|
||||
services: Vec<Box<ServiceFactory>>,
|
||||
guards: Vec<Box<Guard>>,
|
||||
default: Rc<RefCell<Option<Rc<HttpNewService<P>>>>>,
|
||||
factory_ref: Rc<RefCell<Option<ScopeFactory<P>>>>,
|
||||
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
|
||||
factory_ref: Rc<RefCell<Option<ScopeFactory>>>,
|
||||
}
|
||||
|
||||
impl<P: 'static> Scope<P> {
|
||||
impl Scope {
|
||||
/// Create a new scope
|
||||
pub fn new(path: &str) -> Scope<P> {
|
||||
pub fn new(path: &str) -> Scope {
|
||||
let fref = Rc::new(RefCell::new(None));
|
||||
Scope {
|
||||
endpoint: ScopeEndpoint::new(fref.clone()),
|
||||
@@ -82,11 +82,10 @@ impl<P: 'static> Scope<P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> Scope<P, T>
|
||||
impl<T> Scope<T>
|
||||
where
|
||||
P: 'static,
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
@@ -146,7 +145,7 @@ where
|
||||
/// ```
|
||||
pub fn service<F>(mut self, factory: F) -> Self
|
||||
where
|
||||
F: HttpServiceFactory<P> + 'static,
|
||||
F: HttpServiceFactory + 'static,
|
||||
{
|
||||
self.services
|
||||
.push(Box::new(ServiceFactoryWrapper::new(factory)));
|
||||
@@ -174,7 +173,7 @@ where
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub fn route(self, path: &str, mut route: Route<P>) -> Self {
|
||||
pub fn route(self, path: &str, mut route: Route) -> Self {
|
||||
self.service(
|
||||
Resource::new(path)
|
||||
.add_guards(route.take_guards())
|
||||
@@ -182,22 +181,24 @@ where
|
||||
)
|
||||
}
|
||||
|
||||
/// Default resource to be used if no matching route could be found.
|
||||
/// Default service to be used if no matching route could be found.
|
||||
///
|
||||
/// If default resource is not registered, app's default resource is being used.
|
||||
pub fn default_resource<F, U>(mut self, f: F) -> Self
|
||||
pub fn default_service<F, U>(mut self, f: F) -> Self
|
||||
where
|
||||
F: FnOnce(Resource<P>) -> Resource<P, U>,
|
||||
F: IntoNewService<U>,
|
||||
U: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
> + 'static,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
// create and configure default resource
|
||||
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service(
|
||||
f(Resource::new("")).into_new_service().map_init_err(|_| ()),
|
||||
f.into_new_service().map_init_err(|e| {
|
||||
log::error!("Can not construct default service: {:?}", e)
|
||||
}),
|
||||
)))));
|
||||
|
||||
self
|
||||
@@ -216,9 +217,8 @@ where
|
||||
self,
|
||||
mw: F,
|
||||
) -> Scope<
|
||||
P,
|
||||
impl NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
@@ -227,7 +227,7 @@ where
|
||||
where
|
||||
M: Transform<
|
||||
T::Service,
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
@@ -279,33 +279,31 @@ where
|
||||
self,
|
||||
mw: F,
|
||||
) -> Scope<
|
||||
P,
|
||||
impl NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
>
|
||||
where
|
||||
F: FnMut(ServiceRequest<P>, &mut T::Service) -> R + Clone,
|
||||
F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone,
|
||||
R: IntoFuture<Item = ServiceResponse, Error = Error>,
|
||||
{
|
||||
self.wrap(mw)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> HttpServiceFactory<P> for Scope<P, T>
|
||||
impl<T> HttpServiceFactory for Scope<T>
|
||||
where
|
||||
P: 'static,
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
> + 'static,
|
||||
{
|
||||
fn register(self, config: &mut ServiceConfig<P>) {
|
||||
fn register(self, config: &mut AppService) {
|
||||
// update default resource if needed
|
||||
if self.default.borrow().is_none() {
|
||||
*self.default.borrow_mut() = Some(config.default_service());
|
||||
@@ -350,18 +348,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScopeFactory<P> {
|
||||
services: Rc<Vec<(ResourceDef, HttpNewService<P>, RefCell<Option<Guards>>)>>,
|
||||
default: Rc<RefCell<Option<Rc<HttpNewService<P>>>>>,
|
||||
pub struct ScopeFactory {
|
||||
services: Rc<Vec<(ResourceDef, HttpNewService, RefCell<Option<Guards>>)>>,
|
||||
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
|
||||
}
|
||||
|
||||
impl<P: 'static> NewService for ScopeFactory<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
impl NewService for ScopeFactory {
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Service = ScopeService<P>;
|
||||
type Future = ScopeFactoryResponse<P>;
|
||||
type Service = ScopeService;
|
||||
type Future = ScopeFactoryResponse;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
let default_fut = if let Some(ref default) = *self.default.borrow() {
|
||||
@@ -390,21 +388,21 @@ impl<P: 'static> NewService for ScopeFactory<P> {
|
||||
|
||||
/// Create scope service
|
||||
#[doc(hidden)]
|
||||
pub struct ScopeFactoryResponse<P> {
|
||||
fut: Vec<CreateScopeServiceItem<P>>,
|
||||
default: Option<HttpService<P>>,
|
||||
default_fut: Option<Box<Future<Item = HttpService<P>, Error = ()>>>,
|
||||
pub struct ScopeFactoryResponse {
|
||||
fut: Vec<CreateScopeServiceItem>,
|
||||
default: Option<HttpService>,
|
||||
default_fut: Option<Box<Future<Item = HttpService, Error = ()>>>,
|
||||
}
|
||||
|
||||
type HttpServiceFut<P> = Box<Future<Item = HttpService<P>, Error = ()>>;
|
||||
type HttpServiceFut = Box<Future<Item = HttpService, Error = ()>>;
|
||||
|
||||
enum CreateScopeServiceItem<P> {
|
||||
Future(Option<ResourceDef>, Option<Guards>, HttpServiceFut<P>),
|
||||
Service(ResourceDef, Option<Guards>, HttpService<P>),
|
||||
enum CreateScopeServiceItem {
|
||||
Future(Option<ResourceDef>, Option<Guards>, HttpServiceFut),
|
||||
Service(ResourceDef, Option<Guards>, HttpService),
|
||||
}
|
||||
|
||||
impl<P> Future for ScopeFactoryResponse<P> {
|
||||
type Item = ScopeService<P>;
|
||||
impl Future for ScopeFactoryResponse {
|
||||
type Item = ScopeService;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
@@ -465,14 +463,14 @@ impl<P> Future for ScopeFactoryResponse<P> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScopeService<P> {
|
||||
router: Router<HttpService<P>, Vec<Box<Guard>>>,
|
||||
default: Option<HttpService<P>>,
|
||||
_ready: Option<(ServiceRequest<P>, ResourceInfo)>,
|
||||
pub struct ScopeService {
|
||||
router: Router<HttpService, Vec<Box<Guard>>>,
|
||||
default: Option<HttpService>,
|
||||
_ready: Option<(ServiceRequest, ResourceInfo)>,
|
||||
}
|
||||
|
||||
impl<P> Service for ScopeService<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
impl Service for ScopeService {
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Future = Either<BoxedResponse, FutureResult<Self::Response, Self::Error>>;
|
||||
@@ -481,7 +479,7 @@ impl<P> Service for ScopeService<P> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future {
|
||||
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
||||
let res = self.router.recognize_mut_checked(&mut req, |req, guards| {
|
||||
if let Some(ref guards) = guards {
|
||||
for f in guards {
|
||||
@@ -505,23 +503,23 @@ impl<P> Service for ScopeService<P> {
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct ScopeEndpoint<P> {
|
||||
factory: Rc<RefCell<Option<ScopeFactory<P>>>>,
|
||||
pub struct ScopeEndpoint {
|
||||
factory: Rc<RefCell<Option<ScopeFactory>>>,
|
||||
}
|
||||
|
||||
impl<P> ScopeEndpoint<P> {
|
||||
fn new(factory: Rc<RefCell<Option<ScopeFactory<P>>>>) -> Self {
|
||||
impl ScopeEndpoint {
|
||||
fn new(factory: Rc<RefCell<Option<ScopeFactory>>>) -> Self {
|
||||
ScopeEndpoint { factory }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: 'static> NewService for ScopeEndpoint<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
impl NewService for ScopeEndpoint {
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Service = ScopeService<P>;
|
||||
type Future = ScopeFactoryResponse<P>;
|
||||
type Service = ScopeService;
|
||||
type Future = ScopeFactoryResponse;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
self.factory.borrow_mut().as_mut().unwrap().new_service(&())
|
||||
@@ -537,7 +535,7 @@ mod tests {
|
||||
use crate::dev::{Body, ResponseBody};
|
||||
use crate::http::{header, HeaderValue, Method, StatusCode};
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
use crate::test::{block_on, call_success, init_service, TestRequest};
|
||||
use crate::test::{block_on, call_service, init_service, TestRequest};
|
||||
use crate::{guard, web, App, Error, HttpRequest, HttpResponse};
|
||||
|
||||
#[test]
|
||||
@@ -848,7 +846,9 @@ mod tests {
|
||||
App::new().service(
|
||||
web::scope("/app")
|
||||
.service(web::resource("/path1").to(|| HttpResponse::Ok()))
|
||||
.default_resource(|r| r.to(|| HttpResponse::BadRequest())),
|
||||
.default_service(|r: ServiceRequest| {
|
||||
r.into_response(HttpResponse::BadRequest())
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -865,12 +865,13 @@ mod tests {
|
||||
fn test_default_resource_propagation() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(
|
||||
web::scope("/app1")
|
||||
.default_resource(|r| r.to(|| HttpResponse::BadRequest())),
|
||||
)
|
||||
.service(web::scope("/app1").default_service(
|
||||
web::resource("").to(|| HttpResponse::BadRequest()),
|
||||
))
|
||||
.service(web::scope("/app2"))
|
||||
.default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())),
|
||||
.default_service(|r: ServiceRequest| {
|
||||
r.into_response(HttpResponse::MethodNotAllowed())
|
||||
}),
|
||||
);
|
||||
|
||||
let req = TestRequest::with_uri("/non-exist").to_request();
|
||||
@@ -886,13 +887,13 @@ mod tests {
|
||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
fn md<S, P, B>(
|
||||
req: ServiceRequest<P>,
|
||||
fn md<S, B>(
|
||||
req: ServiceRequest,
|
||||
srv: &mut S,
|
||||
) -> impl IntoFuture<Item = ServiceResponse<B>, Error = Error>
|
||||
where
|
||||
S: Service<
|
||||
Request = ServiceRequest<P>,
|
||||
Request = ServiceRequest,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
>,
|
||||
@@ -911,7 +912,7 @@ mod tests {
|
||||
web::resource("/test").route(web::get().to(|| HttpResponse::Ok())),
|
||||
)));
|
||||
let req = TestRequest::with_uri("/app/test").to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
@@ -937,7 +938,7 @@ mod tests {
|
||||
),
|
||||
);
|
||||
let req = TestRequest::with_uri("/app/test").to_request();
|
||||
let resp = call_success(&mut srv, req);
|
||||
let resp = call_service(&mut srv, req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
|
@@ -1,6 +1,5 @@
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::{fmt, net};
|
||||
|
||||
use actix_http::body::{Body, MessageBody, ResponseBody};
|
||||
use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version};
|
||||
@@ -11,63 +10,62 @@ use actix_http::{
|
||||
use actix_router::{Path, Resource, Url};
|
||||
use futures::future::{ok, FutureResult, IntoFuture};
|
||||
|
||||
use crate::config::{AppConfig, ServiceConfig};
|
||||
use crate::config::{AppConfig, AppService};
|
||||
use crate::data::Data;
|
||||
use crate::info::ConnectionInfo;
|
||||
use crate::request::HttpRequest;
|
||||
|
||||
pub trait HttpServiceFactory<P> {
|
||||
fn register(self, config: &mut ServiceConfig<P>);
|
||||
pub trait HttpServiceFactory {
|
||||
fn register(self, config: &mut AppService);
|
||||
}
|
||||
|
||||
pub(crate) trait ServiceFactory<P> {
|
||||
fn register(&mut self, config: &mut ServiceConfig<P>);
|
||||
pub(crate) trait ServiceFactory {
|
||||
fn register(&mut self, config: &mut AppService);
|
||||
}
|
||||
|
||||
pub(crate) struct ServiceFactoryWrapper<T, P> {
|
||||
pub(crate) struct ServiceFactoryWrapper<T> {
|
||||
factory: Option<T>,
|
||||
_t: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<T, P> ServiceFactoryWrapper<T, P> {
|
||||
impl<T> ServiceFactoryWrapper<T> {
|
||||
pub fn new(factory: T) -> Self {
|
||||
Self {
|
||||
factory: Some(factory),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P> ServiceFactory<P> for ServiceFactoryWrapper<T, P>
|
||||
impl<T> ServiceFactory for ServiceFactoryWrapper<T>
|
||||
where
|
||||
T: HttpServiceFactory<P>,
|
||||
T: HttpServiceFactory,
|
||||
{
|
||||
fn register(&mut self, config: &mut ServiceConfig<P>) {
|
||||
fn register(&mut self, config: &mut AppService) {
|
||||
if let Some(item) = self.factory.take() {
|
||||
item.register(config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServiceRequest<P = PayloadStream> {
|
||||
pub struct ServiceRequest {
|
||||
req: HttpRequest,
|
||||
payload: Payload<P>,
|
||||
payload: Payload,
|
||||
}
|
||||
|
||||
impl<P> ServiceRequest<P> {
|
||||
impl ServiceRequest {
|
||||
/// Construct service request from parts
|
||||
pub fn from_parts(req: HttpRequest, payload: Payload<P>) -> Self {
|
||||
pub(crate) fn from_parts(req: HttpRequest, payload: Payload) -> Self {
|
||||
ServiceRequest { req, payload }
|
||||
}
|
||||
|
||||
/// Deconstruct request into parts
|
||||
pub fn into_parts(self) -> (HttpRequest, Payload<P>) {
|
||||
pub fn into_parts(self) -> (HttpRequest, Payload) {
|
||||
(self.req, self.payload)
|
||||
}
|
||||
|
||||
/// Create service response
|
||||
#[inline]
|
||||
pub fn into_response<B>(self, res: Response<B>) -> ServiceResponse<B> {
|
||||
ServiceResponse::new(self.req, res)
|
||||
pub fn into_response<B, R: Into<Response<B>>>(self, res: R) -> ServiceResponse<B> {
|
||||
ServiceResponse::new(self.req, res.into())
|
||||
}
|
||||
|
||||
/// Create service response for error
|
||||
@@ -137,6 +135,23 @@ impl<P> ServiceRequest<P> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Peer socket address
|
||||
///
|
||||
/// Peer address is actual socket address, if proxy is used in front of
|
||||
/// actix http server, then peer address would be address of this proxy.
|
||||
///
|
||||
/// To get client connection information `ConnectionInfo` should be used.
|
||||
#[inline]
|
||||
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
|
||||
self.head().peer_addr
|
||||
}
|
||||
|
||||
/// Get *ConnectionInfo* for the current request.
|
||||
#[inline]
|
||||
pub fn connection_info(&self) -> Ref<ConnectionInfo> {
|
||||
ConnectionInfo::get(self.head(), &*self.app_config())
|
||||
}
|
||||
|
||||
/// Get a reference to the Path parameters.
|
||||
///
|
||||
/// Params is a container for url parameters.
|
||||
@@ -170,14 +185,14 @@ impl<P> ServiceRequest<P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> Resource<Url> for ServiceRequest<P> {
|
||||
impl Resource<Url> for ServiceRequest {
|
||||
fn resource_path(&mut self) -> &mut Path<Url> {
|
||||
self.match_info_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> HttpMessage for ServiceRequest<P> {
|
||||
type Stream = P;
|
||||
impl HttpMessage for ServiceRequest {
|
||||
type Stream = PayloadStream;
|
||||
|
||||
#[inline]
|
||||
/// Returns Request's headers.
|
||||
@@ -203,7 +218,7 @@ impl<P> HttpMessage for ServiceRequest<P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> fmt::Debug for ServiceRequest<P> {
|
||||
impl fmt::Debug for ServiceRequest {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
|
343
src/test.rs
343
src/test.rs
@@ -4,21 +4,26 @@ use std::rc::Rc;
|
||||
|
||||
use actix_http::cookie::Cookie;
|
||||
use actix_http::http::header::{Header, HeaderName, IntoHeaderValue};
|
||||
use actix_http::http::{HttpTryFrom, Method, StatusCode, Version};
|
||||
use actix_http::http::{HttpTryFrom, Method, StatusCode, Uri, Version};
|
||||
use actix_http::test::TestRequest as HttpTestRequest;
|
||||
use actix_http::{Extensions, PayloadStream, Request};
|
||||
use actix_http::{Extensions, Request};
|
||||
use actix_router::{Path, ResourceDef, Url};
|
||||
use actix_rt::Runtime;
|
||||
use actix_server_config::ServerConfig;
|
||||
use actix_service::{FnService, IntoNewService, NewService, Service};
|
||||
use bytes::Bytes;
|
||||
use futures::future::{lazy, Future};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{
|
||||
future::{lazy, ok, Future},
|
||||
stream::Stream,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json;
|
||||
|
||||
pub use actix_http::test::TestBuffer;
|
||||
|
||||
use crate::config::{AppConfig, AppConfigInner};
|
||||
use crate::data::RouteData;
|
||||
use crate::dev::{Body, Payload};
|
||||
use crate::data::{Data, RouteData};
|
||||
use crate::dev::{Body, MessageBody, Payload};
|
||||
use crate::request::HttpRequestPool;
|
||||
use crate::rmap::ResourceMap;
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
@@ -53,30 +58,25 @@ where
|
||||
/// This function panics on nested call.
|
||||
pub fn run_on<F, R>(f: F) -> R
|
||||
where
|
||||
F: Fn() -> R,
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
RT.with(move |rt| rt.borrow_mut().block_on(lazy(|| Ok::<_, ()>(f()))))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Create service that always responds with `HttpResponse::Ok()`
|
||||
pub fn ok_service() -> impl Service<
|
||||
Request = ServiceRequest<PayloadStream>,
|
||||
Response = ServiceResponse<Body>,
|
||||
Error = Error,
|
||||
> {
|
||||
pub fn ok_service(
|
||||
) -> impl Service<Request = ServiceRequest, Response = ServiceResponse<Body>, Error = Error>
|
||||
{
|
||||
default_service(StatusCode::OK)
|
||||
}
|
||||
|
||||
/// Create service that responds with response with specified status code
|
||||
pub fn default_service(
|
||||
status_code: StatusCode,
|
||||
) -> impl Service<
|
||||
Request = ServiceRequest<PayloadStream>,
|
||||
Response = ServiceResponse<Body>,
|
||||
Error = Error,
|
||||
> {
|
||||
FnService::new(move |req: ServiceRequest<PayloadStream>| {
|
||||
) -> impl Service<Request = ServiceRequest, Response = ServiceResponse<Body>, Error = Error>
|
||||
{
|
||||
FnService::new(move |req: ServiceRequest| {
|
||||
req.into_response(HttpResponse::build(status_code).finish())
|
||||
})
|
||||
}
|
||||
@@ -84,11 +84,12 @@ pub fn default_service(
|
||||
/// This method accepts application builder instance, and constructs
|
||||
/// service.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// ```rust
|
||||
/// use actix_service::Service;
|
||||
/// use actix_web::{test, web, App, HttpResponse, http::StatusCode};
|
||||
///
|
||||
/// fn main() {
|
||||
/// #[test]
|
||||
/// fn test_init_service() {
|
||||
/// let mut app = test::init_service(
|
||||
/// App::new()
|
||||
/// .service(web::resource("/test").to(|| HttpResponse::Ok()))
|
||||
@@ -116,16 +117,19 @@ where
|
||||
S::InitError: std::fmt::Debug,
|
||||
{
|
||||
let cfg = ServerConfig::new("127.0.0.1:8080".parse().unwrap());
|
||||
block_on(app.into_new_service().new_service(&cfg)).unwrap()
|
||||
let srv = app.into_new_service();
|
||||
let fut = run_on(move || srv.new_service(&cfg));
|
||||
block_on(fut).unwrap()
|
||||
}
|
||||
|
||||
/// Calls service and waits for response future completion.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// ```rust
|
||||
/// use actix_web::{test, App, HttpResponse, http::StatusCode};
|
||||
/// use actix_service::Service;
|
||||
///
|
||||
/// fn main() {
|
||||
/// #[test]
|
||||
/// fn test_response() {
|
||||
/// let mut app = test::init_service(
|
||||
/// App::new()
|
||||
/// .service(web::resource("/test").to(|| HttpResponse::Ok()))
|
||||
@@ -135,16 +139,155 @@ where
|
||||
/// let req = test::TestRequest::with_uri("/test").to_request();
|
||||
///
|
||||
/// // Call application
|
||||
/// let resp = test::call_success(&mut app, req);
|
||||
/// let resp = test::call_service(&mut app, req);
|
||||
/// assert_eq!(resp.status(), StatusCode::OK);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn call_success<S, R, B, E>(app: &mut S, req: R) -> S::Response
|
||||
pub fn call_service<S, R, B, E>(app: &mut S, req: R) -> S::Response
|
||||
where
|
||||
S: Service<Request = R, Response = ServiceResponse<B>, Error = E>,
|
||||
E: std::fmt::Debug,
|
||||
{
|
||||
block_on(app.call(req)).unwrap()
|
||||
block_on(run_on(move || app.call(req))).unwrap()
|
||||
}
|
||||
|
||||
/// Helper function that returns a response body of a TestRequest
|
||||
/// This function blocks the current thread until futures complete.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{test, web, App, HttpResponse, http::header};
|
||||
/// use bytes::Bytes;
|
||||
///
|
||||
/// #[test]
|
||||
/// fn test_index() {
|
||||
/// let mut app = test::init_service(
|
||||
/// App::new().service(
|
||||
/// web::resource("/index.html")
|
||||
/// .route(web::post().to(
|
||||
/// || HttpResponse::Ok().body("welcome!")))));
|
||||
///
|
||||
/// let req = test::TestRequest::post()
|
||||
/// .uri("/index.html")
|
||||
/// .header(header::CONTENT_TYPE, "application/json")
|
||||
/// .to_request();
|
||||
///
|
||||
/// let result = test::read_response(&mut app, req);
|
||||
/// assert_eq!(result, Bytes::from_static(b"welcome!"));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn read_response<S, B>(app: &mut S, req: Request) -> Bytes
|
||||
where
|
||||
S: Service<Request = Request, Response = ServiceResponse<B>, Error = Error>,
|
||||
B: MessageBody,
|
||||
{
|
||||
block_on(run_on(move || {
|
||||
app.call(req).and_then(|mut resp: ServiceResponse<B>| {
|
||||
resp.take_body()
|
||||
.fold(BytesMut::new(), move |mut body, chunk| {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok::<_, Error>(body)
|
||||
})
|
||||
.map(|body: BytesMut| body.freeze())
|
||||
})
|
||||
}))
|
||||
.unwrap_or_else(|_| panic!("read_response failed at block_on unwrap"))
|
||||
}
|
||||
|
||||
/// Helper function that returns a response body of a ServiceResponse.
|
||||
/// This function blocks the current thread until futures complete.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{test, web, App, HttpResponse, http::header};
|
||||
/// use bytes::Bytes;
|
||||
///
|
||||
/// #[test]
|
||||
/// fn test_index() {
|
||||
/// let mut app = test::init_service(
|
||||
/// App::new().service(
|
||||
/// web::resource("/index.html")
|
||||
/// .route(web::post().to(
|
||||
/// || HttpResponse::Ok().body("welcome!")))));
|
||||
///
|
||||
/// let req = test::TestRequest::post()
|
||||
/// .uri("/index.html")
|
||||
/// .header(header::CONTENT_TYPE, "application/json")
|
||||
/// .to_request();
|
||||
///
|
||||
/// let resp = call_service(&mut srv, req);
|
||||
/// let result = test::read_body(resp);
|
||||
/// assert_eq!(result, Bytes::from_static(b"welcome!"));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn read_body<B>(mut res: ServiceResponse<B>) -> Bytes
|
||||
where
|
||||
B: MessageBody,
|
||||
{
|
||||
block_on(run_on(move || {
|
||||
res.take_body()
|
||||
.fold(BytesMut::new(), move |mut body, chunk| {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok::<_, Error>(body)
|
||||
})
|
||||
.map(|body: BytesMut| body.freeze())
|
||||
}))
|
||||
.unwrap_or_else(|_| panic!("read_response failed at block_on unwrap"))
|
||||
}
|
||||
|
||||
/// Helper function that returns a deserialized response body of a TestRequest
|
||||
/// This function blocks the current thread until futures complete.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{App, test, web, HttpResponse, http::header};
|
||||
/// use serde::{Serialize, Deserialize};
|
||||
///
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
/// pub struct Person {
|
||||
/// id: String,
|
||||
/// name: String
|
||||
/// }
|
||||
///
|
||||
/// #[test]
|
||||
/// fn test_add_person() {
|
||||
/// let mut app = test::init_service(
|
||||
/// App::new().service(
|
||||
/// web::resource("/people")
|
||||
/// .route(web::post().to(|person: web::Json<Person>| {
|
||||
/// HttpResponse::Ok()
|
||||
/// .json(person.into_inner())})
|
||||
/// )));
|
||||
///
|
||||
/// let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
|
||||
///
|
||||
/// let req = test::TestRequest::post()
|
||||
/// .uri("/people")
|
||||
/// .header(header::CONTENT_TYPE, "application/json")
|
||||
/// .set_payload(payload)
|
||||
/// .to_request();
|
||||
///
|
||||
/// let result: Person = test::read_response_json(&mut app, req);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn read_response_json<S, B, T>(app: &mut S, req: Request) -> T
|
||||
where
|
||||
S: Service<Request = Request, Response = ServiceResponse<B>, Error = Error>,
|
||||
B: MessageBody,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
block_on(run_on(move || {
|
||||
app.call(req).and_then(|mut resp: ServiceResponse<B>| {
|
||||
resp.take_body()
|
||||
.fold(BytesMut::new(), move |mut body, chunk| {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok::<_, Error>(body)
|
||||
})
|
||||
.and_then(|body: BytesMut| {
|
||||
ok(serde_json::from_slice(&body).unwrap_or_else(|_| {
|
||||
panic!("read_response_json failed during deserialization")
|
||||
}))
|
||||
})
|
||||
})
|
||||
}))
|
||||
.unwrap_or_else(|_| panic!("read_response_json failed at block_on unwrap"))
|
||||
}
|
||||
|
||||
/// Test `Request` builder.
|
||||
@@ -156,7 +299,7 @@ where
|
||||
/// * `TestRequest::to_from` creates `ServiceFromRequest` instance, which is used for testing extractors.
|
||||
/// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// ```rust
|
||||
/// # use futures::IntoFuture;
|
||||
/// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage};
|
||||
/// use actix_web::http::{header, StatusCode};
|
||||
@@ -169,7 +312,8 @@ where
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// #[test]
|
||||
/// fn test_index() {
|
||||
/// let req = test::TestRequest::with_header("content-type", "text/plain")
|
||||
/// .to_http_request();
|
||||
///
|
||||
@@ -186,6 +330,7 @@ pub struct TestRequest {
|
||||
rmap: ResourceMap,
|
||||
config: AppConfigInner,
|
||||
route_data: Extensions,
|
||||
path: Path<Url>,
|
||||
}
|
||||
|
||||
impl Default for TestRequest {
|
||||
@@ -195,6 +340,7 @@ impl Default for TestRequest {
|
||||
rmap: ResourceMap::new(ResourceDef::new("")),
|
||||
config: AppConfigInner::default(),
|
||||
route_data: Extensions::new(),
|
||||
path: Path::new(Url::new(Uri::default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,6 +376,16 @@ impl TestRequest {
|
||||
TestRequest::default().method(Method::POST)
|
||||
}
|
||||
|
||||
/// Create TestRequest and set method to `Method::PUT`
|
||||
pub fn put() -> TestRequest {
|
||||
TestRequest::default().method(Method::PUT)
|
||||
}
|
||||
|
||||
/// Create TestRequest and set method to `Method::PATCH`
|
||||
pub fn patch() -> TestRequest {
|
||||
TestRequest::default().method(Method::PATCH)
|
||||
}
|
||||
|
||||
/// Set HTTP version of this request
|
||||
pub fn version(mut self, ver: Version) -> Self {
|
||||
self.req.version(ver);
|
||||
@@ -270,6 +426,12 @@ impl TestRequest {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set request path pattern parameter
|
||||
pub fn param(mut self, name: &'static str, value: &'static str) -> Self {
|
||||
self.path.add_static(name, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set request payload
|
||||
pub fn set_payload<B: Into<Bytes>>(mut self, data: B) -> Self {
|
||||
self.req.set_payload(data);
|
||||
@@ -279,7 +441,7 @@ impl TestRequest {
|
||||
/// Set application data. This is equivalent of `App::data()` method
|
||||
/// for testing purpose.
|
||||
pub fn app_data<T: 'static>(self, data: T) -> Self {
|
||||
self.config.extensions.borrow_mut().insert(data);
|
||||
self.config.extensions.borrow_mut().insert(Data::new(data));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -298,16 +460,17 @@ impl TestRequest {
|
||||
}
|
||||
|
||||
/// Complete request creation and generate `Request` instance
|
||||
pub fn to_request(mut self) -> Request<PayloadStream> {
|
||||
pub fn to_request(mut self) -> Request {
|
||||
self.req.finish()
|
||||
}
|
||||
|
||||
/// Complete request creation and generate `ServiceRequest` instance
|
||||
pub fn to_srv_request(mut self) -> ServiceRequest<PayloadStream> {
|
||||
pub fn to_srv_request(mut self) -> ServiceRequest {
|
||||
let (head, payload) = self.req.finish().into_parts();
|
||||
self.path.get_mut().update(&head.uri);
|
||||
|
||||
let req = HttpRequest::new(
|
||||
Path::new(Url::new(head.uri.clone())),
|
||||
self.path,
|
||||
head,
|
||||
Rc::new(self.rmap),
|
||||
AppConfig::new(self.config),
|
||||
@@ -325,9 +488,10 @@ impl TestRequest {
|
||||
/// Complete request creation and generate `HttpRequest` instance
|
||||
pub fn to_http_request(mut self) -> HttpRequest {
|
||||
let (head, _) = self.req.finish().into_parts();
|
||||
self.path.get_mut().update(&head.uri);
|
||||
|
||||
let mut req = HttpRequest::new(
|
||||
Path::new(Url::new(head.uri.clone())),
|
||||
self.path,
|
||||
head,
|
||||
Rc::new(self.rmap),
|
||||
AppConfig::new(self.config),
|
||||
@@ -340,9 +504,10 @@ impl TestRequest {
|
||||
/// Complete request creation and generate `HttpRequest` and `Payload` instances
|
||||
pub fn to_http_parts(mut self) -> (HttpRequest, Payload) {
|
||||
let (head, payload) = self.req.finish().into_parts();
|
||||
self.path.get_mut().update(&head.uri);
|
||||
|
||||
let mut req = HttpRequest::new(
|
||||
Path::new(Url::new(head.uri.clone())),
|
||||
self.path,
|
||||
head,
|
||||
Rc::new(self.rmap),
|
||||
AppConfig::new(self.config),
|
||||
@@ -351,21 +516,101 @@ impl TestRequest {
|
||||
req.set_route_data(Some(Rc::new(self.route_data)));
|
||||
(req, payload)
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the provided future, blocking the current thread until the future
|
||||
/// completes.
|
||||
///
|
||||
/// This function can be used to synchronously block the current thread
|
||||
/// until the provided `future` has resolved either successfully or with an
|
||||
/// error. The result of the future is then returned from this function
|
||||
/// call.
|
||||
///
|
||||
/// Note that this function is intended to be used only for testing purpose.
|
||||
/// This function panics on nested call.
|
||||
pub fn block_on<F>(f: F) -> Result<F::Item, F::Error>
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
block_on(f)
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::SystemTime;
|
||||
|
||||
use super::*;
|
||||
use crate::{http::header, web, App, HttpResponse};
|
||||
|
||||
#[test]
|
||||
fn test_basics() {
|
||||
let req = TestRequest::with_hdr(header::ContentType::json())
|
||||
.version(Version::HTTP_2)
|
||||
.set(header::Date(SystemTime::now().into()))
|
||||
.param("test", "123")
|
||||
.app_data(10u32)
|
||||
.to_http_request();
|
||||
assert!(req.headers().contains_key(header::CONTENT_TYPE));
|
||||
assert!(req.headers().contains_key(header::DATE));
|
||||
assert_eq!(&req.match_info()["test"], "123");
|
||||
assert_eq!(req.version(), Version::HTTP_2);
|
||||
let data = req.app_data::<u32>().unwrap();
|
||||
assert_eq!(*data, 10);
|
||||
assert_eq!(*data.get_ref(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_methods() {
|
||||
let mut app = init_service(
|
||||
App::new().service(
|
||||
web::resource("/index.html")
|
||||
.route(web::put().to(|| HttpResponse::Ok().body("put!")))
|
||||
.route(web::patch().to(|| HttpResponse::Ok().body("patch!"))),
|
||||
),
|
||||
);
|
||||
|
||||
let put_req = TestRequest::put()
|
||||
.uri("/index.html")
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.to_request();
|
||||
|
||||
let result = read_response(&mut app, put_req);
|
||||
assert_eq!(result, Bytes::from_static(b"put!"));
|
||||
|
||||
let patch_req = TestRequest::patch()
|
||||
.uri("/index.html")
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.to_request();
|
||||
|
||||
let result = read_response(&mut app, patch_req);
|
||||
assert_eq!(result, Bytes::from_static(b"patch!"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response() {
|
||||
let mut app = init_service(
|
||||
App::new().service(
|
||||
web::resource("/index.html")
|
||||
.route(web::post().to(|| HttpResponse::Ok().body("welcome!"))),
|
||||
),
|
||||
);
|
||||
|
||||
let req = TestRequest::post()
|
||||
.uri("/index.html")
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.to_request();
|
||||
|
||||
let result = read_response(&mut app, req);
|
||||
assert_eq!(result, Bytes::from_static(b"welcome!"));
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Person {
|
||||
id: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_json() {
|
||||
let mut app = init_service(App::new().service(web::resource("/people").route(
|
||||
web::post().to(|person: web::Json<Person>| {
|
||||
HttpResponse::Ok().json(person.into_inner())
|
||||
}),
|
||||
)));
|
||||
|
||||
let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
|
||||
|
||||
let req = TestRequest::post()
|
||||
.uri("/people")
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.set_payload(payload)
|
||||
.to_request();
|
||||
|
||||
let result: Person = read_response_json(&mut app, req);
|
||||
assert_eq!(&result.id, "12345");
|
||||
}
|
||||
}
|
||||
|
@@ -3,15 +3,15 @@
|
||||
use std::rc::Rc;
|
||||
use std::{fmt, ops};
|
||||
|
||||
use actix_http::error::{Error, PayloadError};
|
||||
use actix_http::{HttpMessage, Payload};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use actix_http::{Error, HttpMessage, Payload};
|
||||
use bytes::BytesMut;
|
||||
use encoding::all::UTF_8;
|
||||
use encoding::types::{DecoderTrap, Encoding};
|
||||
use encoding::EncodingRef;
|
||||
use futures::{Future, Poll, Stream};
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use crate::dev::Decompress;
|
||||
use crate::error::UrlencodedError;
|
||||
use crate::extract::FromRequest;
|
||||
use crate::http::header::CONTENT_LENGTH;
|
||||
@@ -69,16 +69,16 @@ impl<T> ops::DerefMut for Form<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P> FromRequest<P> for Form<T>
|
||||
impl<T> FromRequest for Form<T>
|
||||
where
|
||||
T: DeserializeOwned + 'static,
|
||||
P: Stream<Item = Bytes, Error = crate::error::PayloadError> + 'static,
|
||||
{
|
||||
type Config = FormConfig;
|
||||
type Error = Error;
|
||||
type Future = Box<Future<Item = Self, Error = Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
let req2 = req.clone();
|
||||
let (limit, err) = req
|
||||
.route_data::<FormConfig>()
|
||||
@@ -116,7 +116,7 @@ impl<T: fmt::Display> fmt::Display for Form<T> {
|
||||
///
|
||||
/// ```rust
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{web, App, Result};
|
||||
/// use actix_web::{web, App, FromRequest, Result};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct FormData {
|
||||
@@ -134,7 +134,9 @@ impl<T: fmt::Display> fmt::Display for Form<T> {
|
||||
/// web::resource("/index.html")
|
||||
/// .route(web::get()
|
||||
/// // change `Form` extractor configuration
|
||||
/// .data(web::FormConfig::default().limit(4097))
|
||||
/// .data(
|
||||
/// web::Form::<FormData>::configure(|cfg| cfg.limit(4097))
|
||||
/// )
|
||||
/// .to(index))
|
||||
/// );
|
||||
/// }
|
||||
@@ -182,8 +184,8 @@ impl Default for FormConfig {
|
||||
/// * content type is not `application/x-www-form-urlencoded`
|
||||
/// * content-length is greater than 32k
|
||||
///
|
||||
pub struct UrlEncoded<P, U> {
|
||||
stream: Payload<P>,
|
||||
pub struct UrlEncoded<U> {
|
||||
stream: Option<Decompress<Payload>>,
|
||||
limit: usize,
|
||||
length: Option<usize>,
|
||||
encoding: EncodingRef,
|
||||
@@ -191,12 +193,9 @@ pub struct UrlEncoded<P, U> {
|
||||
fut: Option<Box<Future<Item = U, Error = UrlencodedError>>>,
|
||||
}
|
||||
|
||||
impl<P, U> UrlEncoded<P, U>
|
||||
where
|
||||
P: Stream<Item = Bytes, Error = PayloadError>,
|
||||
{
|
||||
impl<U> UrlEncoded<U> {
|
||||
/// Create a new future to URL encode a request
|
||||
pub fn new(req: &HttpRequest, payload: &mut Payload<P>) -> UrlEncoded<P, U> {
|
||||
pub fn new(req: &HttpRequest, payload: &mut Payload) -> UrlEncoded<U> {
|
||||
// check content type
|
||||
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
|
||||
return Self::err(UrlencodedError::ContentType);
|
||||
@@ -219,9 +218,10 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
let payload = Decompress::from_headers(payload.take(), req.headers());
|
||||
UrlEncoded {
|
||||
encoding,
|
||||
stream: payload.take(),
|
||||
stream: Some(payload),
|
||||
limit: 32_768,
|
||||
length: len,
|
||||
fut: None,
|
||||
@@ -231,7 +231,7 @@ where
|
||||
|
||||
fn err(e: UrlencodedError) -> Self {
|
||||
UrlEncoded {
|
||||
stream: Payload::None,
|
||||
stream: None,
|
||||
limit: 32_768,
|
||||
fut: None,
|
||||
err: Some(e),
|
||||
@@ -247,9 +247,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, U> Future for UrlEncoded<P, U>
|
||||
impl<U> Future for UrlEncoded<U>
|
||||
where
|
||||
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
U: DeserializeOwned + 'static,
|
||||
{
|
||||
type Item = U;
|
||||
@@ -274,7 +273,10 @@ where
|
||||
|
||||
// future
|
||||
let encoding = self.encoding;
|
||||
let fut = std::mem::replace(&mut self.stream, Payload::None)
|
||||
let fut = self
|
||||
.stream
|
||||
.take()
|
||||
.unwrap()
|
||||
.from_err()
|
||||
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
@@ -329,10 +331,6 @@ mod tests {
|
||||
|
||||
fn eq(err: UrlencodedError, other: UrlencodedError) -> bool {
|
||||
match err {
|
||||
UrlencodedError::Chunked => match other {
|
||||
UrlencodedError::Chunked => true,
|
||||
_ => false,
|
||||
},
|
||||
UrlencodedError::Overflow => match other {
|
||||
UrlencodedError::Overflow => true,
|
||||
_ => false,
|
||||
@@ -355,20 +353,20 @@ mod tests {
|
||||
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||
.header(CONTENT_LENGTH, "xxxx")
|
||||
.to_http_parts();
|
||||
let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl));
|
||||
let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl));
|
||||
assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength));
|
||||
|
||||
let (req, mut pl) =
|
||||
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||
.header(CONTENT_LENGTH, "1000000")
|
||||
.to_http_parts();
|
||||
let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl));
|
||||
let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl));
|
||||
assert!(eq(info.err().unwrap(), UrlencodedError::Overflow));
|
||||
|
||||
let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain")
|
||||
.header(CONTENT_LENGTH, "10")
|
||||
.to_http_parts();
|
||||
let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl));
|
||||
let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl));
|
||||
assert!(eq(info.err().unwrap(), UrlencodedError::ContentType));
|
||||
}
|
||||
|
||||
@@ -380,7 +378,7 @@ mod tests {
|
||||
.set_payload(Bytes::from_static(b"hello=world"))
|
||||
.to_http_parts();
|
||||
|
||||
let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)).unwrap();
|
||||
let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl)).unwrap();
|
||||
assert_eq!(
|
||||
info,
|
||||
Info {
|
||||
@@ -396,7 +394,7 @@ mod tests {
|
||||
.set_payload(Bytes::from_static(b"hello=world"))
|
||||
.to_http_parts();
|
||||
|
||||
let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)).unwrap();
|
||||
let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl)).unwrap();
|
||||
assert_eq!(
|
||||
info,
|
||||
Info {
|
||||
|
@@ -3,7 +3,7 @@
|
||||
use std::rc::Rc;
|
||||
use std::{fmt, ops};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use bytes::BytesMut;
|
||||
use futures::{Future, Poll, Stream};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
@@ -12,7 +12,8 @@ use serde_json;
|
||||
use actix_http::http::{header::CONTENT_LENGTH, StatusCode};
|
||||
use actix_http::{HttpMessage, Payload, Response};
|
||||
|
||||
use crate::error::{Error, JsonPayloadError, PayloadError};
|
||||
use crate::dev::Decompress;
|
||||
use crate::error::{Error, JsonPayloadError};
|
||||
use crate::extract::FromRequest;
|
||||
use crate::request::HttpRequest;
|
||||
use crate::responder::Responder;
|
||||
@@ -163,16 +164,16 @@ impl<T: Serialize> Responder for Json<T> {
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
impl<T, P> FromRequest<P> for Json<T>
|
||||
impl<T> FromRequest for Json<T>
|
||||
where
|
||||
T: DeserializeOwned + 'static,
|
||||
P: Stream<Item = Bytes, Error = crate::error::PayloadError> + 'static,
|
||||
{
|
||||
type Config = JsonConfig;
|
||||
type Error = Error;
|
||||
type Future = Box<Future<Item = Self, Error = Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
let req2 = req.clone();
|
||||
let (limit, err) = req
|
||||
.route_data::<JsonConfig>()
|
||||
@@ -205,7 +206,7 @@ where
|
||||
///
|
||||
/// ```rust
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{error, web, App, HttpResponse};
|
||||
/// use actix_web::{error, web, App, FromRequest, HttpResponse};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
@@ -222,10 +223,12 @@ where
|
||||
/// web::resource("/index.html").route(
|
||||
/// web::post().data(
|
||||
/// // change json extractor configuration
|
||||
/// web::JsonConfig::default().limit(4096)
|
||||
/// web::Json::<Info>::configure(|cfg| {
|
||||
/// cfg.limit(4096)
|
||||
/// .error_handler(|err, req| { // <- create custom error response
|
||||
/// error::InternalError::from_response(
|
||||
/// err, HttpResponse::Conflict().finish()).into()
|
||||
/// })
|
||||
/// }))
|
||||
/// .to(index))
|
||||
/// );
|
||||
@@ -270,21 +273,20 @@ impl Default for JsonConfig {
|
||||
///
|
||||
/// * content type is not `application/json`
|
||||
/// * content length is greater than 256k
|
||||
pub struct JsonBody<P, U> {
|
||||
pub struct JsonBody<U> {
|
||||
limit: usize,
|
||||
length: Option<usize>,
|
||||
stream: Payload<P>,
|
||||
stream: Option<Decompress<Payload>>,
|
||||
err: Option<JsonPayloadError>,
|
||||
fut: Option<Box<Future<Item = U, Error = JsonPayloadError>>>,
|
||||
}
|
||||
|
||||
impl<P, U> JsonBody<P, U>
|
||||
impl<U> JsonBody<U>
|
||||
where
|
||||
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
U: DeserializeOwned + 'static,
|
||||
{
|
||||
/// Create `JsonBody` for request.
|
||||
pub fn new(req: &HttpRequest, payload: &mut Payload<P>) -> Self {
|
||||
pub fn new(req: &HttpRequest, payload: &mut Payload) -> Self {
|
||||
// check content-type
|
||||
let json = if let Ok(Some(mime)) = req.mime_type() {
|
||||
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
|
||||
@@ -295,7 +297,7 @@ where
|
||||
return JsonBody {
|
||||
limit: 262_144,
|
||||
length: None,
|
||||
stream: Payload::None,
|
||||
stream: None,
|
||||
fut: None,
|
||||
err: Some(JsonPayloadError::ContentType),
|
||||
};
|
||||
@@ -309,11 +311,12 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
let payload = Decompress::from_headers(payload.take(), req.headers());
|
||||
|
||||
JsonBody {
|
||||
limit: 262_144,
|
||||
length: len,
|
||||
stream: payload.take(),
|
||||
stream: Some(payload),
|
||||
fut: None,
|
||||
err: None,
|
||||
}
|
||||
@@ -326,9 +329,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, U> Future for JsonBody<P, U>
|
||||
impl<U> Future for JsonBody<U>
|
||||
where
|
||||
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
U: DeserializeOwned + 'static,
|
||||
{
|
||||
type Item = U;
|
||||
@@ -350,7 +352,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
let fut = std::mem::replace(&mut self.stream, Payload::None)
|
||||
let fut = self
|
||||
.stream
|
||||
.take()
|
||||
.unwrap()
|
||||
.from_err()
|
||||
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
@@ -508,7 +513,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_json_body() {
|
||||
let (req, mut pl) = TestRequest::default().to_http_parts();
|
||||
let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl));
|
||||
let json = block_on(JsonBody::<MyObject>::new(&req, &mut pl));
|
||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
|
||||
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
@@ -517,7 +522,7 @@ mod tests {
|
||||
header::HeaderValue::from_static("application/text"),
|
||||
)
|
||||
.to_http_parts();
|
||||
let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl));
|
||||
let json = block_on(JsonBody::<MyObject>::new(&req, &mut pl));
|
||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
|
||||
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
@@ -531,7 +536,7 @@ mod tests {
|
||||
)
|
||||
.to_http_parts();
|
||||
|
||||
let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl).limit(100));
|
||||
let json = block_on(JsonBody::<MyObject>::new(&req, &mut pl).limit(100));
|
||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow));
|
||||
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
@@ -546,7 +551,7 @@ mod tests {
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.to_http_parts();
|
||||
|
||||
let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl));
|
||||
let json = block_on(JsonBody::<MyObject>::new(&req, &mut pl));
|
||||
assert_eq!(
|
||||
json.ok().unwrap(),
|
||||
MyObject {
|
||||
|
@@ -152,15 +152,16 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
impl<T, P> FromRequest<P> for Path<T>
|
||||
impl<T> FromRequest for Path<T>
|
||||
where
|
||||
T: de::DeserializeOwned,
|
||||
{
|
||||
type Config = ();
|
||||
type Error = Error;
|
||||
type Future = Result<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
|
||||
.map(|inner| Path { inner })
|
||||
.map_err(ErrorNotFound)
|
||||
@@ -170,10 +171,25 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_router::ResourceDef;
|
||||
use derive_more::Display;
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
use super::*;
|
||||
use crate::test::{block_on, TestRequest};
|
||||
|
||||
#[derive(Deserialize, Debug, Display)]
|
||||
#[display(fmt = "MyStruct({}, {})", key, value)]
|
||||
struct MyStruct {
|
||||
key: String,
|
||||
value: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Test2 {
|
||||
key: String,
|
||||
value: u32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_path_single() {
|
||||
let resource = ResourceDef::new("/{value}/");
|
||||
@@ -183,6 +199,7 @@ mod tests {
|
||||
|
||||
let (req, mut pl) = req.into_parts();
|
||||
assert_eq!(*Path::<i8>::from_request(&req, &mut pl).unwrap(), 32);
|
||||
assert!(Path::<MyStruct>::from_request(&req, &mut pl).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -212,4 +229,46 @@ mod tests {
|
||||
let () = <()>::from_request(&req, &mut pl).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_extract() {
|
||||
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
|
||||
|
||||
let resource = ResourceDef::new("/{key}/{value}/");
|
||||
resource.match_path(req.match_info_mut());
|
||||
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let mut s = Path::<MyStruct>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.key, "name");
|
||||
assert_eq!(s.value, "user1");
|
||||
s.value = "user2".to_string();
|
||||
assert_eq!(s.value, "user2");
|
||||
assert_eq!(
|
||||
format!("{}, {:?}", s, s),
|
||||
"MyStruct(name, user2), MyStruct { key: \"name\", value: \"user2\" }"
|
||||
);
|
||||
let s = s.into_inner();
|
||||
assert_eq!(s.value, "user2");
|
||||
|
||||
let s = Path::<(String, String)>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, "user1");
|
||||
|
||||
let mut req = TestRequest::with_uri("/name/32/").to_srv_request();
|
||||
let resource = ResourceDef::new("/{key}/{value}/");
|
||||
resource.match_path(req.match_info_mut());
|
||||
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let s = Path::<Test2>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.as_ref().key, "name");
|
||||
assert_eq!(s.value, 32);
|
||||
|
||||
let s = Path::<(String, u8)>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, 32);
|
||||
|
||||
let res = Path::<Vec<String>>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(res[0], "name".to_owned());
|
||||
assert_eq!(res[1], "32".to_owned());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -44,7 +44,7 @@ use crate::request::HttpRequest;
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Payload(crate::dev::Payload<Box<Stream<Item = Bytes, Error = PayloadError>>>);
|
||||
pub struct Payload(crate::dev::Payload);
|
||||
|
||||
impl Stream for Payload {
|
||||
type Item = Bytes;
|
||||
@@ -85,26 +85,14 @@ impl Stream for Payload {
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
impl<P> FromRequest<P> for Payload
|
||||
where
|
||||
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
{
|
||||
impl FromRequest for Payload {
|
||||
type Config = PayloadConfig;
|
||||
type Error = Error;
|
||||
type Future = Result<Payload, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(_: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future {
|
||||
let pl = match payload.take() {
|
||||
crate::dev::Payload::Stream(s) => {
|
||||
let pl: Box<dyn Stream<Item = Bytes, Error = PayloadError>> =
|
||||
Box::new(s);
|
||||
crate::dev::Payload::Stream(pl)
|
||||
}
|
||||
crate::dev::Payload::None => crate::dev::Payload::None,
|
||||
crate::dev::Payload::H1(pl) => crate::dev::Payload::H1(pl),
|
||||
crate::dev::Payload::H2(pl) => crate::dev::Payload::H2(pl),
|
||||
};
|
||||
Ok(Payload(pl))
|
||||
fn from_request(_: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||
Ok(Payload(payload.take()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,16 +121,14 @@ where
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
impl<P> FromRequest<P> for Bytes
|
||||
where
|
||||
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
{
|
||||
impl FromRequest for Bytes {
|
||||
type Config = PayloadConfig;
|
||||
type Error = Error;
|
||||
type Future =
|
||||
Either<Box<Future<Item = Bytes, Error = Error>>, FutureResult<Bytes, Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||
let mut tmp;
|
||||
let cfg = if let Some(cfg) = req.route_data::<PayloadConfig>() {
|
||||
cfg
|
||||
@@ -172,7 +158,7 @@ where
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, App};
|
||||
/// use actix_web::{web, App, FromRequest};
|
||||
///
|
||||
/// /// extract text data from request
|
||||
/// fn index(text: String) -> String {
|
||||
@@ -183,21 +169,21 @@ where
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html").route(
|
||||
/// web::get()
|
||||
/// .data(web::PayloadConfig::new(4096)) // <- limit size of the payload
|
||||
/// .data(String::configure(|cfg| { // <- limit size of the payload
|
||||
/// cfg.limit(4096)
|
||||
/// }))
|
||||
/// .to(index)) // <- register handler with extractor params
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
impl<P> FromRequest<P> for String
|
||||
where
|
||||
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
{
|
||||
impl FromRequest for String {
|
||||
type Config = PayloadConfig;
|
||||
type Error = Error;
|
||||
type Future =
|
||||
Either<Box<Future<Item = String, Error = Error>>, FutureResult<String, Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||
let mut tmp;
|
||||
let cfg = if let Some(cfg) = req.route_data::<PayloadConfig>() {
|
||||
cfg
|
||||
@@ -247,7 +233,9 @@ pub struct PayloadConfig {
|
||||
impl PayloadConfig {
|
||||
/// Create `PayloadConfig` instance and set max size of payload.
|
||||
pub fn new(limit: usize) -> Self {
|
||||
Self::default().limit(limit)
|
||||
let mut cfg = Self::default();
|
||||
cfg.limit = limit;
|
||||
cfg
|
||||
}
|
||||
|
||||
/// Change max size of payload. By default max size is 256Kb
|
||||
@@ -300,20 +288,17 @@ impl Default for PayloadConfig {
|
||||
/// By default only 256Kb payload reads to a memory, then
|
||||
/// `PayloadError::Overflow` get returned. Use `MessageBody::limit()`
|
||||
/// method to change upper limit.
|
||||
pub struct HttpMessageBody<P> {
|
||||
pub struct HttpMessageBody {
|
||||
limit: usize,
|
||||
length: Option<usize>,
|
||||
stream: dev::Payload<P>,
|
||||
stream: Option<dev::Decompress<dev::Payload>>,
|
||||
err: Option<PayloadError>,
|
||||
fut: Option<Box<Future<Item = Bytes, Error = PayloadError>>>,
|
||||
}
|
||||
|
||||
impl<P> HttpMessageBody<P>
|
||||
where
|
||||
P: Stream<Item = Bytes, Error = PayloadError>,
|
||||
{
|
||||
impl HttpMessageBody {
|
||||
/// Create `MessageBody` for request.
|
||||
pub fn new(req: &HttpRequest, payload: &mut dev::Payload<P>) -> HttpMessageBody<P> {
|
||||
pub fn new(req: &HttpRequest, payload: &mut dev::Payload) -> HttpMessageBody {
|
||||
let mut len = None;
|
||||
if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) {
|
||||
if let Ok(s) = l.to_str() {
|
||||
@@ -328,7 +313,7 @@ where
|
||||
}
|
||||
|
||||
HttpMessageBody {
|
||||
stream: payload.take(),
|
||||
stream: Some(dev::Decompress::from_headers(payload.take(), req.headers())),
|
||||
limit: 262_144,
|
||||
length: len,
|
||||
fut: None,
|
||||
@@ -344,7 +329,7 @@ where
|
||||
|
||||
fn err(e: PayloadError) -> Self {
|
||||
HttpMessageBody {
|
||||
stream: dev::Payload::None,
|
||||
stream: None,
|
||||
limit: 262_144,
|
||||
fut: None,
|
||||
err: Some(e),
|
||||
@@ -353,10 +338,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> Future for HttpMessageBody<P>
|
||||
where
|
||||
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
{
|
||||
impl Future for HttpMessageBody {
|
||||
type Item = Bytes;
|
||||
type Error = PayloadError;
|
||||
|
||||
@@ -378,7 +360,9 @@ where
|
||||
// future
|
||||
let limit = self.limit;
|
||||
self.fut = Some(Box::new(
|
||||
std::mem::replace(&mut self.stream, actix_http::Payload::None)
|
||||
self.stream
|
||||
.take()
|
||||
.unwrap()
|
||||
.from_err()
|
||||
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
|
@@ -111,15 +111,16 @@ impl<T: fmt::Display> fmt::Display for Query<T> {
|
||||
/// .route(web::get().to(index))); // <- use `Query` extractor
|
||||
/// }
|
||||
/// ```
|
||||
impl<T, P> FromRequest<P> for Query<T>
|
||||
impl<T> FromRequest for Query<T>
|
||||
where
|
||||
T: de::DeserializeOwned,
|
||||
{
|
||||
type Config = ();
|
||||
type Error = Error;
|
||||
type Future = Result<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
serde_urlencoded::from_str::<T>(req.query_string())
|
||||
.map(|val| Ok(Query(val)))
|
||||
.unwrap_or_else(|e| {
|
||||
@@ -132,3 +133,35 @@ where
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use derive_more::Display;
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
use super::*;
|
||||
use crate::test::TestRequest;
|
||||
|
||||
#[derive(Deserialize, Debug, Display)]
|
||||
struct Id {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_extract() {
|
||||
let req = TestRequest::with_uri("/name/user1/").to_srv_request();
|
||||
let (req, mut pl) = req.into_parts();
|
||||
assert!(Query::<Id>::from_request(&req, &mut pl).is_err());
|
||||
|
||||
let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
|
||||
let (req, mut pl) = req.into_parts();
|
||||
|
||||
let mut s = Query::<Id>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.id, "test");
|
||||
assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }");
|
||||
|
||||
s.id = "test1".to_string();
|
||||
let s = s.into_inner();
|
||||
assert_eq!(s.id, "test1");
|
||||
}
|
||||
}
|
||||
|
46
src/web.rs
46
src/web.rs
@@ -1,5 +1,5 @@
|
||||
//! Essentials helper functions and types for application registration.
|
||||
use actix_http::{http::Method, Response};
|
||||
use actix_http::http::Method;
|
||||
use futures::{Future, IntoFuture};
|
||||
|
||||
pub use actix_http::Response as HttpResponse;
|
||||
@@ -13,7 +13,7 @@ use crate::responder::Responder;
|
||||
use crate::route::Route;
|
||||
use crate::scope::Scope;
|
||||
|
||||
pub use crate::config::RouterConfig;
|
||||
pub use crate::config::ServiceConfig;
|
||||
pub use crate::data::{Data, RouteData};
|
||||
pub use crate::request::HttpRequest;
|
||||
pub use crate::types::*;
|
||||
@@ -50,7 +50,7 @@ pub use crate::types::*;
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub fn resource<P: 'static>(path: &str) -> Resource<P> {
|
||||
pub fn resource(path: &str) -> Resource {
|
||||
Resource::new(path)
|
||||
}
|
||||
|
||||
@@ -77,12 +77,12 @@ pub fn resource<P: 'static>(path: &str) -> Resource<P> {
|
||||
/// * /{project_id}/path2
|
||||
/// * /{project_id}/path3
|
||||
///
|
||||
pub fn scope<P: 'static>(path: &str) -> Scope<P> {
|
||||
pub fn scope(path: &str) -> Scope {
|
||||
Scope::new(path)
|
||||
}
|
||||
|
||||
/// Create *route* without configuration.
|
||||
pub fn route<P: 'static>() -> Route<P> {
|
||||
pub fn route() -> Route {
|
||||
Route::new()
|
||||
}
|
||||
|
||||
@@ -102,8 +102,8 @@ pub fn route<P: 'static>() -> Route<P> {
|
||||
/// In the above example, one `GET` route get added:
|
||||
/// * /{project_id}
|
||||
///
|
||||
pub fn get<P: 'static>() -> Route<P> {
|
||||
Route::new().method(Method::GET)
|
||||
pub fn get() -> Route {
|
||||
method(Method::GET)
|
||||
}
|
||||
|
||||
/// Create *route* with `POST` method guard.
|
||||
@@ -122,8 +122,8 @@ pub fn get<P: 'static>() -> Route<P> {
|
||||
/// In the above example, one `POST` route get added:
|
||||
/// * /{project_id}
|
||||
///
|
||||
pub fn post<P: 'static>() -> Route<P> {
|
||||
Route::new().method(Method::POST)
|
||||
pub fn post() -> Route {
|
||||
method(Method::POST)
|
||||
}
|
||||
|
||||
/// Create *route* with `PUT` method guard.
|
||||
@@ -142,8 +142,8 @@ pub fn post<P: 'static>() -> Route<P> {
|
||||
/// In the above example, one `PUT` route get added:
|
||||
/// * /{project_id}
|
||||
///
|
||||
pub fn put<P: 'static>() -> Route<P> {
|
||||
Route::new().method(Method::PUT)
|
||||
pub fn put() -> Route {
|
||||
method(Method::PUT)
|
||||
}
|
||||
|
||||
/// Create *route* with `PATCH` method guard.
|
||||
@@ -162,8 +162,8 @@ pub fn put<P: 'static>() -> Route<P> {
|
||||
/// In the above example, one `PATCH` route get added:
|
||||
/// * /{project_id}
|
||||
///
|
||||
pub fn patch<P: 'static>() -> Route<P> {
|
||||
Route::new().method(Method::PATCH)
|
||||
pub fn patch() -> Route {
|
||||
method(Method::PATCH)
|
||||
}
|
||||
|
||||
/// Create *route* with `DELETE` method guard.
|
||||
@@ -182,8 +182,8 @@ pub fn patch<P: 'static>() -> Route<P> {
|
||||
/// In the above example, one `DELETE` route get added:
|
||||
/// * /{project_id}
|
||||
///
|
||||
pub fn delete<P: 'static>() -> Route<P> {
|
||||
Route::new().method(Method::DELETE)
|
||||
pub fn delete() -> Route {
|
||||
method(Method::DELETE)
|
||||
}
|
||||
|
||||
/// Create *route* with `HEAD` method guard.
|
||||
@@ -202,8 +202,8 @@ pub fn delete<P: 'static>() -> Route<P> {
|
||||
/// In the above example, one `HEAD` route get added:
|
||||
/// * /{project_id}
|
||||
///
|
||||
pub fn head<P: 'static>() -> Route<P> {
|
||||
Route::new().method(Method::HEAD)
|
||||
pub fn head() -> Route {
|
||||
method(Method::HEAD)
|
||||
}
|
||||
|
||||
/// Create *route* and add method guard.
|
||||
@@ -222,7 +222,7 @@ pub fn head<P: 'static>() -> Route<P> {
|
||||
/// In the above example, one `GET` route get added:
|
||||
/// * /{project_id}
|
||||
///
|
||||
pub fn method<P: 'static>(method: Method) -> Route<P> {
|
||||
pub fn method(method: Method) -> Route {
|
||||
Route::new().method(method)
|
||||
}
|
||||
|
||||
@@ -240,10 +240,10 @@ pub fn method<P: 'static>(method: Method) -> Route<P> {
|
||||
/// web::to(index))
|
||||
/// );
|
||||
/// ```
|
||||
pub fn to<F, I, R, P: 'static>(handler: F) -> Route<P>
|
||||
pub fn to<F, I, R>(handler: F) -> Route
|
||||
where
|
||||
F: Factory<I, R> + 'static,
|
||||
I: FromRequest<P> + 'static,
|
||||
I: FromRequest + 'static,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
Route::new().to(handler)
|
||||
@@ -263,12 +263,12 @@ where
|
||||
/// web::to_async(index))
|
||||
/// );
|
||||
/// ```
|
||||
pub fn to_async<F, I, R, P: 'static>(handler: F) -> Route<P>
|
||||
pub fn to_async<F, I, R>(handler: F) -> Route
|
||||
where
|
||||
F: AsyncFactory<I, R>,
|
||||
I: FromRequest<P> + 'static,
|
||||
I: FromRequest + 'static,
|
||||
R: IntoFuture + 'static,
|
||||
R::Item: Into<Response>,
|
||||
R::Item: Responder,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
Route::new().to_async(handler)
|
||||
|
@@ -1,5 +1,10 @@
|
||||
# Changes
|
||||
|
||||
## [0.1.0] - 2019-04-16
|
||||
|
||||
* No changes
|
||||
|
||||
|
||||
## [0.1.0-alpha.3] - 2019-04-02
|
||||
|
||||
* Request functions accept path #743
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-http-test"
|
||||
version = "0.1.0-alpha.3"
|
||||
version = "0.1.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix http test server"
|
||||
readme = "README.md"
|
||||
@@ -33,9 +33,9 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"]
|
||||
actix-codec = "0.1.2"
|
||||
actix-rt = "0.2.2"
|
||||
actix-service = "0.3.6"
|
||||
actix-server = "0.4.1"
|
||||
actix-server = "0.4.3"
|
||||
actix-utils = "0.3.5"
|
||||
awc = "0.1.0-alpha.5"
|
||||
awc = "0.1.0"
|
||||
|
||||
base64 = "0.10"
|
||||
bytes = "0.4"
|
||||
@@ -56,4 +56,4 @@ openssl = { version="0.10", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-web = "1.0.0-alpha.5"
|
||||
actix-http = "0.1.0-alpha.5"
|
||||
actix-http = "0.1.0"
|
||||
|
@@ -16,12 +16,8 @@ use flate2::Compression;
|
||||
use futures::stream::once;
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
|
||||
use actix_web::{http, test, web, App, HttpResponse, HttpServer};
|
||||
|
||||
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||
use actix_web::middleware::encoding;
|
||||
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||
use actix_web::middleware::encoding::BodyEncoding;
|
||||
use actix_web::middleware::{BodyEncoding, Compress};
|
||||
use actix_web::{dev, http, test, web, App, HttpResponse, HttpServer};
|
||||
|
||||
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
|
||||
Hello World Hello World Hello World Hello World Hello World \
|
||||
@@ -68,7 +64,7 @@ fn test_body_gzip() {
|
||||
let mut srv = TestServer::new(|| {
|
||||
h1::H1Service::new(
|
||||
App::new()
|
||||
.wrap(encoding::Compress::new(ContentEncoding::Gzip))
|
||||
.wrap(Compress::new(ContentEncoding::Gzip))
|
||||
.service(web::resource("/").route(web::to(|| Response::Ok().body(STR)))),
|
||||
)
|
||||
});
|
||||
@@ -93,19 +89,50 @@ fn test_body_gzip() {
|
||||
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||
#[test]
|
||||
fn test_body_gzip2() {
|
||||
let mut srv = TestServer::new(|| {
|
||||
h1::H1Service::new(
|
||||
App::new()
|
||||
.wrap(Compress::new(ContentEncoding::Gzip))
|
||||
.service(web::resource("/").route(web::to(|| {
|
||||
Response::Ok().body(STR).into_body::<dev::Body>()
|
||||
}))),
|
||||
)
|
||||
});
|
||||
|
||||
let mut response = srv
|
||||
.block_on(
|
||||
srv.get("/")
|
||||
.no_decompress()
|
||||
.header(ACCEPT_ENCODING, "gzip")
|
||||
.send(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
// read response
|
||||
let bytes = srv.block_on(response.body()).unwrap();
|
||||
|
||||
// decode
|
||||
let mut e = GzDecoder::new(&bytes[..]);
|
||||
let mut dec = Vec::new();
|
||||
e.read_to_end(&mut dec).unwrap();
|
||||
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||
#[test]
|
||||
fn test_body_encoding_override() {
|
||||
let mut srv = TestServer::new(|| {
|
||||
h1::H1Service::new(
|
||||
App::new()
|
||||
.wrap(encoding::Compress::new(ContentEncoding::Gzip))
|
||||
.wrap(Compress::new(ContentEncoding::Gzip))
|
||||
.service(web::resource("/").route(web::to(|| {
|
||||
use actix_web::middleware::encoding::BodyEncoding;
|
||||
Response::Ok().encoding(ContentEncoding::Deflate).body(STR)
|
||||
})))
|
||||
.service(web::resource("/raw").route(web::to(|| {
|
||||
use actix_web::middleware::encoding::BodyEncoding;
|
||||
let body = actix_web::dev::Body::Bytes(STR.into());
|
||||
let mut response =
|
||||
Response::with_body(actix_web::http::StatusCode::OK, body);
|
||||
@@ -168,7 +195,7 @@ fn test_body_gzip_large() {
|
||||
let data = srv_data.clone();
|
||||
h1::H1Service::new(
|
||||
App::new()
|
||||
.wrap(encoding::Compress::new(ContentEncoding::Gzip))
|
||||
.wrap(Compress::new(ContentEncoding::Gzip))
|
||||
.service(
|
||||
web::resource("/")
|
||||
.route(web::to(move || Response::Ok().body(data.clone()))),
|
||||
@@ -209,7 +236,7 @@ fn test_body_gzip_large_random() {
|
||||
let data = srv_data.clone();
|
||||
h1::H1Service::new(
|
||||
App::new()
|
||||
.wrap(encoding::Compress::new(ContentEncoding::Gzip))
|
||||
.wrap(Compress::new(ContentEncoding::Gzip))
|
||||
.service(
|
||||
web::resource("/")
|
||||
.route(web::to(move || Response::Ok().body(data.clone()))),
|
||||
@@ -244,7 +271,7 @@ fn test_body_chunked_implicit() {
|
||||
let mut srv = TestServer::new(move || {
|
||||
h1::H1Service::new(
|
||||
App::new()
|
||||
.wrap(encoding::Compress::new(ContentEncoding::Gzip))
|
||||
.wrap(Compress::new(ContentEncoding::Gzip))
|
||||
.service(web::resource("/").route(web::get().to(move || {
|
||||
Response::Ok().streaming(once(Ok::<_, Error>(Bytes::from_static(
|
||||
STR.as_ref(),
|
||||
@@ -281,15 +308,12 @@ fn test_body_chunked_implicit() {
|
||||
#[cfg(feature = "brotli")]
|
||||
fn test_body_br_streaming() {
|
||||
let mut srv = TestServer::new(move || {
|
||||
h1::H1Service::new(
|
||||
App::new()
|
||||
.wrap(encoding::Compress::new(ContentEncoding::Br))
|
||||
.service(web::resource("/").route(web::to(move || {
|
||||
Response::Ok().streaming(once(Ok::<_, Error>(Bytes::from_static(
|
||||
STR.as_ref(),
|
||||
))))
|
||||
}))),
|
||||
)
|
||||
h1::H1Service::new(App::new().wrap(Compress::new(ContentEncoding::Br)).service(
|
||||
web::resource("/").route(web::to(move || {
|
||||
Response::Ok()
|
||||
.streaming(once(Ok::<_, Error>(Bytes::from_static(STR.as_ref()))))
|
||||
})),
|
||||
))
|
||||
});
|
||||
|
||||
let mut response = srv
|
||||
@@ -361,7 +385,7 @@ fn test_body_deflate() {
|
||||
let mut srv = TestServer::new(move || {
|
||||
h1::H1Service::new(
|
||||
App::new()
|
||||
.wrap(encoding::Compress::new(ContentEncoding::Deflate))
|
||||
.wrap(Compress::new(ContentEncoding::Deflate))
|
||||
.service(
|
||||
web::resource("/").route(web::to(move || Response::Ok().body(STR))),
|
||||
),
|
||||
@@ -392,13 +416,9 @@ fn test_body_deflate() {
|
||||
#[cfg(any(feature = "brotli"))]
|
||||
fn test_body_brotli() {
|
||||
let mut srv = TestServer::new(move || {
|
||||
h1::H1Service::new(
|
||||
App::new()
|
||||
.wrap(encoding::Compress::new(ContentEncoding::Br))
|
||||
.service(
|
||||
h1::H1Service::new(App::new().wrap(Compress::new(ContentEncoding::Br)).service(
|
||||
web::resource("/").route(web::to(move || Response::Ok().body(STR))),
|
||||
),
|
||||
)
|
||||
))
|
||||
});
|
||||
|
||||
// client request
|
||||
@@ -427,7 +447,7 @@ fn test_body_brotli() {
|
||||
fn test_encoding() {
|
||||
let mut srv = TestServer::new(move || {
|
||||
HttpService::new(
|
||||
App::new().enable_encoding().service(
|
||||
App::new().wrap(Compress::default()).service(
|
||||
web::resource("/")
|
||||
.route(web::to(move |body: Bytes| Response::Ok().body(body))),
|
||||
),
|
||||
@@ -456,7 +476,7 @@ fn test_encoding() {
|
||||
fn test_gzip_encoding() {
|
||||
let mut srv = TestServer::new(move || {
|
||||
HttpService::new(
|
||||
App::new().chain(encoding::Decompress::new()).service(
|
||||
App::new().service(
|
||||
web::resource("/")
|
||||
.route(web::to(move |body: Bytes| Response::Ok().body(body))),
|
||||
),
|
||||
@@ -486,7 +506,7 @@ fn test_gzip_encoding_large() {
|
||||
let data = STR.repeat(10);
|
||||
let mut srv = TestServer::new(move || {
|
||||
h1::H1Service::new(
|
||||
App::new().chain(encoding::Decompress::new()).service(
|
||||
App::new().service(
|
||||
web::resource("/")
|
||||
.route(web::to(move |body: Bytes| Response::Ok().body(body))),
|
||||
),
|
||||
@@ -520,7 +540,7 @@ fn test_reading_gzip_encoding_large_random() {
|
||||
|
||||
let mut srv = TestServer::new(move || {
|
||||
HttpService::new(
|
||||
App::new().chain(encoding::Decompress::new()).service(
|
||||
App::new().service(
|
||||
web::resource("/")
|
||||
.route(web::to(move |body: Bytes| Response::Ok().body(body))),
|
||||
),
|
||||
@@ -550,7 +570,7 @@ fn test_reading_gzip_encoding_large_random() {
|
||||
fn test_reading_deflate_encoding() {
|
||||
let mut srv = TestServer::new(move || {
|
||||
h1::H1Service::new(
|
||||
App::new().chain(encoding::Decompress::new()).service(
|
||||
App::new().service(
|
||||
web::resource("/")
|
||||
.route(web::to(move |body: Bytes| Response::Ok().body(body))),
|
||||
),
|
||||
@@ -580,7 +600,7 @@ fn test_reading_deflate_encoding_large() {
|
||||
let data = STR.repeat(10);
|
||||
let mut srv = TestServer::new(move || {
|
||||
h1::H1Service::new(
|
||||
App::new().chain(encoding::Decompress::new()).service(
|
||||
App::new().service(
|
||||
web::resource("/")
|
||||
.route(web::to(move |body: Bytes| Response::Ok().body(body))),
|
||||
),
|
||||
@@ -614,7 +634,7 @@ fn test_reading_deflate_encoding_large_random() {
|
||||
|
||||
let mut srv = TestServer::new(move || {
|
||||
h1::H1Service::new(
|
||||
App::new().chain(encoding::Decompress::new()).service(
|
||||
App::new().service(
|
||||
web::resource("/")
|
||||
.route(web::to(move |body: Bytes| Response::Ok().body(body))),
|
||||
),
|
||||
@@ -644,7 +664,7 @@ fn test_reading_deflate_encoding_large_random() {
|
||||
fn test_brotli_encoding() {
|
||||
let mut srv = TestServer::new(move || {
|
||||
h1::H1Service::new(
|
||||
App::new().chain(encoding::Decompress::new()).service(
|
||||
App::new().service(
|
||||
web::resource("/")
|
||||
.route(web::to(move |body: Bytes| Response::Ok().body(body))),
|
||||
),
|
||||
@@ -674,7 +694,7 @@ fn test_brotli_encoding_large() {
|
||||
let data = STR.repeat(10);
|
||||
let mut srv = TestServer::new(move || {
|
||||
h1::H1Service::new(
|
||||
App::new().chain(encoding::Decompress::new()).service(
|
||||
App::new().service(
|
||||
web::resource("/")
|
||||
.route(web::to(move |body: Bytes| Response::Ok().body(body))),
|
||||
),
|
||||
|
Reference in New Issue
Block a user